]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_update.c
- Correct typo in Copyright
[bacula/bacula] / bacula / src / dird / ua_update.c
1 /*
2  *
3  *   Bacula Director -- Update command processing
4  *     Split from ua_cmds.c March 2005
5  *
6  *     Kern Sibbald, September MM
7  *
8  *   Version $Id$
9  */
10 /*
11    Copyright (C) 2000-2005 Kern Sibbald
12
13    This program is free software; you can redistribute it and/or
14    modify it under the terms of the GNU General Public License
15    version 2 as amended with additional clauses defined in the
16    file LICENSE in the main source directory.
17
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
21    the file LICENSE for additional details.
22
23  */
24
25 #include "bacula.h"
26 #include "dird.h"
27
28 /* External variables */
29 extern char *list_pool;               /* in sql_cmds.c */
30
31 /* Imported functions */
32 void set_pooldbr_from_poolres(POOL_DBR *pr, POOL *pool, e_pool_op op);
33 int update_slots(UAContext *ua);
34
35
36 /* Forward referenced functions */
37 static int update_volume(UAContext *ua);
38 static int update_pool(UAContext *ua);
39
40 /*
41  * Update a Pool Record in the database.
42  *  It is always updated from the Resource record.
43  *
44  *    update pool=<pool-name>
45  *         updates pool from Pool resource
46  *    update media pool=<pool-name> volume=<volume-name>
47  *         changes pool info for volume
48  *    update slots [scan=...]
49  *         updates autochanger slots
50  */
51 int update_cmd(UAContext *ua, const char *cmd)
52 {
53    static const char *kw[] = {
54       N_("media"),  /* 0 */
55       N_("volume"), /* 1 */
56       N_("pool"),   /* 2 */
57       N_("slots"),  /* 3 */
58       NULL};
59
60    if (!open_db(ua)) {
61       return 1;
62    }
63
64    switch (find_arg_keyword(ua, kw)) {
65    case 0:
66    case 1:
67       update_volume(ua);
68       return 1;
69    case 2:
70       update_pool(ua);
71       return 1;
72    case 3:
73       update_slots(ua);
74       return 1;
75    default:
76       break;
77    }
78
79    start_prompt(ua, _("Update choice:\n"));
80    add_prompt(ua, _("Volume parameters"));
81    add_prompt(ua, _("Pool from resource"));
82    add_prompt(ua, _("Slots from autochanger"));
83    switch (do_prompt(ua, _("item"), _("Choose catalog item to update"), NULL, 0)) {
84    case 0:
85       update_volume(ua);
86       break;
87    case 1:
88       update_pool(ua);
89       break;
90    case 2:
91       update_slots(ua);
92       break;
93    default:
94       break;
95    }
96    return 1;
97 }
98
99 static void update_volstatus(UAContext *ua, const char *val, MEDIA_DBR *mr)
100 {
101    POOLMEM *query = get_pool_memory(PM_MESSAGE);
102    const char *kw[] = {
103       "Append",
104       "Archive",
105       "Disabled",
106       "Full",
107       "Used",
108       "Cleaning",
109       "Recycle",
110       "Read-Only",
111       NULL};
112    bool found = false;
113    int i;
114
115    for (i=0; kw[i]; i++) {
116       if (strcasecmp(val, kw[i]) == 0) {
117          found = true;
118          break;
119       }
120    }
121    if (!found) {
122       bsendmsg(ua, _("Invalid VolStatus specified: %s\n"), val);
123    } else {
124       char ed1[50];
125       bstrncpy(mr->VolStatus, kw[i], sizeof(mr->VolStatus));
126       Mmsg(query, "UPDATE Media SET VolStatus='%s' WHERE MediaId=%s",
127          mr->VolStatus, edit_int64(mr->MediaId,ed1));
128       if (!db_sql_query(ua->db, query, NULL, NULL)) {
129          bsendmsg(ua, "%s", db_strerror(ua->db));
130       } else {
131          bsendmsg(ua, _("New Volume status is: %s\n"), mr->VolStatus);
132       }
133    }
134    free_pool_memory(query);
135 }
136
137 static void update_volretention(UAContext *ua, char *val, MEDIA_DBR *mr)
138 {
139    char ed1[150], ed2[50];
140    POOLMEM *query;
141    if (!duration_to_utime(val, &mr->VolRetention)) {
142       bsendmsg(ua, _("Invalid retention period specified: %s\n"), val);
143       return;
144    }
145    query = get_pool_memory(PM_MESSAGE);
146    Mmsg(query, "UPDATE Media SET VolRetention=%s WHERE MediaId=%s",
147       edit_uint64(mr->VolRetention, ed1), edit_int64(mr->MediaId,ed2));
148    if (!db_sql_query(ua->db, query, NULL, NULL)) {
149       bsendmsg(ua, "%s", db_strerror(ua->db));
150    } else {
151       bsendmsg(ua, _("New retention period is: %s\n"),
152          edit_utime(mr->VolRetention, ed1, sizeof(ed1)));
153    }
154    free_pool_memory(query);
155 }
156
157 static void update_voluseduration(UAContext *ua, char *val, MEDIA_DBR *mr)
158 {
159    char ed1[150], ed2[50];
160    POOLMEM *query;
161
162    if (!duration_to_utime(val, &mr->VolUseDuration)) {
163       bsendmsg(ua, _("Invalid use duration specified: %s\n"), val);
164       return;
165    }
166    query = get_pool_memory(PM_MESSAGE);
167    Mmsg(query, "UPDATE Media SET VolUseDuration=%s WHERE MediaId=%s",
168       edit_uint64(mr->VolUseDuration, ed1), edit_int64(mr->MediaId,ed2));
169    if (!db_sql_query(ua->db, query, NULL, NULL)) {
170       bsendmsg(ua, "%s", db_strerror(ua->db));
171    } else {
172       bsendmsg(ua, _("New use duration is: %s\n"),
173          edit_utime(mr->VolUseDuration, ed1, sizeof(ed1)));
174    }
175    free_pool_memory(query);
176 }
177
178 static void update_volmaxjobs(UAContext *ua, char *val, MEDIA_DBR *mr)
179 {
180    POOLMEM *query = get_pool_memory(PM_MESSAGE);
181    char ed1[50];
182    Mmsg(query, "UPDATE Media SET MaxVolJobs=%s WHERE MediaId=%s",
183       val, edit_int64(mr->MediaId,ed1));
184    if (!db_sql_query(ua->db, query, NULL, NULL)) {
185       bsendmsg(ua, "%s", db_strerror(ua->db));
186    } else {
187       bsendmsg(ua, _("New max jobs is: %s\n"), val);
188    }
189    free_pool_memory(query);
190 }
191
192 static void update_volmaxfiles(UAContext *ua, char *val, MEDIA_DBR *mr)
193 {
194    POOLMEM *query = get_pool_memory(PM_MESSAGE);
195    char ed1[50];
196    Mmsg(query, "UPDATE Media SET MaxVolFiles=%s WHERE MediaId=%s",
197       val, edit_int64(mr->MediaId, ed1));
198    if (!db_sql_query(ua->db, query, NULL, NULL)) {
199       bsendmsg(ua, "%s", db_strerror(ua->db));
200    } else {
201       bsendmsg(ua, _("New max files is: %s\n"), val);
202    }
203    free_pool_memory(query);
204 }
205
206 static void update_volmaxbytes(UAContext *ua, char *val, MEDIA_DBR *mr)
207 {
208    uint64_t maxbytes;
209    char ed1[50], ed2[50];
210    POOLMEM *query;
211
212    if (!size_to_uint64(val, strlen(val), &maxbytes)) {
213       bsendmsg(ua, _("Invalid max. bytes specification: %s\n"), val);
214       return;
215    }
216    query = get_pool_memory(PM_MESSAGE);
217    Mmsg(query, "UPDATE Media SET MaxVolBytes=%s WHERE MediaId=%s",
218       edit_uint64(maxbytes, ed1), edit_int64(mr->MediaId, ed2));
219    if (!db_sql_query(ua->db, query, NULL, NULL)) {
220       bsendmsg(ua, "%s", db_strerror(ua->db));
221    } else {
222       bsendmsg(ua, _("New Max bytes is: %s\n"), edit_uint64(maxbytes, ed1));
223    }
224    free_pool_memory(query);
225 }
226
227 static void update_volrecycle(UAContext *ua, char *val, MEDIA_DBR *mr)
228 {
229    int recycle;
230    char ed1[50];
231    POOLMEM *query;
232    if (strcasecmp(val, _("yes")) == 0) {
233       recycle = 1;
234    } else if (strcasecmp(val, _("no")) == 0) {
235       recycle = 0;
236    } else {
237       bsendmsg(ua, _("Invalid value. It must by yes or no.\n"));
238       return;
239    }
240    query = get_pool_memory(PM_MESSAGE);
241    Mmsg(query, "UPDATE Media SET Recycle=%d WHERE MediaId=%s",
242       recycle, edit_int64(mr->MediaId, ed1));
243    if (!db_sql_query(ua->db, query, NULL, NULL)) {
244       bsendmsg(ua, "%s", db_strerror(ua->db));
245    } else {
246       bsendmsg(ua, _("New Recycle flag is: %s\n"),
247          mr->Recycle==1?_("yes"):_("no"));
248    }
249    free_pool_memory(query);
250 }
251
252 /* Modify the Pool in which this Volume is located */
253 static void update_vol_pool(UAContext *ua, char *val, MEDIA_DBR *mr, POOL_DBR *opr)
254 {
255    POOL_DBR pr;
256    POOLMEM *query;
257    char ed1[50], ed2[50];
258
259    memset(&pr, 0, sizeof(pr));
260    bstrncpy(pr.Name, val, sizeof(pr.Name));
261    if (!get_pool_dbr(ua, &pr)) {
262       return;
263    }
264    mr->PoolId = pr.PoolId;            /* set new PoolId */
265    /*
266     */
267    query = get_pool_memory(PM_MESSAGE);
268    db_lock(ua->db);
269    Mmsg(query, "UPDATE Media SET PoolId=%s WHERE MediaId=%s",
270       edit_int64(mr->PoolId, ed1),
271       edit_int64(mr->MediaId, ed2));
272    if (!db_sql_query(ua->db, query, NULL, NULL)) {
273       bsendmsg(ua, "%s", db_strerror(ua->db));
274    } else {
275       bsendmsg(ua, _("New Pool is: %s\n"), pr.Name);
276       opr->NumVols--;
277       if (!db_update_pool_record(ua->jcr, ua->db, opr)) {
278          bsendmsg(ua, "%s", db_strerror(ua->db));
279       }
280       pr.NumVols++;
281       if (!db_update_pool_record(ua->jcr, ua->db, &pr)) {
282          bsendmsg(ua, "%s", db_strerror(ua->db));
283       }
284    }
285    db_unlock(ua->db);
286    free_pool_memory(query);
287 }
288
289 /*
290  * Refresh the Volume information from the Pool record
291  */
292 static void update_volfrompool(UAContext *ua, MEDIA_DBR *mr)
293 {
294    POOL_DBR pr;
295
296    memset(&pr, 0, sizeof(pr));
297    pr.PoolId = mr->PoolId;
298    if (!db_get_pool_record(ua->jcr, ua->db, &pr) ||
299        !acl_access_ok(ua, Pool_ACL, pr.Name)) {
300       return;
301    }
302    set_pool_dbr_defaults_in_media_dbr(mr, &pr);
303    if (!db_update_media_defaults(ua->jcr, ua->db, mr)) {
304       bsendmsg(ua, _("Error updating Volume record: ERR=%s"), db_strerror(ua->db));
305    } else {
306       bsendmsg(ua, _("Volume defaults updated from \"%s\" Pool record.\n"),
307          pr.Name);
308    }
309 }
310
311 /*
312  * Refresh the Volume information from the Pool record
313  *   for all Volumes
314  */
315 static void update_all_vols_from_pool(UAContext *ua)
316 {
317    POOL_DBR pr;
318    MEDIA_DBR mr;
319
320    memset(&pr, 0, sizeof(pr));
321    memset(&mr, 0, sizeof(mr));
322    if (!get_pool_dbr(ua, &pr)) {
323       return;
324    }
325    set_pool_dbr_defaults_in_media_dbr(&mr, &pr);
326    mr.PoolId = pr.PoolId;
327    if (!db_update_media_defaults(ua->jcr, ua->db, &mr)) {
328       bsendmsg(ua, _("Error updating Volume records: ERR=%s"), db_strerror(ua->db));
329    } else {
330       bsendmsg(ua, _("All Volume defaults updated from Pool record.\n"));
331    }
332 }
333
334
335 /*
336  * Update a media record -- allows you to change the
337  *  Volume status. E.g. if you want Bacula to stop
338  *  writing on the volume, set it to anything other
339  *  than Append.
340  */
341 static int update_volume(UAContext *ua)
342 {
343    MEDIA_DBR mr;
344    POOL_DBR pr;
345    POOLMEM *query;
346    char ed1[130];
347    bool done = false;
348    const char *kw[] = {
349       N_("VolStatus"),                /* 0 */
350       N_("VolRetention"),             /* 1 */
351       N_("VolUse"),                   /* 2 */
352       N_("MaxVolJobs"),               /* 3 */
353       N_("MaxVolFiles"),              /* 4 */
354       N_("MaxVolBytes"),              /* 5 */
355       N_("Recycle"),                  /* 6 */
356       N_("Pool"),                     /* 7 */
357       N_("FromPool"),                 /* 8 */
358       N_("AllFromPool"),              /* 9 */
359       NULL };
360
361    for (int i=0; kw[i]; i++) {
362       int j;
363       POOL_DBR pr;
364       if ((j=find_arg_with_value(ua, kw[i])) > 0) {
365          if (i != 9 && !select_media_dbr(ua, &mr)) {
366             return 0;
367          }
368          switch (i) {
369          case 0:
370             update_volstatus(ua, ua->argv[j], &mr);
371             break;
372          case 1:
373             update_volretention(ua, ua->argv[j], &mr);
374             break;
375          case 2:
376             update_voluseduration(ua, ua->argv[j], &mr);
377             break;
378          case 3:
379             update_volmaxjobs(ua, ua->argv[j], &mr);
380             break;
381          case 4:
382             update_volmaxfiles(ua, ua->argv[j], &mr);
383             break;
384          case 5:
385             update_volmaxbytes(ua, ua->argv[j], &mr);
386             break;
387          case 6:
388             update_volrecycle(ua, ua->argv[j], &mr);
389             break;
390          case 7:
391             memset(&pr, 0, sizeof(POOL_DBR));
392             pr.PoolId = mr.PoolId;
393             if (!db_get_pool_record(ua->jcr, ua->db, &pr)) {
394                bsendmsg(ua, "%s", db_strerror(ua->db));
395                break;
396             }
397             update_vol_pool(ua, ua->argv[j], &mr, &pr);
398             break;
399          case 8:
400             update_volfrompool(ua, &mr);
401             return 1;
402          case 9:
403             update_all_vols_from_pool(ua);
404             return 1;
405          }
406          done = true;
407       }
408    }
409
410    for ( ; !done; ) {
411       if (!select_media_dbr(ua, &mr)) {
412          return 0;
413       }
414       bsendmsg(ua, _("Updating Volume \"%s\"\n"), mr.VolumeName);
415       start_prompt(ua, _("Parameters to modify:\n"));
416       add_prompt(ua, _("Volume Status"));
417       add_prompt(ua, _("Volume Retention Period"));
418       add_prompt(ua, _("Volume Use Duration"));
419       add_prompt(ua, _("Maximum Volume Jobs"));
420       add_prompt(ua, _("Maximum Volume Files"));
421       add_prompt(ua, _("Maximum Volume Bytes"));
422       add_prompt(ua, _("Recycle Flag"));
423       add_prompt(ua, _("Slot"));
424       add_prompt(ua, _("InChanger Flag"));
425       add_prompt(ua, _("Volume Files"));
426       add_prompt(ua, _("Pool"));
427       add_prompt(ua, _("Volume from Pool"));
428       add_prompt(ua, _("All Volumes from Pool"));
429       add_prompt(ua, _("Done"));
430       switch (do_prompt(ua, "", _("Select parameter to modify"), NULL, 0)) {
431       case 0:                         /* Volume Status */
432          /* Modify Volume Status */
433          bsendmsg(ua, _("Current Volume status is: %s\n"), mr.VolStatus);
434          start_prompt(ua, _("Possible Values are:\n"));
435          add_prompt(ua, "Append");      /* Better not translate these as */
436          add_prompt(ua, "Archive");     /* They are known in the database code */
437          add_prompt(ua, "Disabled");
438          add_prompt(ua, "Full");
439          add_prompt(ua, "Used");
440          add_prompt(ua, "Cleaning");
441          if (strcmp(mr.VolStatus, "Purged") == 0) {
442             add_prompt(ua, "Recycle");
443          }
444          add_prompt(ua, "Read-Only");
445          if (do_prompt(ua, "", _("Choose new Volume Status"), ua->cmd, sizeof(mr.VolStatus)) < 0) {
446             return 1;
447          }
448          update_volstatus(ua, ua->cmd, &mr);
449          break;
450       case 1:                         /* Retention */
451          bsendmsg(ua, _("Current retention period is: %s\n"),
452             edit_utime(mr.VolRetention, ed1, sizeof(ed1)));
453          if (!get_cmd(ua, _("Enter Volume Retention period: "))) {
454             return 0;
455          }
456          update_volretention(ua, ua->cmd, &mr);
457          break;
458
459       case 2:                         /* Use Duration */
460          bsendmsg(ua, _("Current use duration is: %s\n"),
461             edit_utime(mr.VolUseDuration, ed1, sizeof(ed1)));
462          if (!get_cmd(ua, _("Enter Volume Use Duration: "))) {
463             return 0;
464          }
465          update_voluseduration(ua, ua->cmd, &mr);
466          break;
467
468       case 3:                         /* Max Jobs */
469          bsendmsg(ua, _("Current max jobs is: %u\n"), mr.MaxVolJobs);
470          if (!get_pint(ua, _("Enter new Maximum Jobs: "))) {
471             return 0;
472          }
473          update_volmaxjobs(ua, ua->cmd, &mr);
474          break;
475
476       case 4:                         /* Max Files */
477          bsendmsg(ua, _("Current max files is: %u\n"), mr.MaxVolFiles);
478          if (!get_pint(ua, _("Enter new Maximum Files: "))) {
479             return 0;
480          }
481          update_volmaxfiles(ua, ua->cmd, &mr);
482          break;
483
484       case 5:                         /* Max Bytes */
485          bsendmsg(ua, _("Current value is: %s\n"), edit_uint64(mr.MaxVolBytes, ed1));
486          if (!get_cmd(ua, _("Enter new Maximum Bytes: "))) {
487             return 0;
488          }
489          update_volmaxbytes(ua, ua->cmd, &mr);
490          break;
491
492
493       case 6:                         /* Recycle */
494          bsendmsg(ua, _("Current recycle flag is: %s\n"),
495             mr.Recycle==1?_("yes"):_("no"));
496          if (!get_yesno(ua, _("Enter new Recycle status: "))) {
497             return 0;
498          }
499          update_volrecycle(ua, ua->cmd, &mr);
500          break;
501
502       case 7:                         /* Slot */
503          int Slot;
504
505          memset(&pr, 0, sizeof(POOL_DBR));
506          pr.PoolId = mr.PoolId;
507          if (!db_get_pool_record(ua->jcr, ua->db, &pr)) {
508             bsendmsg(ua, "%s", db_strerror(ua->db));
509             return 0;
510          }
511          bsendmsg(ua, _("Current Slot is: %d\n"), mr.Slot);
512          if (!get_pint(ua, _("Enter new Slot: "))) {
513             return 0;
514          }
515          Slot = ua->pint32_val;
516          if (pr.MaxVols > 0 && Slot > (int)pr.MaxVols) {
517             bsendmsg(ua, _("Invalid slot, it must be between 0 and %d\n"),
518                pr.MaxVols);
519             break;
520          }
521          mr.Slot = Slot;
522          /*
523           * Make sure to use db_update... rather than doing this directly,
524           *   so that any Slot is handled correctly.
525           */
526          if (!db_update_media_record(ua->jcr, ua->db, &mr)) {
527             bsendmsg(ua, _("Error updating media record Slot: ERR=%s"), db_strerror(ua->db));
528          } else {
529             bsendmsg(ua, _("New Slot is: %d\n"), mr.Slot);
530          }
531          break;
532
533       case 8:                         /* InChanger */
534          bsendmsg(ua, _("Current InChanger flag is: %d\n"), mr.InChanger);
535          if (!get_yesno(ua, _("Set InChanger flag? yes/no: "))) {
536             return 0;
537          }
538          mr.InChanger = ua->pint32_val;
539          /*
540           * Make sure to use db_update... rather than doing this directly,
541           *   so that any Slot is handled correctly.
542           */
543          if (!db_update_media_record(ua->jcr, ua->db, &mr)) {
544             bsendmsg(ua, _("Error updating media record Slot: ERR=%s"), db_strerror(ua->db));
545          } else {
546             bsendmsg(ua, _("New InChanger flag is: %d\n"), mr.InChanger);
547          }
548          break;
549
550
551       case 9:                         /* Volume Files */
552          int32_t VolFiles;
553          bsendmsg(ua, _("Warning changing Volume Files can result\n"
554                         "in loss of data on your Volume\n\n"));
555          bsendmsg(ua, _("Current Volume Files is: %u\n"), mr.VolFiles);
556          if (!get_pint(ua, _("Enter new number of Files for Volume: "))) {
557             return 0;
558          }
559          VolFiles = ua->pint32_val;
560          if (VolFiles != (int)(mr.VolFiles + 1)) {
561             bsendmsg(ua, _("Normally, you should only increase Volume Files by one!\n"));
562             if (!get_yesno(ua, _("Continue? (yes/no): ")) || ua->pint32_val == 0) {
563                break;
564             }
565          }
566          query = get_pool_memory(PM_MESSAGE);
567          Mmsg(query, "UPDATE Media SET VolFiles=%u WHERE MediaId=%s",
568             VolFiles, edit_int64(mr.MediaId, ed1));
569          if (!db_sql_query(ua->db, query, NULL, NULL)) {
570             bsendmsg(ua, "%s", db_strerror(ua->db));
571          } else {
572             bsendmsg(ua, _("New Volume Files is: %u\n"), VolFiles);
573          }
574          free_pool_memory(query);
575          break;
576
577       case 10:                        /* Volume's Pool */
578          memset(&pr, 0, sizeof(POOL_DBR));
579          pr.PoolId = mr.PoolId;
580          if (!db_get_pool_record(ua->jcr, ua->db, &pr)) {
581             bsendmsg(ua, "%s", db_strerror(ua->db));
582             return 0;
583          }
584          bsendmsg(ua, _("Current Pool is: %s\n"), pr.Name);
585          if (!get_cmd(ua, _("Enter new Pool name: "))) {
586             return 0;
587          }
588          update_vol_pool(ua, ua->cmd, &mr, &pr);
589          return 1;
590
591       case 11:
592          update_volfrompool(ua, &mr);
593          return 1;
594       case 12:
595          update_all_vols_from_pool(ua);
596          return 1;
597       default:                        /* Done or error */
598          bsendmsg(ua, "Selection done.\n");
599          return 1;
600       }
601    }
602    return 1;
603 }
604
605 /*
606  * Update pool record -- pull info from current POOL resource
607  */
608 static int update_pool(UAContext *ua)
609 {
610    POOL_DBR  pr;
611    int id;
612    POOL *pool;
613    POOLMEM *query;
614    char ed1[50];
615
616    pool = get_pool_resource(ua);
617    if (!pool) {
618       return 0;
619    }
620
621    memset(&pr, 0, sizeof(pr));
622    bstrncpy(pr.Name, pool->hdr.name, sizeof(pr.Name));
623    if (!get_pool_dbr(ua, &pr)) {
624       return 0;
625    }
626
627    set_pooldbr_from_poolres(&pr, pool, POOL_OP_UPDATE); /* update */
628
629    id = db_update_pool_record(ua->jcr, ua->db, &pr);
630    if (id <= 0) {
631       bsendmsg(ua, _("db_update_pool_record returned %d. ERR=%s\n"),
632          id, db_strerror(ua->db));
633    }
634    query = get_pool_memory(PM_MESSAGE);
635    Mmsg(query, list_pool, edit_int64(pr.PoolId, ed1));
636    db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
637    free_pool_memory(query);
638    bsendmsg(ua, _("Pool DB record updated from resource.\n"));
639    return 1;
640 }