]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/snapshot.c
b6dfcabd7339ee96bed137ba6da7ea311b0d0243
[bacula/bacula] / bacula / src / dird / snapshot.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2015 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18
19    Written by Eric Bollengier, 2015
20 */
21
22 #include "bacula.h"
23 #include "dird.h"
24
25 static char CreateSnap[] = "CatReq Job=%127s new_snapshot name=%127s volume=%s device=%s tdate=%d type=%127s retention=%50s";
26 static char ListSnap[] = "CatReq Job=%127s list_snapshot name=%127s volume=%s device=%s tdate=%d type=%127s before=%50s after=%50s";
27 static char DelSnap[] = "CatReq Job=%127s del_snapshot name=%127s device=%s";
28 static char snapretentioncmd[] = "snapshot retention=%s\n";
29
30
31 static void send_list(void *ctx, const char *msg)
32 {
33    BSOCK *bs = (BSOCK *)ctx;
34    bs->fsend("%s", msg);
35 }
36
37
38 /* Scan command line for common snapshot arguments */
39 static void snapshot_scan_cmdline(UAContext *ua, int start, SNAPSHOT_DBR *snapdbr)
40 {
41    for (int j=start; j<ua->argc; j++) {
42       if (strcasecmp(ua->argk[j], NT_("device")) == 0 && ua->argv[j]) {
43          snapdbr->Device = bstrdup(ua->argv[j]);
44          snapdbr->need_to_free = true;
45
46       } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
47          snapdbr->JobId = str_to_int64(ua->argv[j]);
48
49       } else if (strcasecmp(ua->argk[j], NT_("type")) == 0 && ua->argv[j]) {
50          bstrncpy(snapdbr->Type, ua->argv[j], sizeof(snapdbr->Type));
51
52       } else if (strcasecmp(ua->argk[j], NT_("client")) == 0 && ua->argv[j]) {
53          bstrncpy(snapdbr->Client, ua->argv[j], sizeof(snapdbr->Client));
54
55       } else if (strcasecmp(ua->argk[j], NT_("snapshotid")) == 0 && ua->argv[j]) {
56          snapdbr->SnapshotId = str_to_int64(ua->argv[j]);
57
58       } else if (strcasecmp(ua->argk[j], NT_("snapshot")) == 0 && ua->argv[j]) {
59          bstrncpy(snapdbr->Name, ua->argv[j], sizeof(snapdbr->Name));
60
61       } else if (strcasecmp(ua->argk[j], NT_("volume")) == 0 && ua->argv[j]) {
62          snapdbr->Volume = bstrdup(ua->argv[j]);
63          snapdbr->need_to_free = true;
64
65       } else if (strcasecmp(ua->argk[j], NT_("createdate")) == 0 && ua->argv[j]) {
66          bstrncpy(snapdbr->CreateDate, ua->argv[j], sizeof(snapdbr->CreateDate));
67          snapdbr->CreateTDate = str_to_utime(ua->argv[j]);
68
69       } else if (strcasecmp(ua->argk[j], NT_("createtdate")) == 0 && ua->argv[j]) {
70          snapdbr->CreateTDate = str_to_uint64(ua->argv[j]);
71          bstrutime(snapdbr->CreateDate, sizeof(snapdbr->CreateDate), snapdbr->CreateTDate);
72
73       } else if (strcasecmp(ua->argk[j], NT_("name")) == 0 && ua->argv[j]) {
74          bstrncpy(snapdbr->Name, ua->argv[j], sizeof(snapdbr->Name));
75
76       } else if (strcasecmp(ua->argk[j], NT_("size")) == 0 && ua->argv[j]) {
77          snapdbr->Size = str_to_uint64(ua->argv[j]);
78
79       } else if (strcasecmp(ua->argk[j], NT_("status")) == 0 && ua->argv[j]) {
80          snapdbr->status = str_to_uint64(ua->argv[j]);
81
82       } else if (strcasecmp(ua->argk[j], NT_("error")) == 0 && ua->argv[j]) {
83          snapdbr->errmsg = bstrdup(ua->argv[j]);
84          unbash_spaces(snapdbr->errmsg);
85          snapdbr->need_to_free = true;
86
87       } else {
88          continue;
89       }
90    }
91 }
92
93 /* Get a snapshot record, and check that the current UA can access to the Client/FileSet */
94 static int get_snapshot_record(UAContext *ua, SNAPSHOT_DBR *snapdbr)
95 {
96    if (!open_client_db(ua)) {
97       Dmsg0(10, "Unable to open database\n");
98       return 0;
99    }
100    if (!db_get_snapshot_record(ua->jcr, ua->db, snapdbr)) {
101       Dmsg0(10, "Unable to get snapshot record\n");
102       return 0;
103    }
104    /* Need to check if the client is authorized */
105    if (!acl_access_ok(ua, Client_ACL, snapdbr->Client)) {
106       Dmsg0(10, "Client access denied\n");
107       return 0;
108    }
109    if (snapdbr->FileSetId && !acl_access_ok(ua, FileSet_ACL, snapdbr->FileSet)) {
110       Dmsg0(10, "Fileset access denied\n");
111       return 0;
112    }
113    return 1;
114 }
115
116 static int check_response(UAContext *ua, BSOCK *sd, const char *resp, const char *cmd)
117 {
118    if (sd->errors) {
119       return 0;
120    }
121    if (bget_msg(sd) > 0) {
122       unbash_spaces(sd->msg);
123       if (strcmp(sd->msg, resp) == 0) {
124          return 1;
125       }
126    }
127    if (sd->is_error()) {
128       ua->error_msg(_("Comm error with SD. bad response to %s. ERR=%s\n"),
129                     cmd, sd->bstrerror());
130    } else {
131       ua->error_msg(_("Bad response from SD to %s command. Wanted %s, got %s len=%ld\n"),
132                     cmd, resp, sd->msg, sd->msglen);
133    }
134    return 0;
135 }
136
137 bool send_snapshot_retention(JCR *jcr, utime_t val)
138 {
139    BSOCK *fd = jcr->file_bsock;
140    char ed1[50];
141    if (val > 0 && jcr->FDVersion >= 13) {
142       fd->fsend(snapretentioncmd, edit_uint64(val, ed1));
143       if (!response(jcr, fd, (char*)"2000 Snapshot retention\n", "set Snapshot Retention", DISPLAY_ERROR)) {
144          jcr->snapshot_retention = 0;      /* can't set snapshot retention */
145          return false;
146       }
147    }
148    return true;
149 }
150
151 /* Called from delete_cmd() in ua_cmd.c */
152 int delete_snapshot(UAContext *ua)
153 {
154    POOLMEM     *out;
155    SNAPSHOT_DBR snapdbr;
156    CLIENT      *client;
157    BSOCK       *fd;
158
159    if (!open_new_client_db(ua)) {
160       return 1;
161    }
162
163    /* If the client or the fileset are not authorized,
164     * the function will fail.
165     */
166    if (!select_snapshot_dbr(ua, &snapdbr)) {
167       ua->error_msg(_("Snapshot not found\n"));
168       snapdbr.debug(0);
169       return 0;
170    }
171
172    client = (CLIENT *)GetResWithName(R_CLIENT, snapdbr.Client);
173    if (!client) {
174       ua->error_msg(_("Client resource not found\n"));
175       return 0;
176    }
177
178    /* Connect to File daemon */
179    ua->jcr->client = client;
180
181    /* Try to connect for 15 seconds */
182    ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
183       client->name(), client->address, client->FDport);
184    if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
185       ua->error_msg(_("Failed to connect to Client.\n"));
186       ua->jcr->client = NULL;
187       return 0;
188    }
189
190    fd = ua->jcr->file_bsock;
191    out = get_pool_memory(PM_FNAME);
192    fd->fsend("snapshot del %s\n", snapdbr.as_arg(&out));
193    free_pool_memory(out);
194
195    /* If the snapshot is not found, still delete ours */
196    if (check_response(ua, fd, "2000 Snapshot deleted ERR=\n", "Snapshot")) {
197       ua->send_msg(_("Snapshot \"%s\" deleted from client %s\n"), snapdbr.Name,
198                    snapdbr.Client);
199    }
200
201    ua->jcr->file_bsock->signal(BNET_TERMINATE);
202    free_bsock(ua->jcr->file_bsock);
203    ua->jcr->client = NULL;
204
205    db_delete_snapshot_record(ua->jcr, ua->db, &snapdbr);
206    ua->send_msg(_("Snapshot \"%s\" deleted from catalog\n"), snapdbr.Name);
207    return 1;
208 }
209
210 /* Called from menu, if snap_list is valid, the snapshot
211  * list will be stored in this list. (not_owned_by_alist)
212  */
213 int list_snapshot(UAContext *ua, alist *snap_list)
214 {
215    SNAPSHOT_DBR snap;
216    POOLMEM     *buf;
217    CLIENT      *client;
218    BSOCK       *fd;
219
220    client = select_client_resource(ua);
221    if (!client) {
222       return 0;
223    }
224
225    /* Connect to File daemon */
226    ua->jcr->client = client;
227
228    /* Try to connect for 15 seconds */
229    ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
230       client->name(), client->address, client->FDport);
231    if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
232       ua->error_msg(_("Failed to connect to Client.\n"));
233       return 0;
234    }
235
236    fd = ua->jcr->file_bsock;
237
238    /* The command line can have filters */
239    snapshot_scan_cmdline(ua, 0, &snap);
240    buf = get_pool_memory(PM_FNAME);
241
242    fd->fsend("snapshot list %s\n", snap.as_arg(&buf));
243    while (fd->recv() >= 0) {
244       if (snap_list) {
245          SNAPSHOT_DBR *snapr = new SNAPSHOT_DBR();
246          parse_args(fd->msg, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
247          snapshot_scan_cmdline(ua, 0, snapr);
248          bstrncpy(snapr->Client, client->name(), sizeof(snapr->Client));
249          snap_list->append(snapr);
250          snapr->debug(0);
251       } else {
252          ua->send_msg("%s", fd->msg);
253       }
254    }
255
256    /* Reset the UA arg list */
257    parse_args(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
258
259    ua->jcr->file_bsock->signal(BNET_TERMINATE);
260    free_bsock(ua->jcr->file_bsock);
261    ua->jcr->client = NULL;
262    free_pool_memory(buf);
263    return 1;
264 }
265
266 static void storeit(void *ctx, const char *msg)
267 {
268    char ed1[51];
269    alist *lst = (alist *)ctx;
270    if (sscanf(msg, "snapshotid=%50s", ed1) == 1) {
271       lst->append((void *)(intptr_t) str_to_int64(ed1));
272    }
273 }
274
275 int prune_snapshot(UAContext *ua)
276 {
277    /* First, we get the snapshot list that can be pruned */
278    CLIENT *client = NULL;
279    BSOCK  *fd = NULL;
280    POOLMEM *buf = NULL;
281    SNAPSHOT_DBR snapdbr;
282    alist *lst;
283    intptr_t id;
284
285    snapshot_scan_cmdline(ua, 0, &snapdbr);
286    snapdbr.expired = true;
287    if (!open_client_db(ua)) {
288       Dmsg0(10, "Unable to open database\n");
289       return 0;
290    }
291
292    buf = get_pool_memory(PM_FNAME);
293    lst = New(alist(10, not_owned_by_alist));
294    db_list_snapshot_records(ua->jcr, ua->db, &snapdbr, storeit, lst, ARG_LIST);
295    foreach_alist(id, lst) {
296       snapdbr.reset();
297       snapdbr.SnapshotId = id;
298       if (get_snapshot_record(ua, &snapdbr)) {
299
300          ua->send_msg(_("Snapshot \"%s\" on Client %s\n"), snapdbr.Name, snapdbr.Client);
301          if (!confirm_retention_yesno(ua, snapdbr.Retention, "Snapshot")) {
302             continue;
303          }
304
305          if (client && strcmp(client->hdr.name, snapdbr.Client) != 0) {
306             ua->jcr->file_bsock->signal(BNET_TERMINATE);
307             free_bsock(ua->jcr->file_bsock);
308             ua->jcr->client = NULL;
309             client = NULL;
310          }
311
312          if (!client) {
313             client = (CLIENT *)GetResWithName(R_CLIENT, snapdbr.Client);
314             if (!client) {
315                continue;
316             }
317
318             /* Connect to File daemon */
319             ua->jcr->client = client;
320
321             /* Try to connect for 15 seconds */
322             ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
323                          client->name(), client->address, client->FDport);
324             if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
325                ua->error_msg(_("Failed to connect to Client.\n"));
326                free_bsock(ua->jcr->file_bsock);
327                ua->jcr->client = NULL;
328                client = NULL;
329                continue;
330             }
331
332             fd = ua->jcr->file_bsock;
333          }
334
335          fd->fsend("snapshot del %s\n", snapdbr.as_arg(&buf));
336
337          fd->recv();
338          if (strncmp(fd->msg, "2000", 4) == 0) {
339             ua->send_msg("Snapshot %s deleted\n", snapdbr.Volume);
340             db_delete_snapshot_record(ua->jcr, ua->db, &snapdbr);
341          } else {
342             unbash_spaces(fd->msg);
343             ua->send_msg("%s", fd->msg);
344          }
345       }
346    }
347
348    if (ua->jcr->file_bsock) {
349       ua->jcr->file_bsock->signal(BNET_TERMINATE);
350       free_bsock(ua->jcr->file_bsock);
351       ua->jcr->client = NULL;
352    }
353
354    free_pool_memory(buf);
355    delete lst;
356    return 1;
357 }
358
359
360 /* Called from the FD, in catreq.c */
361 int snapshot_catreq(JCR *jcr, BSOCK *bs)
362 {
363    SNAPSHOT_DBR snapdbr;
364    char Job[MAX_NAME_LENGTH], ed1[50];
365    POOLMEM *vol = get_memory(bs->msglen);
366    POOLMEM *dev = get_memory(bs->msglen);
367    POOLMEM *err = get_pool_memory(PM_MESSAGE);
368    int n, ret = 1, expired;
369    *vol = *dev = 0;
370
371    Dmsg1(DT_SNAPSHOT|10, "Get snapshot catalog request %s\n", bs->msg);
372
373    /* We need to create a snapshot record in the catalog */
374    n = sscanf(bs->msg, CreateSnap, Job, snapdbr.Name, vol, dev, 
375               &snapdbr.CreateTDate, snapdbr.Type, ed1);
376    if (n == 7) {
377       snapdbr.Volume = vol;
378       snapdbr.Device = dev;
379       snapdbr.JobId = jcr->JobId;
380       unbash_spaces(snapdbr.Name);
381       unbash_spaces(snapdbr.Volume);
382       unbash_spaces(snapdbr.Device);
383       snapdbr.Retention = str_to_uint64(ed1);
384       bstrftimes(snapdbr.CreateDate, sizeof(snapdbr.CreateDate), snapdbr.CreateTDate);
385       unbash_spaces(snapdbr.Type);
386       bstrncpy(snapdbr.Client, jcr->client->hdr.name, sizeof(snapdbr.Client));
387       bstrncpy(snapdbr.FileSet, (jcr->fileset)?jcr->fileset->hdr.name:"", sizeof(snapdbr.FileSet));
388
389       Dmsg1(DT_SNAPSHOT|10, "Creating snapshot %s\n", snapdbr.Name);
390       snapdbr.debug(20);
391
392       /* We lock the db before to keep the error message */
393       db_lock(jcr->db);
394       ret = db_create_snapshot_record(jcr, jcr->db, &snapdbr);
395       pm_strcpy(err, jcr->db->errmsg);
396       db_unlock(jcr->db);
397
398       if (ret) {
399          bs->fsend("1000 Snapshot created\n");
400
401       } else {
402          bs->fsend("1999 Snapshot not created ERR=%s\n", err);
403       }
404       goto bail_out;
405    }
406
407    n = sscanf(bs->msg, ListSnap, Job, snapdbr.Name, vol, dev, &snapdbr.CreateTDate, snapdbr.Type,
408               snapdbr.created_before, snapdbr.created_after, &expired);
409    if (n == 8) {
410       snapdbr.Volume = vol;
411       snapdbr.Device = dev;
412       unbash_spaces(snapdbr.Name);
413       unbash_spaces(snapdbr.Volume);
414       unbash_spaces(snapdbr.Device);
415       bstrftimes(snapdbr.CreateDate, sizeof(snapdbr.CreateDate), snapdbr.CreateTDate);
416       unbash_spaces(snapdbr.Type);
417       unbash_spaces(snapdbr.created_before);
418       unbash_spaces(snapdbr.created_after);
419       bstrncpy(snapdbr.Client, jcr->client->hdr.name, sizeof(snapdbr.Client));
420       snapdbr.expired = (expired != 0);
421       Dmsg0(DT_SNAPSHOT|10, "List snapshots\n");
422       snapdbr.debug(20);
423       db_list_snapshot_records(jcr, jcr->db, &snapdbr, send_list, bs, ARG_LIST);
424       bs->signal(BNET_EOD);
425       goto bail_out;
426    }
427
428    n = sscanf(bs->msg, DelSnap, Job, snapdbr.Name, dev);
429    if (n == 3) {
430       snapdbr.Device = dev;
431       unbash_spaces(snapdbr.Name);
432       unbash_spaces(snapdbr.Device);
433       bstrncpy(snapdbr.Client, jcr->client->hdr.name, sizeof(snapdbr.Client));
434       Dmsg2(DT_SNAPSHOT|10, "Delete snapshot %s from %s\n", snapdbr.Name, snapdbr.Client);
435       snapdbr.debug(20);      
436
437       /* We lock the db before to keep the error message */
438       db_lock(jcr->db);
439       ret = db_delete_snapshot_record(jcr, jcr->db, &snapdbr);
440       pm_strcpy(err, jcr->db->errmsg);
441       db_unlock(jcr->db);
442
443       if (ret) {
444          bs->fsend("1000 Snapshot deleted\n");
445
446       } else {
447          bs->fsend("1999 Snapshot not deleted ERR=%s\n", err);
448       }
449       goto bail_out;
450    }
451    ret = 0;
452
453 bail_out:
454    free_pool_memory(vol);
455    free_pool_memory(dev);
456    free_pool_memory(err);
457    return ret;
458 }
459
460 /* List snapshots, allow to use some parameters from the command line */
461 void snapshot_list(UAContext *ua, int i, DB_LIST_HANDLER *sendit, e_list_type llist)
462 {
463    SNAPSHOT_DBR snapdbr;
464    snapshot_scan_cmdline(ua, i, &snapdbr);
465    if (open_new_client_db(ua)) {
466       db_list_snapshot_records(ua->jcr, ua->db, &snapdbr, sendit, ua, llist);
467    }
468 }
469
470 static int list_client_snapshot(UAContext *ua, bool sync)
471 {
472    SNAPSHOT_DBR *s, stemp;
473    alist *lst;
474 //   char ed1[50];
475
476    if (sync) {
477       if (!open_new_client_db(ua)) {
478          return 1;
479       }
480    }
481
482    lst = New(alist(10, not_owned_by_alist));
483    if (list_snapshot(ua, lst)) {
484       foreach_alist(s, lst) {
485          ua->send_msg(_(
486             "Snapshot      %s:\n"
487             "  Volume:     %s\n"
488             "  Device:     %s\n"
489             "  CreateDate: %s\n"
490 //            "  Size:       %sB\n",
491             "  Type:       %s\n"
492             "  Status:     %s\n"
493             "  Error:      %s\n"),
494             s->Name, NPRT(s->Volume), NPRT(s->Device),
495             s->CreateDate,
496 //          edit_uint64_with_suffix(s->Size, ed1),
497                       s->Type, s->status?_("OK"):_("Error"), s->errmsg);
498          if (sync && s->Device && *s->Name) {
499             stemp.reset();
500             stemp.Device = s->Device;
501             bstrncpy(stemp.Name, s->Name, sizeof(stemp.Name));
502             if (!db_get_snapshot_record(ua->jcr, ua->db, &stemp)) {
503                if (db_create_snapshot_record(ua->jcr, ua->db, s)) {
504                   ua->send_msg(_("Snapshot added in Catalog\n"));
505                }
506             }
507          }
508       }
509       if (lst->size() == 0) {
510          ua->send_msg(_("No snapshot found\n"));
511       }
512    }
513    /* Cleanup the list */
514    foreach_alist (s, lst) {
515       delete s;
516    }
517    delete lst;
518    return 1;
519 }
520
521 /* Ask client to create/prune/delete a snapshot via the command line */
522 int snapshot_cmd(UAContext *ua, const char *cmd)
523 {
524    SNAPSHOT_DBR snapdbr;
525    for (int i=0; i<ua->argc; i++) {
526       if (strcasecmp(ua->argk[i], NT_("purge")) == 0) {
527
528       } else if (strcasecmp(ua->argk[i], NT_("prune")) == 0) {
529          return prune_snapshot(ua);
530
531       } else if (strcasecmp(ua->argk[i], NT_("listclient")) == 0) {
532          return list_client_snapshot(ua, false);
533
534       } else if (strcasecmp(ua->argk[i], NT_("list")) == 0) {
535          snapshot_list(ua, 0, prtit, HORZ_LIST);
536          return 1;
537
538       } else if (strcasecmp(ua->argk[i], NT_("create")) == 0) {
539          /* We need a job definition, or a client */
540
541       } else if (strcasecmp(ua->argk[i], NT_("delete")) == 0) {
542          return delete_snapshot(ua);
543
544       } else if (strcasecmp(ua->argk[i], NT_("status")) == 0) {
545
546       } else if (strcasecmp(ua->argk[i], NT_("sync")) == 0) {
547          return list_client_snapshot(ua, true);
548
549       } else if (strcasecmp(ua->argk[i], NT_("update")) == 0) {
550          return update_snapshot(ua);
551
552       } else {
553          continue;
554       }
555    }
556
557    for ( ;; ) {
558
559       start_prompt(ua, _("Snapshot choice: \n"));
560       add_prompt(ua, _("List snapshots in Catalog"));
561       add_prompt(ua, _("List snapshots on Client"));
562       add_prompt(ua, _("Prune snapshots"));
563       add_prompt(ua, _("Delete snapshot"));
564       add_prompt(ua, _("Update snapshot parameters"));
565       add_prompt(ua, _("Update catalog with Client snapshots"));
566       add_prompt(ua, _("Done"));
567
568       switch(do_prompt(ua, "", _("Select action to perform on Snapshot Engine"), NULL, 0)) {
569       case 0:                         /* list catalog */
570          snapshot_list(ua, 0, prtit, HORZ_LIST);
571          break;
572       case 1:                         /* list client */
573          list_client_snapshot(ua, false);
574          break;
575       case 2:                   /* prune */
576          prune_snapshot(ua);
577          break;
578       case 3:                   /* delete */
579          delete_snapshot(ua);
580          break;
581       case 4:                      /* update snapshot */
582          update_snapshot(ua);
583          break;
584       case 5:                      /* sync snapshot */
585          list_client_snapshot(ua, true);
586          break;
587       case 6:                   /* done */
588       default:
589          ua->info_msg(_("Selection terminated.\n"));
590          return 1;
591       }
592    }
593    return 1;
594 }
595
596 /* Select a Snapshot record from the database, might be in ua_select.c */
597 int select_snapshot_dbr(UAContext *ua, SNAPSHOT_DBR *sr)
598 {
599    int   ret = 0;
600    char *p;
601    POOLMEM *err = get_pool_memory(PM_FNAME);
602    *err=0;
603
604    sr->reset();
605    snapshot_scan_cmdline(ua, 0, sr);
606
607    if (sr->SnapshotId == 0 && (sr->Name[0] == 0 || sr->Client[0] == 0)) {
608       CLIENT_DBR cr;
609       memset(&cr, 0, sizeof(cr));
610       /* Get the pool from client=<client-name> */
611       if (!get_client_dbr(ua, &cr)) {
612          goto bail_out;
613       }
614       sr->ClientId = cr.ClientId;
615       db_list_snapshot_records(ua->jcr, ua->db, sr, prtit, ua, HORZ_LIST);
616       if (!get_cmd(ua, _("Enter a SnapshotId: "))) {
617          goto bail_out;
618       }
619       p = ua->cmd;
620       if (*p == '*') {
621          p++;
622       }
623       if (is_a_number(p)) {
624          sr->SnapshotId = str_to_int64(p);
625       } else {
626          goto bail_out;
627       }
628    }
629
630    if (!get_snapshot_record(ua, sr)) {
631       ua->error_msg(_("Unable to get Snapshot record.\n"));
632       goto bail_out;
633    }
634
635    ret = 1;
636
637 bail_out:
638    if (!ret && *err) {
639       ua->error_msg("%s", err);
640    }
641    free_pool_memory(err);
642    return ret;
643 }
644
645 /* This part should be in ua_update.c */
646 static void update_snapretention(UAContext *ua, char *val, SNAPSHOT_DBR *sr)
647 {
648    char ed1[150];
649    POOL_MEM tmp(PM_MESSAGE);
650    bool ret;
651    if (!duration_to_utime(val, &sr->Retention)) {
652       ua->error_msg(_("Invalid retention period specified: %s\n"), val);
653       return;
654    }
655
656    db_lock(ua->db);
657    if (!(ret = db_update_snapshot_record(ua->jcr, ua->db, sr))) {
658       pm_strcpy(tmp, db_strerror(ua->db));
659    }
660    db_unlock(ua->db);
661
662    if (!ret) {
663       ua->error_msg("%s", tmp.c_str());
664
665    } else {
666       ua->info_msg(_("New retention period is: %s\n"),
667          edit_utime(sr->Retention, ed1, sizeof(ed1)));
668    }
669 }
670
671 /* This part should be in ua_update.c */
672 static void update_snapcomment(UAContext *ua, char *val, SNAPSHOT_DBR *sr)
673 {
674    POOL_MEM tmp(PM_MESSAGE);
675    bool ret;
676
677    bstrncpy(sr->Comment, val, sizeof(sr->Comment));
678
679    db_lock(ua->db);
680    if (!(ret = db_update_snapshot_record(ua->jcr, ua->db, sr))) {
681       pm_strcpy(tmp, db_strerror(ua->db));
682    }
683    db_unlock(ua->db);
684
685    if (!ret) {
686       ua->error_msg("%s", tmp.c_str());
687
688    } else {
689       ua->info_msg(_("New Comment is: %s\n"), sr->Comment);
690    }
691 }
692
693 /* This part should be in ua_update.c */
694 bool update_snapshot(UAContext *ua)
695 {
696    SNAPSHOT_DBR sr;
697    POOL_MEM ret;
698    char ed1[130];
699    bool done = false;
700    int i;
701    const char *kw[] = {
702       NT_("Retention"),                /* 0 */
703       NT_("Comment"),                  /* 1 */
704       NULL };
705
706    for (i=0; kw[i]; i++) {
707       int j;
708       if ((j=find_arg_with_value(ua, kw[i])) > 0) {
709          /* If all from pool don't select a media record */
710          if (!select_snapshot_dbr(ua, &sr)) {
711             return 0;
712          }
713          switch (i) {
714          case 0:
715             update_snapretention(ua, ua->argv[j], &sr);
716             break;
717          case 1:
718             update_snapcomment(ua, ua->argv[j], &sr);
719             break;
720          default:
721             break;
722          }
723          done = true;
724       }
725    }
726
727    for ( ; !done; ) {
728       start_prompt(ua, _("Parameters to modify:\n"));
729       add_prompt(ua, _("Snapshot Retention Period"));  /* 0 */
730       add_prompt(ua, _("Snapshot Comment"));           /* 1 */
731       add_prompt(ua, _("Done"));                       /* 2 */
732       i = do_prompt(ua, "", _("Select parameter to modify"), NULL, 0);
733       if (i == 2) {
734          return 0;
735       }
736
737       if (!select_snapshot_dbr(ua, &sr)) {  /* Get Snapshot record */
738          return 0;
739       }
740       ua->info_msg(_("Updating Snapshot \"%s\" on \"%s\"\n"), sr.Name, sr.Client);
741
742       switch (i) {
743       case 0:                         /* Snapshot retention */
744          ua->info_msg(_("Current retention period is: %s\n"),
745             edit_utime(sr.Retention, ed1, sizeof(ed1)));
746          if (!get_cmd(ua, _("Enter Snapshot Retention period: "))) {
747             return 0;
748          }
749          update_snapretention(ua, ua->cmd, &sr);
750          break;
751       case 1:
752          ua->info_msg(_("Current comment is: %s\n"), NPRTB(sr.Comment));
753          if (!get_cmd(ua, _("Enter Snapshot comment: "))) {
754             return 0;
755          }
756          update_snapcomment(ua, ua->cmd, &sr);
757          break;
758       default:                        /* Done or error */
759          ua->info_msg(_("Selection terminated.\n"));
760          return 1;
761       }
762    }
763    return 1;
764 }