2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
19 Written by Eric Bollengier, 2015
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";
31 static void send_list(void *ctx, const char *msg)
33 BSOCK *bs = (BSOCK *)ctx;
38 /* Scan command line for common snapshot arguments */
39 static void snapshot_scan_cmdline(UAContext *ua, int start, SNAPSHOT_DBR *snapdbr)
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;
46 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
47 snapdbr->JobId = str_to_int64(ua->argv[j]);
49 } else if (strcasecmp(ua->argk[j], NT_("type")) == 0 && ua->argv[j]) {
50 bstrncpy(snapdbr->Type, ua->argv[j], sizeof(snapdbr->Type));
52 } else if (strcasecmp(ua->argk[j], NT_("client")) == 0 && ua->argv[j]) {
53 bstrncpy(snapdbr->Client, ua->argv[j], sizeof(snapdbr->Client));
55 } else if (strcasecmp(ua->argk[j], NT_("snapshotid")) == 0 && ua->argv[j]) {
56 snapdbr->SnapshotId = str_to_int64(ua->argv[j]);
58 } else if (strcasecmp(ua->argk[j], NT_("snapshot")) == 0 && ua->argv[j]) {
59 bstrncpy(snapdbr->Name, ua->argv[j], sizeof(snapdbr->Name));
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;
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]);
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);
73 } else if (strcasecmp(ua->argk[j], NT_("name")) == 0 && ua->argv[j]) {
74 bstrncpy(snapdbr->Name, ua->argv[j], sizeof(snapdbr->Name));
76 } else if (strcasecmp(ua->argk[j], NT_("size")) == 0 && ua->argv[j]) {
77 snapdbr->Size = str_to_uint64(ua->argv[j]);
79 } else if (strcasecmp(ua->argk[j], NT_("status")) == 0 && ua->argv[j]) {
80 snapdbr->status = str_to_uint64(ua->argv[j]);
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;
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)
96 if (!open_client_db(ua)) {
97 Dmsg0(10, "Unable to open database\n");
100 if (!db_get_snapshot_record(ua->jcr, ua->db, snapdbr)) {
101 Dmsg0(10, "Unable to get snapshot record\n");
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");
109 if (snapdbr->FileSetId && !acl_access_ok(ua, FileSet_ACL, snapdbr->FileSet)) {
110 Dmsg0(10, "Fileset access denied\n");
116 static int check_response(UAContext *ua, BSOCK *sd, const char *resp, const char *cmd)
121 if (bget_msg(sd) > 0) {
122 unbash_spaces(sd->msg);
123 if (strcmp(sd->msg, resp) == 0) {
127 if (sd->is_error()) {
128 ua->error_msg(_("Comm error with SD. bad response to %s. ERR=%s\n"),
129 cmd, sd->bstrerror());
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);
137 bool send_snapshot_retention(JCR *jcr, utime_t val)
139 BSOCK *fd = jcr->file_bsock;
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 */
151 /* Called from delete_cmd() in ua_cmd.c */
152 int delete_snapshot(UAContext *ua)
155 SNAPSHOT_DBR snapdbr;
159 if (!open_new_client_db(ua)) {
163 /* If the client or the fileset are not authorized,
164 * the function will fail.
166 if (!select_snapshot_dbr(ua, &snapdbr)) {
167 ua->error_msg(_("Snapshot not found\n"));
172 client = (CLIENT *)GetResWithName(R_CLIENT, snapdbr.Client);
174 ua->error_msg(_("Client resource not found\n"));
178 /* Connect to File daemon */
179 ua->jcr->client = client;
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;
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);
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,
201 ua->jcr->file_bsock->signal(BNET_TERMINATE);
202 free_bsock(ua->jcr->file_bsock);
203 ua->jcr->client = NULL;
205 db_delete_snapshot_record(ua->jcr, ua->db, &snapdbr);
206 ua->send_msg(_("Snapshot \"%s\" deleted from catalog\n"), snapdbr.Name);
210 /* Called from menu, if snap_list is valid, the snapshot
211 * list will be stored in this list. (not_owned_by_alist)
213 int list_snapshot(UAContext *ua, alist *snap_list)
220 client = select_client_resource(ua);
225 /* Connect to File daemon */
226 ua->jcr->client = client;
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"));
236 fd = ua->jcr->file_bsock;
238 /* The command line can have filters */
239 snapshot_scan_cmdline(ua, 0, &snap);
240 buf = get_pool_memory(PM_FNAME);
242 fd->fsend("snapshot list %s\n", snap.as_arg(&buf));
243 while (fd->recv() >= 0) {
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);
252 ua->send_msg("%s", fd->msg);
256 /* Reset the UA arg list */
257 parse_args(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS);
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);
266 static void storeit(void *ctx, const char *msg)
269 alist *lst = (alist *)ctx;
270 if (sscanf(msg, "snapshotid=%50s", ed1) == 1) {
271 lst->append((void *)(intptr_t) str_to_int64(ed1));
275 int prune_snapshot(UAContext *ua)
277 /* First, we get the snapshot list that can be pruned */
278 CLIENT *client = NULL;
281 SNAPSHOT_DBR snapdbr;
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");
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) {
297 snapdbr.SnapshotId = id;
298 if (get_snapshot_record(ua, &snapdbr)) {
300 ua->send_msg(_("Snapshot \"%s\" on Client %s\n"), snapdbr.Name, snapdbr.Client);
301 if (!confirm_retention_yesno(ua, snapdbr.Retention, "Snapshot")) {
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;
313 client = (CLIENT *)GetResWithName(R_CLIENT, snapdbr.Client);
318 /* Connect to File daemon */
319 ua->jcr->client = client;
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;
332 fd = ua->jcr->file_bsock;
335 fd->fsend("snapshot del %s\n", snapdbr.as_arg(&buf));
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);
342 unbash_spaces(fd->msg);
343 ua->send_msg("%s", fd->msg);
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;
354 free_pool_memory(buf);
360 /* Called from the FD, in catreq.c */
361 int snapshot_catreq(JCR *jcr, BSOCK *bs)
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;
371 Dmsg1(DT_SNAPSHOT|10, "Get snapshot catalog request %s\n", bs->msg);
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);
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));
389 Dmsg1(DT_SNAPSHOT|10, "Creating snapshot %s\n", snapdbr.Name);
392 /* We lock the db before to keep the error message */
394 ret = db_create_snapshot_record(jcr, jcr->db, &snapdbr);
395 pm_strcpy(err, jcr->db->errmsg);
399 bs->fsend("1000 Snapshot created\n");
402 bs->fsend("1999 Snapshot not created ERR=%s\n", err);
407 n = sscanf(bs->msg, ListSnap, Job, snapdbr.Name, vol, dev, &snapdbr.CreateTDate, snapdbr.Type,
408 snapdbr.created_before, snapdbr.created_after, &expired);
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");
423 db_list_snapshot_records(jcr, jcr->db, &snapdbr, send_list, bs, ARG_LIST);
424 bs->signal(BNET_EOD);
428 n = sscanf(bs->msg, DelSnap, Job, snapdbr.Name, dev);
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);
437 /* We lock the db before to keep the error message */
439 ret = db_delete_snapshot_record(jcr, jcr->db, &snapdbr);
440 pm_strcpy(err, jcr->db->errmsg);
444 bs->fsend("1000 Snapshot deleted\n");
447 bs->fsend("1999 Snapshot not deleted ERR=%s\n", err);
454 free_pool_memory(vol);
455 free_pool_memory(dev);
456 free_pool_memory(err);
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)
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);
470 static int list_client_snapshot(UAContext *ua, bool sync)
472 SNAPSHOT_DBR *s, stemp;
477 if (!open_new_client_db(ua)) {
482 lst = New(alist(10, not_owned_by_alist));
483 if (list_snapshot(ua, lst)) {
484 foreach_alist(s, lst) {
494 s->Name, NPRT(s->Volume), NPRT(s->Device),
496 // edit_uint64_with_suffix(s->Size, ed1),
497 s->Type, s->status?_("OK"):_("Error"), s->errmsg);
498 if (sync && s->Device && *s->Name) {
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"));
509 if (lst->size() == 0) {
510 ua->send_msg(_("No snapshot found\n"));
513 /* Cleanup the list */
514 foreach_alist (s, lst) {
521 /* Ask client to create/prune/delete a snapshot via the command line */
522 int snapshot_cmd(UAContext *ua, const char *cmd)
524 SNAPSHOT_DBR snapdbr;
525 for (int i=0; i<ua->argc; i++) {
526 if (strcasecmp(ua->argk[i], NT_("purge")) == 0) {
528 } else if (strcasecmp(ua->argk[i], NT_("prune")) == 0) {
529 return prune_snapshot(ua);
531 } else if (strcasecmp(ua->argk[i], NT_("listclient")) == 0) {
532 return list_client_snapshot(ua, false);
534 } else if (strcasecmp(ua->argk[i], NT_("list")) == 0) {
535 snapshot_list(ua, 0, prtit, HORZ_LIST);
538 } else if (strcasecmp(ua->argk[i], NT_("create")) == 0) {
539 /* We need a job definition, or a client */
541 } else if (strcasecmp(ua->argk[i], NT_("delete")) == 0) {
542 return delete_snapshot(ua);
544 } else if (strcasecmp(ua->argk[i], NT_("status")) == 0) {
546 } else if (strcasecmp(ua->argk[i], NT_("sync")) == 0) {
547 return list_client_snapshot(ua, true);
549 } else if (strcasecmp(ua->argk[i], NT_("update")) == 0) {
550 return update_snapshot(ua);
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"));
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);
572 case 1: /* list client */
573 list_client_snapshot(ua, false);
581 case 4: /* update snapshot */
584 case 5: /* sync snapshot */
585 list_client_snapshot(ua, true);
589 ua->info_msg(_("Selection terminated.\n"));
596 /* Select a Snapshot record from the database, might be in ua_select.c */
597 int select_snapshot_dbr(UAContext *ua, SNAPSHOT_DBR *sr)
601 POOLMEM *err = get_pool_memory(PM_FNAME);
605 snapshot_scan_cmdline(ua, 0, sr);
607 if (sr->SnapshotId == 0 && (sr->Name[0] == 0 || sr->Client[0] == 0)) {
609 memset(&cr, 0, sizeof(cr));
610 /* Get the pool from client=<client-name> */
611 if (!get_client_dbr(ua, &cr)) {
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: "))) {
623 if (is_a_number(p)) {
624 sr->SnapshotId = str_to_int64(p);
630 if (!get_snapshot_record(ua, sr)) {
631 ua->error_msg(_("Unable to get Snapshot record.\n"));
639 ua->error_msg("%s", err);
641 free_pool_memory(err);
645 /* This part should be in ua_update.c */
646 static void update_snapretention(UAContext *ua, char *val, SNAPSHOT_DBR *sr)
649 POOL_MEM tmp(PM_MESSAGE);
651 if (!duration_to_utime(val, &sr->Retention)) {
652 ua->error_msg(_("Invalid retention period specified: %s\n"), val);
657 if (!(ret = db_update_snapshot_record(ua->jcr, ua->db, sr))) {
658 pm_strcpy(tmp, db_strerror(ua->db));
663 ua->error_msg("%s", tmp.c_str());
666 ua->info_msg(_("New retention period is: %s\n"),
667 edit_utime(sr->Retention, ed1, sizeof(ed1)));
671 /* This part should be in ua_update.c */
672 static void update_snapcomment(UAContext *ua, char *val, SNAPSHOT_DBR *sr)
674 POOL_MEM tmp(PM_MESSAGE);
677 bstrncpy(sr->Comment, val, sizeof(sr->Comment));
680 if (!(ret = db_update_snapshot_record(ua->jcr, ua->db, sr))) {
681 pm_strcpy(tmp, db_strerror(ua->db));
686 ua->error_msg("%s", tmp.c_str());
689 ua->info_msg(_("New Comment is: %s\n"), sr->Comment);
693 /* This part should be in ua_update.c */
694 bool update_snapshot(UAContext *ua)
702 NT_("Retention"), /* 0 */
703 NT_("Comment"), /* 1 */
706 for (i=0; kw[i]; i++) {
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)) {
715 update_snapretention(ua, ua->argv[j], &sr);
718 update_snapcomment(ua, ua->argv[j], &sr);
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);
737 if (!select_snapshot_dbr(ua, &sr)) { /* Get Snapshot record */
740 ua->info_msg(_("Updating Snapshot \"%s\" on \"%s\"\n"), sr.Name, sr.Client);
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: "))) {
749 update_snapretention(ua, ua->cmd, &sr);
752 ua->info_msg(_("Current comment is: %s\n"), NPRTB(sr.Comment));
753 if (!get_cmd(ua, _("Enter Snapshot comment: "))) {
756 update_snapcomment(ua, ua->cmd, &sr);
758 default: /* Done or error */
759 ua->info_msg(_("Selection terminated.\n"));