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