2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 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
23 Documentation about snapshot backend
24 ----------------------------------------------------------------
26 The snapshot manager is using environment variables to communicate.
32 create, delete, list, mount, unmount, check, support, subvolume
36 ex: /dev/vgroot/home_Test-2014-01-01_00_00 (lvm)
37 /home/.snapshots/Test-2014-01-01_00_00 (btrfs)
38 /.zfs/snapshot/Test-2014-01-01_00_00 (zfs)
40 The volume name is generated by the create command
44 ex: /dev/vgroot/home (lvm)
48 The device name can be found via getmntent()
51 Snapshot name, usually the Job name
54 Device filesystem type, can be found via getmntent()
58 Snapshot backend type, generated by support command
62 Device mount point, found via getmntent()
64 SNAPSHOT_SNAPMOUNTPOINT
65 Snapshot mount point is generated by the mount command
69 ----------------------------------------------------------------
71 OK: exit code 0 and status=1 in the output
72 ERR: exit code <> 0 and/or status=0 in the output
74 status=1 keyword="value" keyword2="value2"
76 status=0 error="Error message"
80 ----------------------------------------------------------------
83 get SNAPSHOT_DEVICE, SNAPSHOT_FSTYPE, SNAPSHOT_MOUNTPOINT
85 volume="" name="" device="" createtime=""
87 2) Test if a filesystem supports snapshot feature
88 SNAPSHOT_ACTION=support
90 SNAPSHOT_MOUNTPOINT=/home
93 => status=1 type=btrfs device=/home
96 2a) Test if a filesystem contains subvolumes
97 SNAPSHOT_ACTION=subvolumes
101 => dev=10 mountpoint=/home/subvol fstype=btrfs
104 SNAPSHOT_ACTION=create
105 SNAPSHOT_NAME=Test-2014-01-01_00_00
106 SNAPSHOT_DEVICE=/home
107 SNAPSHOT_MOUNTPOINT=/home
108 SNAPSHOT_FSTYPE=btrfs
111 => status=1 volume="/home/.snapshots/Test-2014-01-01_00_00" createtdate=1418935776 type=btrfs
113 4) Mount the snapshot
114 SNAPSHOT_ACTION=mount
115 SNAPSHOT_NAME=Test-2014-01-01_00_00
116 SNAPSHOT_DEVICE=/home
117 SNAPSHOT_MOUNTPOINT=/home
118 SNAPSHOT_FSTYPE=btrfs
121 => status=1 volume="/home/.snapshots/Test-2014-01-01_00_00" createtdate=1418935776 type=btrfs
124 5) Unmount the snapshot
125 SNAPSHOT_ACTION=unmount
126 SNAPSHOT_SNAPMOUNTPOINT=/home/.snapshots/Test-2014-01-01_00_00
130 6) Delete the snapshot
131 SNAPSHOT_ACTION=delete
132 SNAPSHOT_VOLUME=/home/.snapshot/Test-2014-01-01_00_00
140 #define USE_CMD_PARSER
141 #include "plugins/fd/fd_common.h"
143 #include "fd_snapshot.h"
144 #define APPMANAGER_CMD "%eappmanager"
145 #define APP_DIR "/tmp/app.d"
146 #define SNAPSHOT_CMD "%ebsnapshot"
148 /* Defined in messages.c */
149 extern char *exepath;
151 /* Catalog interface with the director */
152 static char CreateSnap[] = "CatReq Job=%s new_snapshot name=%s volume=%s device=%s tdate=%d type=%s retention=%s";
153 static char DelSnap[] = "CatReq Job=%s del_snapshot name=%s device=%s";
154 static char GetSnap[] = "CatReq Job=%s get_snapshot name=%s volume=%s";
156 /* Command line interface with the director */
157 static char LsCmd[] = "snapshot ls name=%127s volume=%s device=%s tdate=%d type=%127s path=%s";
158 static char DelCmd[] = "snapshot del name=%127s volume=%s device=%s tdate=%d type=%127s";
159 static char QueryCmd[] = "snapshot query name=%127s volume=%s device=%s tdate=%d type=%127s";
160 static char PruneCmd[] = "snapshot prune volume=%s type=%127s";
161 static char SyncCmd[] = "snapshot sync volume=%s type=%127%";
162 static char ListCmd[] = "snapshot list";
163 static char ConfCmd[] = "snapshot retention=%50s";
165 /* Small function to quickly tell us if we can do snapshot here */
166 static bool is_snapshot_supported(JCR *jcr)
170 POOLMEM *cmd = get_pool_memory(PM_FNAME);
175 /* We are not really interested by arguments, just
179 for (p=me->snapshot_command; *p; p++) {
186 str = NPRTB(exepath);
195 } else if (*p == ' ') {
206 ret = stat(cmd, &sp) == 0;
207 free_pool_memory(cmd);
208 Dmsg1(10, "Snapshot = %d\n", ret);
212 /* Return the default snapshot handler, must be freed at the end */
213 char *snapshot_get_command()
215 return bstrdup(SNAPSHOT_CMD);
218 /* Initialize the snapshot manager at the begining of the
219 * job and create snapshots
221 bool open_snapshot_backup_session(JCR *jcr)
223 if (!is_snapshot_supported(jcr)) {
224 Dmsg0(DT_SNAPSHOT, "Snapshot not supported\n");
228 jcr->snap_mgr = New(snapshot_manager(jcr));
229 /* Get all volumes and subvolumes */
230 if (!jcr->snap_mgr->scan_mtab()) {
231 berrno be; /* error probably in errno */
232 Dmsg1(DT_SNAPSHOT, "Unable to scan mtab. ERR=%s\n", be.bstrerror());
233 Jmsg(jcr, M_ERROR, 0, "Unable to scan mtab to determine devices to snapshot\n");
236 /* Match the volume list with the fileset */
237 if (!jcr->snap_mgr->scan_fileset()) {
238 Jmsg(jcr, M_ERROR,0, "Unable to scan fileset to determine devices to snapshot\n");
241 /* Create fileset needed */
242 if (!jcr->snap_mgr->create_snapshots()) {
243 /* Error message already displayed if needed */
246 return true; /* We should have some snapshots */
249 /* Command that can be called from outside */
250 bool list_all_snapshots(JCR *jcr, alist *lst)
252 snapshot_manager snap_mgr(jcr);
254 /* Get all volumes and subvolumes */
255 if (!snap_mgr.scan_mtab()) {
259 if (snap_mgr.list_snapshots(lst)) {
265 /* Cleanup the snapshot manager at the end of the job */
266 void close_snapshot_backup_session(JCR *jcr)
269 jcr->snap_mgr->cleanup_snapshots();
270 delete jcr->snap_mgr;
271 jcr->snap_mgr = NULL;
277 /* Device that exists on the system */
278 class fs_device: public SMARTALLOC
283 uint32_t dev; /* dev no */
284 char *mountpoint; /* where it's mounted */
285 char *fstype; /* ntfs, ext3, ext4... */
286 char *device; /* /dev/mapper/xxx */
288 bool supportSnapshotTested; /* True if support() was called */
289 bool isSuitableForSnapshot; /* Compatible with snapshots */
292 snapshot *snap; /* Associated snapshot */
294 dlist *include; /* Where the fs_device was found in the fileset */
295 void *node; /* At which node */
299 dev(0), mountpoint(NULL), fstype(NULL), device(NULL),
300 supportSnapshotTested(false), isSuitableForSnapshot(false), snap(NULL)
304 fs_device(uint32_t adev, const char *adevice, const char *amountpoint, const char *aftype) {
306 fstype = bstrdup(aftype);
307 device = bstrdup(adevice);
308 mountpoint = bstrdup(amountpoint);
309 supportSnapshotTested = false;
310 isSuitableForSnapshot = false;
311 inSnapshotSet = false;
322 /* Call support() and cache the result in supportSnapshotTested and isSuitableForSnapshot */
323 bool can_do_snapshot();
325 void setInFileSet(dlist *inc, void *where) {
326 include = inc; /* where we need to include subvolumes */
327 node = where; /* after which node */
329 inSnapshotSet = true;
332 void set_snap(snapshot *s) {
339 /* The device list is stored in a rblist, using the
340 * dev no as key. The devno can be found in every stat()
343 static int compare_entries(void *item1, void *item2)
345 fs_device *dev1 = (fs_device *) item1;
346 fs_device *dev2 = (fs_device *) item2;
347 if (dev1->dev > dev2->dev) {
350 } else if (dev1->dev < dev2->dev) {
358 static int search_entry(void *item1, void *item2)
360 uint32_t dev1 = (intptr_t) item1;
361 fs_device* dev2 = (fs_device *) item2;
362 if (dev1 > dev2->dev) {
365 } else if (dev1 < dev2->dev) {
373 /* List of all fd_device that are on the system
374 * Some devices are excluded automatically from
375 * the list, such as proc, sysfs, etc...
377 class mtab: public SMARTALLOC
381 int sCount; /* Snapshot count */
382 int dCount; /* Device count */
384 fs_device *elt = NULL;
385 entries = New(rblist(elt, &elt->link));
391 foreach_rblist(elt, entries) {
397 /* Have we devices for snapshot in our list ? */
402 /* Get a fs_device corresponding to a file */
403 fs_device *search(char *file);
405 /* Get subvolumes for a specific device */
406 bool get_subvolumes(uint32_t dev, alist *items, FF_PKT *ff) {
407 fs_device *elt, *elt2;
408 elt = (fs_device *)entries->search((void*)(intptr_t)dev, search_entry);
413 foreach_rblist(elt2, entries) {
414 if (elt2->dev == elt->dev) {
417 if (strncmp(elt2->mountpoint, elt->mountpoint, strlen(elt->mountpoint)) == 0) {
418 /* the mount point is included in the volume */
420 if (file_is_excluded(ff, elt2->mountpoint)) {
421 Dmsg1(DT_SNAPSHOT|50, "Looks to be excluded %s\n", elt2->mountpoint);
428 return items->size() > 0;
431 bool add_in_snapshot_set(char *file, dlist *inc, void *node) {
433 fs_device *elt = search(file);
435 Dmsg1(DT_SNAPSHOT, "%s will not be added to snapshot set\n", file);
436 return sCount == dCount; /* not found in our list, skip it */
438 return add_in_snapshot_set(elt, inc, node);
441 bool add_in_snapshot_set(fs_device *elt, dlist *inc, void *node) {
442 Dmsg4(DT_SNAPSHOT|10, "%s in=%d can=%d tested=%d\n", elt->mountpoint, elt->inSnapshotSet,
443 elt->isSuitableForSnapshot, elt->supportSnapshotTested);
444 if (!elt->inSnapshotSet && elt->can_do_snapshot()) {
445 Dmsg1(DT_SNAPSHOT, "Marking %s for snapshot\n", elt->mountpoint);
446 elt->setInFileSet(inc, node);
449 /* It will help to count when all devices are in the snapshot set */
450 Dmsg2(DT_SNAPSHOT|10, "sCount %d = dCount %d\n", sCount, dCount);
451 return sCount == dCount;
454 bool add_entry(fs_device *vol) {
455 fs_device *ret = (fs_device *) entries->insert(vol, compare_entries);
456 if (ret == vol && vol->snap) {
457 dCount++; /* We skip directly FS such as /proc, /sys or /dev */
463 /* Snapshot descriptor, used to communicate with the snapshot
464 * backend on the system.
466 class snapshot: public SMARTALLOC
472 const char *action; /* current action */
473 char Name[MAX_NAME_LENGTH]; /* Name of the snapshot */
474 char Type[MAX_NAME_LENGTH]; /* lvm, btrfs, netapp */
475 char FSType[MAX_NAME_LENGTH]; /* btrfs, zfs, ext3 */
476 char CreateDate[MAX_TIME_LENGTH]; /* Creation date */
477 time_t CreateTDate; /* Creation date in seconds */
478 int64_t size; /* Size of the snapshot */
479 int status; /* snapshot status */
480 utime_t Retention; /* Snapshot retention, might come from Pool/FileSet */
482 POOLMEM *Volume; /* Path of the volume */
483 POOLMEM *Device; /* Device path */
484 POOLMEM *MountPoint; /* Device Mount point */
485 POOLMEM *SnapMountPoint; /* Snapshot Mount point */
486 POOLMEM *path; /* path used in ls query */
487 POOLMEM *errmsg; /* Error message generated by commands */
488 POOLMEM *SnapDirectory; /* Where snapshots are stored */
490 char **env; /* Variables used to call snapshot */
491 bool mounted; /* True if mounted on SnapMountPoint */
492 bool created; /* True if the snapshot is created */
494 snapshot(JCR *ajcr) {
497 path = get_pool_memory(PM_FNAME);
498 errmsg = get_pool_memory(PM_MESSAGE);
499 Volume = get_pool_memory(PM_FNAME);
500 Device = get_pool_memory(PM_FNAME);
501 MountPoint = get_pool_memory(PM_FNAME);
502 SnapMountPoint = get_pool_memory(PM_FNAME);
503 SnapDirectory = get_pool_memory(PM_FNAME);
508 free_pool_memory(path);
509 free_pool_memory(errmsg);
510 free_pool_memory(Volume);
511 free_pool_memory(Device);
512 free_pool_memory(MountPoint);
513 free_pool_memory(SnapMountPoint);
514 free_pool_memory(SnapDirectory);
519 *SnapDirectory = *Type = *FSType = *SnapMountPoint = 0;
520 *MountPoint = *Volume = *Device = *path = *errmsg = 0;
526 Retention = jcr->snapshot_retention;
529 /* Free the env[] structure */
532 for (int i=0; env[i] ; i++) {
540 void set_device(const char *d) {
541 pm_strcpy(Device, d);
544 void set_mountpoint(const char *d) {
545 pm_strcpy(MountPoint, d);
548 void set_name(const char *n) {
549 bstrncpy(Name, n, sizeof(Name));
552 void set_fstype(const char *n) {
553 bstrncpy(FSType, n, sizeof(FSType));
556 void set_action(const char *a) {
560 /* Convert a real top path to a snapshot path
561 * and set proper variables inside ff_pkt
562 * to translate back all subfiles.
564 bool convert_path(FF_PKT *ff) {
565 if (!*MountPoint || !*SnapMountPoint) {
566 Dmsg2(DT_SNAPSHOT, "MountPoint=%s SnapMountPoint=%s\n",
567 NPRT(MountPoint), NPRT(SnapMountPoint));
570 if (!ff->snap_top_fname) {
571 ff->snap_top_fname = get_pool_memory(PM_FNAME);
573 ff->volume_path = MountPoint; /* /tmp */
574 ff->snapshot_path = SnapMountPoint; /* /tmp/.snapshot/Job.20140502.01.01.01 */
575 ff->top_fname_save = ff->top_fname;
577 int mp_first = strlen(MountPoint); /* will point to after MountPoint in top_fname */
578 int last = pm_strcpy(ff->snap_top_fname, SnapMountPoint);
579 last = MAX(last - 1, 0);
581 /* We need to concat path and avoid double / and no / */
582 if (ff->snap_top_fname[last] == '/') {
583 if (ff->top_fname[mp_first] == '/') {
584 ff->snap_top_fname[last] = 0; /* strip double / */
586 } else { /* no / at all */
587 if (ff->top_fname[mp_first] != '/') {
588 pm_strcat(ff->snap_top_fname, "/");
592 pm_strcat(ff->snap_top_fname, ff->top_fname + mp_first);
593 ff->top_fname = ff->snap_top_fname;
594 ff->strip_snap_path = true;
595 Dmsg1(DT_SNAPSHOT|50, "top_fname=%s\n", ff->top_fname);
599 /* Create a environment used in the child process */
600 int edit_snapshot_env() {
602 POOLMEM *tmp = get_pool_memory(PM_FNAME);
605 /* Update "10" to add more variables */
606 env = (char **) malloc(sizeof(char *) * 10);
609 Mmsg(tmp, "SNAPSHOT_NAME=%s", Name);
610 env[i++] = bstrdup(tmp);
613 Mmsg(tmp, "SNAPSHOT_VOLUME=%s", Volume);
614 env[i++] = bstrdup(tmp);
617 Mmsg(tmp, "SNAPSHOT_DEVICE=%s", Device);
618 env[i++] = bstrdup(tmp);
621 Mmsg(tmp, "SNAPSHOT_TYPE=%s", Type);
622 env[i++] = bstrdup(tmp);
625 Mmsg(tmp, "SNAPSHOT_FSTYPE=%s", FSType);
626 env[i++] = bstrdup(tmp);
629 Mmsg(tmp, "SNAPSHOT_MOUNTPOINT=%s", MountPoint);
630 env[i++] = bstrdup(tmp);
632 if (*SnapDirectory) {
633 Mmsg(tmp, "SNAPSHOT_SNAPDIRECTORY=%s", SnapDirectory);
634 env[i++] = bstrdup(tmp);
636 if (*SnapMountPoint) {
637 Mmsg(tmp, "SNAPSHOT_SNAPMOUNTPOINT=%s", SnapMountPoint);
638 env[i++] = bstrdup(tmp);
640 /* When adding new entries, do not forget to add more slots to env[] */
642 Mmsg(tmp, "SNAPSHOT_ACTION=%s", action);
643 env[i++] = bstrdup(tmp);
645 env[i] = NULL; /* last record */
647 if (chk_dbglvl(DT_SNAPSHOT|100)) {
648 for (i = 0; env[i] ; i++) {
649 Dmsg1(0, "%s\n", env[i]);
653 free_pool_memory(tmp);
657 /* Edit the command line if needed */
658 int edit_snapshot_codes(POOLMEM **omsg, const char *imsg) {
664 for (p=imsg; *p; p++) {
671 str = NPRTB(exepath);
698 str = SnapMountPoint;
712 pm_strcat(omsg, str);
715 if (chk_dbglvl(DT_SNAPSHOT|10)) {
717 Mmsg(tmp, " -d %d -o /tmp/bsnapshot.log ", debug_level);
718 pm_strcat(omsg, tmp.c_str());
721 Dmsg2(DT_SNAPSHOT|30, "edit_snapshot_codes: %s -> %s\n", imsg, *omsg);
725 /* Call the snapshot backend to know if we can snapshot the current FS */
726 int support_snapshot(fs_device *vol) {
731 set_device(vol->device);
732 set_mountpoint(vol->mountpoint);
733 set_fstype(vol->fstype);
735 if (!do_command("support", &cmd)) {
741 Dmsg2(DT_SNAPSHOT|50, "%s snapshot support status=%d\n", vol->mountpoint, status);
745 /* Scan sub volumes for a particular volume */
746 int scan_subvolumes(fs_device *vol, alist *lst) {
750 char *mp=NULL, *fstype=NULL, *device=Device;
753 set_device(vol->device);
754 set_mountpoint(vol->mountpoint);
755 set_fstype(vol->fstype);
757 if (!do_command("subvolumes", &cmd)) {
761 for (int i = 0; i < cmd.argc ; i++) {
762 if (strcasecmp(cmd.argk[i], "Dev") == 0 && cmd.argv[i]) {
763 dev = str_to_int64(cmd.argv[i]);
765 } else if (strcasecmp(cmd.argk[i], "MountPoint") == 0 && cmd.argv[i]) {
768 } else if (strcasecmp(cmd.argk[i], "Device") == 0 && cmd.argv[i]) {
769 device = cmd.argv[i];
771 } else if (strcasecmp(cmd.argk[i], "FSType") == 0 && cmd.argv[i]) {
772 fstype = cmd.argv[i];
773 if (mp && fstype && dev) {
774 fs_device *elt = New(fs_device(dev, device, mp, fstype));
776 /* reset variables */
789 /* Prune current snapshots
790 * - List snapshots available on the director, keep a list locally
791 * - get mtab, list snapshots for all devices, or devices that are in the director list
793 int prune(BSOCK *bs) {
797 /* List local snapshots, list director snapshots, and synchronize the two */
798 int sync(BSOCK *bs) {
802 /* List files from a snapshot
803 * Need to set the Volume and the Path
809 /* Scan common arguments */
810 int scan_arg(arg_parser *cmd) {
811 for (int i = 0; i < cmd->argc ; i++) {
812 if (strcasecmp(cmd->argk[i], "Volume") == 0 && cmd->argv[i]) {
813 pm_strcpy(Volume, cmd->argv[i]);
815 } else if (strcasecmp(cmd->argk[i], "CreateDate") == 0 && cmd->argv[i]) {
816 bstrncpy(CreateDate, cmd->argv[i], sizeof(CreateDate));
817 CreateTDate = str_to_utime(CreateDate);
819 } else if (strcasecmp(cmd->argk[i], "CreateTDate") == 0 && cmd->argv[i]) {
820 CreateTDate = str_to_int64(cmd->argv[i]);
821 bstrftimes(CreateDate, sizeof(CreateDate), CreateTDate);
823 } else if (strcasecmp(cmd->argk[i], "Type") == 0 && cmd->argv[i]) {
824 bstrncpy(Type, cmd->argv[i], sizeof(Type));
826 } else if (strcasecmp(cmd->argk[i], "SnapMountPoint") == 0 && cmd->argv[i]) {
827 pm_strcpy(SnapMountPoint, cmd->argv[i]);
829 } else if (strcasecmp(cmd->argk[i], "SnapDirectory") == 0 && cmd->argv[i]) {
830 pm_strcpy(SnapDirectory, cmd->argv[i]);
832 } else if (strcasecmp(cmd->argk[i], "status") == 0 && cmd->argv[i]) {
833 status = str_to_int64(cmd->argv[i]);
835 } else if (strcasecmp(cmd->argk[i], "Device") == 0 && cmd->argv[i]) {
836 pm_strcpy(Device, cmd->argv[i]);
842 /* Create a snapshot with already given attributes
843 * Need to set Name and Device at the minimum
849 if (!*Name || !*Device) {
853 Dmsg2(DT_SNAPSHOT, "Create Snapshot of %s %s\n", Device, Name);
855 /* TODO: see if we handle multiple snapshots per call */
856 if (!do_command("create", &cmd)) {
873 if (!*Name || !*Volume || !*Device || !*Type) {
877 Dmsg1(DT_SNAPSHOT, "Doing mount of %s\n", Volume);
879 if (!do_command("mount", &cmd)) {
887 mounted = (status > 0 && *SnapMountPoint);
897 if (!*Name || !*SnapMountPoint || !*Type || !mounted) {
901 Dmsg1(DT_SNAPSHOT, "Doing unmount of a %s\n", SnapMountPoint);
903 if (!do_command("unmount", &cmd)) {
915 /* Delete a snapshot with the given device name */
919 if (!*Name || !*Volume || !created) {
922 ret = do_command("delete", &cmd);
927 /* TODO: Need to read stdout as well */
928 int do_command(const char *act, arg_parser *cmd) {
930 POOLMEM *command = get_pool_memory(PM_FNAME);
931 POOLMEM *out = get_pool_memory(PM_FNAME);
934 edit_snapshot_codes(&command, me->snapshot_command);
937 Dmsg1(DT_SNAPSHOT|20, "Execute %s command\n", act);
940 Mmsg(errmsg, _("Error while creating command string %s.\n"), act);
941 Dmsg1(DT_SNAPSHOT, "%s", errmsg);
945 /* If the exit code is 1, we can expect to have a clear error
946 * message in the output
948 if ((rcode = run_program_full_output(command, 600, out, env)) != 0) {
949 if ((rcode & ~b_errno_exit) == 1) { /* exit 1, look the output */
950 if (cmd->parse_cmd(out) == bRC_OK) {
951 int i = cmd->find_arg_with_value("status");
953 /* If we have a status, take it */
954 status = str_to_int64(cmd->argv[i]);
958 i = cmd->find_arg_with_value("error");
960 pm_strcpy(errmsg, cmd->argv[i]);
961 Dmsg1(DT_SNAPSHOT|20, "%s\n", errmsg);
967 Mmsg(errmsg, _("Error while executing \"%s\" %s. %s %s\n"),
968 act, command, out, be.bstrerror());
969 Dmsg1(DT_SNAPSHOT, "%s", errmsg);
973 /* Need to decode the output of the script
974 * TODO: some commands will have multiple lines
976 if (cmd->parse_cmd(out) != bRC_OK) {
977 Dmsg2(DT_SNAPSHOT, "snapshot command %s output error: [%s]\n", act, out);
978 Mmsg(errmsg, _("Unable to parse snapshot command output\n"));
981 Dmsg1(DT_SNAPSHOT|50, "ret = %s\n", out);
984 free_pool_memory(command);
985 free_pool_memory(out);
989 int list(alist *lst) {
990 Dmsg0(DT_SNAPSHOT, "Doing list of a snapshots of a given device\n");
993 int i, ret=0, status=1;
994 char *volume=NULL, *name=NULL, *device=NULL, *createdate=NULL, *error=NULL;
995 utime_t createtdate = 0;
997 /* TODO: Here we need to loop over a list */
998 if (!do_command("list", &cmd)) {
1004 * volume=xxx device=zzzz name=yyy createtdate=12121212 size=xx status=xx error=xx type=lvm
1006 for (i = 0; i < cmd.argc ; i++) {
1007 if (strcasecmp(cmd.argk[i], "Volume") == 0) {
1008 volume = cmd.argv[i];
1010 } else if (strcasecmp(cmd.argk[i], "Name") == 0) {
1013 } else if (strcasecmp(cmd.argk[i], "Device") == 0) {
1014 device = cmd.argv[i];
1016 } else if (strcasecmp(cmd.argk[i], "Error") == 0) {
1017 error = cmd.argv[i];
1019 } else if (strcasecmp(cmd.argk[i], "Status") == 0) {
1020 status = str_to_int64(cmd.argv[i]);
1022 } else if (strcasecmp(cmd.argk[i], "Type") == 0) {
1023 snap = New(snapshot(jcr));
1024 pm_strcpy(snap->Volume, volume);
1025 pm_strcpy(snap->Device, NPRTB(device));
1026 bstrncpy(snap->Name, NPRTB(name), sizeof(snap->Name));
1027 bstrncpy(snap->Type, cmd.argv[i], sizeof(snap->Type));
1028 bstrncpy(snap->CreateDate, createdate, sizeof(snap->CreateDate));
1029 pm_strcpy(snap->errmsg, NPRTB(error));
1030 snap->status = status;
1031 snap->CreateTDate = createtdate;
1032 error = createdate = device = name = volume = NULL;
1037 } else if (strcasecmp(cmd.argk[i], "CreateTDate") == 0) {
1038 createtdate = str_to_int64(cmd.argv[i]);
1040 } else if (strcasecmp(cmd.argk[i], "CreateDate") == 0) {
1041 createdate = cmd.argv[i];
1042 createtdate = str_to_utime(cmd.argv[i]);
1049 /* Query information about snapshot */
1051 Dmsg0(0, "Doing query of a snapshot\n");
1059 if (!do_command("query", &cmd)) {
1063 if ((i = cmd.find_arg_with_value("size")) >= 0) {
1064 size = str_to_int64(cmd.argv[i]);
1067 if ((i = cmd.find_arg_with_value("status")) >= 0) {
1068 status = str_to_int64(cmd.argv[i]);
1077 /* Quickly unbash all attributes after a sscanf */
1078 void unbash_spaces() {
1079 ::unbash_spaces(Volume);
1080 ::unbash_spaces(Device);
1081 ::unbash_spaces(path);
1082 ::unbash_spaces(Name);
1083 ::unbash_spaces(CreateDate);
1084 ::unbash_spaces(errmsg);
1087 void bash_spaces() {
1088 ::bash_spaces(Volume);
1089 ::bash_spaces(Device);
1090 ::bash_spaces(path);
1091 ::bash_spaces(Name);
1092 ::bash_spaces(CreateDate);
1093 ::bash_spaces(errmsg);
1096 /* Quicky make sure we have enough space to handle the request */
1097 void check_buffer_size(int len) {
1098 Volume = check_pool_memory_size(Volume, len);
1099 Device = check_pool_memory_size(Device, len);
1100 path = check_pool_memory_size(path, len);
1103 /* Create Catalog entry for the current snapshot */
1104 int create_catalog_entry() {
1108 jcr->dir_bsock->fsend(CreateSnap,
1109 jcr->Job, Name, Volume,
1110 Device, CreateTDate, Type, edit_uint64(Retention, ed1));
1111 if (jcr->dir_bsock->recv() < 0) {
1112 Mmsg(errmsg, _("Unable to create snapshot record. ERR=%s\n"),
1113 jcr->dir_bsock->bstrerror());
1115 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1116 Mmsg(errmsg, _("Unable to create snapshot record, got %s\n"),
1117 jcr->dir_bsock->msg);
1126 /* Delete Catalog entry of the current snapshot */
1127 int delete_catalog_entry() {
1130 jcr->dir_bsock->fsend(DelSnap, jcr->Job, Name, Device);
1132 if (jcr->dir_bsock->recv() < 0) {
1133 Mmsg(errmsg, _("Unable to delete snapshot record. ERR=%s\n"),
1134 jcr->dir_bsock->bstrerror());
1136 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1137 Mmsg(errmsg, _("Unable to delete snapshot record, got %s\n"),
1138 jcr->dir_bsock->msg);
1148 /* Get Catalog entry of the current snapshot */
1149 int get_catalog_entry() {
1153 if (!*Name || !*Volume) {
1158 jcr->dir_bsock->fsend(GetSnap, jcr->Job, Name, Volume);
1160 if (jcr->dir_bsock->recv() < 0) {
1161 Mmsg(errmsg, _("Unable to get snapshot record. ERR=%s\n"),
1162 jcr->dir_bsock->bstrerror());
1164 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1165 Mmsg(errmsg, _("Unable to get snapshot record, got %s\n"),
1166 jcr->dir_bsock->msg);
1169 if (cmd.parse_cmd(jcr->dir_bsock->msg) != bRC_OK) {
1170 Mmsg(errmsg, _("Unable to parse command output\n"));
1171 scan_arg(&cmd); /* Fill all parameters from director */
1184 /* Should be after snapshot declaration */
1185 void fs_device::destroy() {
1204 bool fs_device::can_do_snapshot() {
1205 if (snap && !supportSnapshotTested) {
1206 if (snap->support_snapshot(this)) {
1207 Dmsg2(DT_SNAPSHOT, "%s suitable for snapshot, type %s\n",
1208 mountpoint, snap->Type);
1209 isSuitableForSnapshot = true;
1212 Dmsg2(DT_SNAPSHOT, "%s is not suitable for snapshot, type %s\n",
1213 mountpoint, snap->Type);
1215 supportSnapshotTested = true;
1217 return isSuitableForSnapshot;
1220 /* Should be after the snapshot declaration */
1221 fs_device *mtab::search(char *file) {
1223 if (lstat(file, &statp) != 0) {
1224 Dmsg1(DT_SNAPSHOT, "%s not found\n", file);
1225 return NULL; /* not found */
1228 fs_device *elt = (fs_device *)entries->search((void *)((intptr_t)(statp.st_dev)),
1231 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not found in our mount list\n",
1232 statp.st_dev, file);
1233 return NULL; /* not found in our list, skip it */
1236 if (!elt->can_do_snapshot()) {
1237 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not snapshotable\n",
1238 statp.st_dev, file);
1241 Dmsg2(DT_SNAPSHOT, "Found device %d for file %s\n", elt->dev, file);
1245 /* Application to quiesce/un-quiesce */
1247 BPIPE *fd; /* Communication channel */
1248 char *name; /* Pointer to the script name */
1249 char cmd[1]; /* Command line */
1252 /* In the application manager, we want to run a set
1253 * of scripts and see if applications are running or
1254 * not on our partitions.
1256 * We can specify application in the fileset, or just
1257 * try all application that are installed.
1260 class app_manager: public SMARTALLOC
1264 char *appdir; /* Where to find application scripts */
1265 alist *applst; /* Application list (script list to call) */
1266 int apptimeout; /* Timeout when trying to quiesce application */
1267 mtab *mount_list; /* snapshot set */
1270 app_manager(JCR *ajcr, mtab *m, char *dir):
1273 applst(New(alist(10, owned_by_alist))),
1281 /* Put in a file the list of all devices that
1282 * are in the snapshot set
1284 bool dump_snapshotset() {
1288 /* Scan application */
1294 struct app *elt = NULL;
1296 if (!appdir || !*appdir || stat(appdir, &sp) == -1) {
1297 Dmsg0(DT_SNAPSHOT, "app not configured\n");
1301 /* Create a file with the list of all devices that are suitable
1306 results = get_pool_memory(PM_FNAME);
1307 if (run_program_full_output((char*)APPMANAGER_CMD, apptimeout, results) != 0) {
1309 Dmsg2(DT_SNAPSHOT, "app scan error results=%s ERR=%s\n", results, be.bstrerror());
1316 /* Put each line of the output in our list */
1317 for (start = results; start && *start;) {
1318 end = strchr(start, '\n');
1321 elt = (struct app *) malloc(sizeof(struct app) + strlen(start) + 1);
1324 strcpy(elt->cmd, start);
1325 elt->name = (char *)last_path_separator(elt->cmd);
1330 elt->name = elt->cmd;
1333 applst->append(elt);
1334 Dmsg2(10, "+ %s (%s)\n", elt->name, elt->cmd);
1341 free_pool_memory(results);
1346 if (applst->size() == 0) {
1350 Jmsg(jcr, M_INFO, 0, _("Un-Quiescing applications\n"));
1354 /* Quiesce applications */
1358 if (applst->size() == 0) {
1362 Jmsg(jcr, M_INFO, 0, _("Quiescing applications\n"));
1364 for (int i = 0 ; i < applst->size() ; i++) {
1365 struct app *elt = (struct app *) applst->get(i);
1366 elt->fd = open_bpipe(elt->cmd, 0, "rw");
1368 /* Unable to execute the program */
1371 /* Send some commands here */
1377 snapshot_manager::snapshot_manager(JCR *ajcr):
1378 jcr(ajcr), mount_list(New(mtab())) {
1381 snapshot_manager::~snapshot_manager() {
1385 bool snapshot_manager::cleanup_snapshots()
1388 foreach_rblist(elt, mount_list->entries) {
1389 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1390 snapshot *s = elt->snap;
1395 /* TODO: Display an error? Can check mounted status */
1397 /* When no retention is set, we delete the snapshot
1398 * just after the backup
1400 if (s->Retention == 0) {
1402 Jmsg(jcr, M_INFO, 0, _(" Delete Snapshot for %s\n"), elt->mountpoint);
1405 Jmsg(jcr, M_ERROR, 0, _(" Unable to delete snapshot of %s ERR=%s\n"),
1406 elt->mountpoint, s->errmsg);
1414 bool snapshot_manager::create_snapshots()
1416 /* No snapshot, no quiescing */
1417 if (mount_list->empty()) {
1418 Dmsg0(DT_SNAPSHOT, "The mount list is empty, no snapshot to take\n");
1422 /* First thing to do is to quiesce application */
1423 app_manager apps(jcr, mount_list, (char *)APP_DIR);
1425 /* TODO: Let see if we really need to abort
1426 * the snapshot part if application
1432 if (!apps.quiesce()) {
1437 foreach_rblist(elt, mount_list->entries) {
1438 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1439 snapshot *s = elt->snap;
1441 Jmsg(jcr, M_INFO, 0, _(" Create Snapshot for %s\n"), elt->mountpoint);
1443 if (s->Retention > 0) {/* Create snapshot catalog entry if we need to keep them */
1444 s->create_catalog_entry();
1447 } else if (s->status == 2) { /* Use Error message */
1448 elt->isSuitableForSnapshot = false; /* Disable snapshot for this device */
1449 Jmsg(jcr, M_ERROR, 0, _(" Unable to create snapshot of %s ERR=%s\n"),
1450 elt->mountpoint, s->errmsg);
1452 } else { /* By default, an error in the creation should be fatal */
1453 Jmsg(jcr, M_FATAL, 0, _(" Unable to create snapshot of %s ERR=%s\n"),
1454 elt->mountpoint, s->errmsg);
1460 Dmsg3(DT_SNAPSHOT|20, "No Snapshot for %s suitable=%d inset=%d\n",
1461 elt->mountpoint, elt->isSuitableForSnapshot, elt->inSnapshotSet);
1465 /* Snapshots are ok, we need to release applications */
1466 if (!apps.unquiesce()) {
1470 /* Save the include context */
1472 findINCEXE *old = get_incexe(jcr);
1473 findINCEXE *exclude = NULL;
1474 foreach_rblist(elt, mount_list->entries) {
1475 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1476 snapshot *s = elt->snap;
1479 Jmsg(jcr, M_ERROR, 0, " Unable to mount snapshot %s ERR=%s\n",
1480 elt->mountpoint, s->errmsg);
1482 } else if (*s->SnapDirectory) {
1484 exclude = new_exclude(jcr);
1485 /* Set the Exclude context */
1486 set_incexe(jcr, exclude);
1488 Mmsg(t, "%s/.snapshots", elt->snap->SnapMountPoint);
1489 Dmsg1(DT_SNAPSHOT|10, "Excluding %s\n", t.c_str());
1490 add_file_to_fileset(jcr, t.c_str(), true);
1495 /* Restore the current context */
1497 set_incexe(jcr, old);
1502 /* TODO: We might want to use some filters here */
1503 bool snapshot_manager::list_snapshots(alist *lst)
1507 /* No device, no snapshot */
1508 if (mount_list->dCount == 0) {
1509 Dmsg0(DT_SNAPSHOT, "mount list is empty, no snapshot\n");
1513 foreach_rblist(elt, mount_list->entries) {
1514 if (elt->can_do_snapshot()) {
1515 elt->snap->list(lst);
1521 void snapshot_manager::add_mount_point(uint32_t dev, const char *device,
1522 const char *mountpoint, const char *fstype)
1525 alist list(10, not_owned_by_alist);
1526 fs_device *vol = New(fs_device(dev, device, mountpoint, fstype));
1528 /* These FS are not supposed to use snapshot */
1529 const char *specialmp[] = {
1534 /* These FS are not supposed to use snapshot */
1535 const char *specialfs[] = {
1561 /* We skip directly /proc, /sys */
1562 for (int i=0; specialmp[i] ; i++) {
1563 if (strncasecmp(specialmp[i], mountpoint, strlen(specialmp[i])) == 0) {
1570 for (int i=0; specialfs[i] ; i++) {
1571 if (strcasecmp(specialfs[i], fstype) == 0) {
1579 snapshot *snap = New(snapshot(jcr));
1580 snap->set_name(jcr->Job);
1583 if (snap->scan_subvolumes(vol, &list)) {
1584 for (int i = list.size() - 1 ; i >= 0 ; i--) {
1585 fs_device *d = (fs_device *)list.remove(i);
1586 add_mount_point(d->dev, d->device, d->mountpoint, d->fstype);
1592 Dmsg4(DT_SNAPSHOT|20, "Adding %s dev=%d snap?=%d to the mount list (%d)\n",
1593 mountpoint, dev, check, mount_list->dCount);
1595 if (!mount_list->add_entry(vol)) {
1596 Dmsg1(DT_SNAPSHOT, "%s Already exists in mount list\n", vol->mountpoint);
1601 /* In this handler, we need to fill a mtab structure */
1602 static void add_handler(void *user_ctx,
1605 const char *mountpoint,
1606 const char *mntopts,
1609 Dmsg5(DT_SNAPSHOT|50, "dev=%ld device=%s mountpoint=%s fstype=%s mntopts=%s\n",
1610 st->st_dev, device, mountpoint, fstype, mntopts);
1612 /* TODO: If the fstype is btrfs or zfs, the fs might contains subvolumes,
1613 * and these subvolumes may not be reported in the mntent list. In this
1614 * case, we need to call proper btrfs/zfs commands to list them.
1615 * # btrfs subvolume list /mnt
1616 * ID 256 gen 17 top level 5 path test
1617 * ID 259 gen 18 top level 5 path test/titi
1619 snapshot_manager *snapmgr = (snapshot_manager *)user_ctx;
1620 snapmgr->add_mount_point(st->st_dev, device, mountpoint, fstype);
1623 bool snapshot_manager::scan_mtab()
1625 return read_mtab(add_handler, this);
1629 /* Scan the fileset to select partitions to snapshot */
1630 bool snapshot_manager::scan_fileset()
1632 if (!jcr->ff || !jcr->ff->fileset) {
1633 Dmsg0(DT_SNAPSHOT, "No fileset associated with JCR\n");
1637 findFILESET *fileset = jcr->ff->fileset;
1641 for (int i=0; i<fileset->include_list.size(); i++) {
1644 findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
1646 /* look through all files */
1647 foreach_dlist(node, &incexe->name_list) {
1648 char *fname = node->c_str();
1649 if (mount_list->add_in_snapshot_set(fname, &incexe->name_list, node)) {
1650 /* When all volumes are selected, we can stop */
1651 Dmsg0(DT_SNAPSHOT, "All Volumes are marked, stopping the loop here\n");
1656 foreach_alist(fo, &incexe->opts_list) {
1657 flags |= fo->flags; /* We are looking for FO_MULTIFS and recurse */
1662 /* If we allow recursion and multifs, we need to include sub volumes by hand
1663 * in the backup list
1665 if (flags & FO_MULTIFS) {
1666 fs_device *elt, *elt2;
1668 foreach_rblist(elt, mount_list->entries) {
1669 if (!elt->inFileSet) {
1672 alist *lst = New(alist(10, not_owned_by_alist));
1673 mount_list->get_subvolumes(elt->dev, lst, jcr->ff);
1674 foreach_alist(elt2, lst) {
1675 if (elt2->inFileSet) {
1679 /* TODO: See how to avoid having two entries for the same directory */
1680 /* Add the directory explicitely in the fileset */
1681 elt->include->insert_after(new_dlistString(elt2->mountpoint), elt->node);
1683 if (mount_list->add_in_snapshot_set(elt2, elt->include, elt->node)) {
1684 /* When all volumes are selected, we can stop */
1685 Dmsg0(DT_SNAPSHOT, "All Volumes are marked, stopping the loop here\n");
1696 int snapshot_cmd(JCR *jcr)
1698 BSOCK *dir = jcr->dir_bsock;
1702 snap.check_buffer_size(dir->msglen);
1704 n = sscanf(dir->msg, QueryCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type);
1706 snap.unbash_spaces();
1707 Dmsg0(DT_SNAPSHOT|10, "Doing query of a snapshot\n");
1709 bash_spaces(snap.errmsg);
1710 dir->fsend("%d Snapshot status=%d size=%lld ERR=%s\n", n?2000:2999, snap.status, snap.size, snap.errmsg);
1714 n = sscanf(dir->msg, LsCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type, snap.path);
1716 snap.unbash_spaces();
1717 Dmsg0(DT_SNAPSHOT|10, "Doing ls of a snapshot\n");
1719 dir->signal(BNET_EOD);
1723 n = sscanf(dir->msg, DelCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type);
1725 Dmsg0(DT_SNAPSHOT|10, "Doing del of a snapshot\n");
1726 snap.unbash_spaces();
1728 bash_spaces(snap.errmsg);
1729 dir->fsend("%d Snapshot deleted ERR=%s\n", n?2000:2999, snap.errmsg);
1733 n = sscanf(dir->msg, PruneCmd, snap.Volume, snap.Type);
1735 snap.unbash_spaces();
1736 n = snap.prune(dir);
1737 bash_spaces(snap.errmsg);
1738 dir->fsend("%d Snapshot pruned ERR=%s\n", n?2000:2999, snap.errmsg);
1739 Dmsg0(DT_SNAPSHOT|10, "Doing pruning of snapshots\n");
1743 n = sscanf(dir->msg, SyncCmd, snap.Volume, snap.Type);
1745 snap.unbash_spaces();
1747 bash_spaces(snap.errmsg);
1748 dir->fsend("%d Snapshot synced ERR=%s\n", n?2000:2999, snap.errmsg);
1749 Dmsg0(DT_SNAPSHOT|10, "Doing sync of snapshots\n");
1753 /* TODO: Include a path name or a device name */
1754 if (strncmp(dir->msg, ListCmd, strlen(ListCmd)) == 0) {
1757 alist *lst = New(alist(10, not_owned_by_alist));
1758 list_all_snapshots(jcr, lst);
1759 foreach_alist(elt, lst) {
1761 dir->fsend("volume=\"%s\" createtdate=\"%s\" name=\"%s\" device=\"%s\" status=%d error=\"%s\" type=\"%s\"\n",
1762 elt->Volume, edit_uint64(elt->CreateTDate, ed1),
1763 elt->Name, elt->Device, elt->status, elt->errmsg, elt->Type);
1767 dir->signal(BNET_EOD);
1771 n = sscanf(dir->msg, ConfCmd, ed1);
1773 jcr->snapshot_retention = str_to_uint64(ed1);
1774 dir->fsend("2000 Snapshot retention\n");
1778 dir->fsend("2999 Snapshot command not found\n");
1779 dir->signal(BNET_EOD);
1786 bool snapshot_convert_path(JCR *jcr, FF_PKT *ff, dlist *filelist, dlistString *node)
1788 Dmsg1(DT_SNAPSHOT, "snapshot_convert_path(%s)\n", ff->top_fname);
1789 snapshot_manager *snapmgr = jcr->snap_mgr;
1790 ff->strip_snap_path = false;
1796 fs_device *elt = snapmgr->mount_list->search(ff->top_fname);
1798 return true; /* not found */
1801 if (!ff->snap_fname) {
1802 ff->snap_fname = get_pool_memory(PM_FNAME);
1805 /* Convert the filename to the original path */
1806 if (!elt->snap->convert_path(ff)) {
1807 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not snapshotable\n",
1808 elt->dev, ff->top_fname);
1814 /* ListSnap[] = "CatReq Job=%s list_snapshot name=%s volume=%s device=%s tdate=%d type=%s before=%s after=%s expired=%d"; */
1816 /* List Catalog entry of the current client */
1817 int snapshot_list_catalog(JCR *jcr,
1824 if (cmd.parse_cmd(query) != bRC_OK) {
1825 Dmsg1(DT_SNAPSHOT, "Unable to decode query %s\n", query);
1828 Mmsg(q, "CatReq Job=%s list_snapshot name=", jcr->Job);
1829 if ((i = cmd.find_arg_with_value("name")) >= 0) {
1830 bash_spaces(cmd.argv[i]);
1831 pm_strcat(q, cmd.argv[i]);
1834 pm_strcat(q, " volume=");
1835 if ((i = cmd.find_arg_with_value("volume")) >= 0) {
1836 bash_spaces(cmd.argv[i]);
1837 pm_strcat(q, cmd.argv[i]);
1840 pm_strcat(q, " device=");
1841 if ((i = cmd.find_arg_with_value("device")) >= 0) {
1842 bash_spaces(cmd.argv[i]);
1843 pm_strcat(q, cmd.argv[i]);
1846 pm_strcat(q, " tdate=");
1847 if ((i = cmd.find_arg_with_value("tdate")) >= 0) {
1848 bash_spaces(cmd.argv[i]);
1849 pm_strcat(q, cmd.argv[i]);
1852 pm_strcat(q, " type=");
1853 if ((i = cmd.find_arg_with_value("type")) >= 0) {
1854 bash_spaces(cmd.argv[i]);
1855 pm_strcat(q, cmd.argv[i]);
1858 pm_strcat(q, " before=");
1859 if ((i = cmd.find_arg_with_value("before")) >= 0) {
1860 bash_spaces(cmd.argv[i]);
1861 pm_strcat(q, cmd.argv[i]);
1864 pm_strcat(q, " after=");
1865 if ((i = cmd.find_arg_with_value("after")) >= 0) {
1866 bash_spaces(cmd.argv[i]);
1867 pm_strcat(q, cmd.argv[i]);
1870 pm_strcat(q, " expired=");
1871 if ((i = cmd.find_arg_with_value("expired")) >= 0) {
1872 bash_spaces(cmd.argv[i]);
1873 pm_strcat(q, cmd.argv[i]);
1876 jcr->dir_bsock->fsend("%s\n", q.c_str());
1878 while (jcr->dir_bsock->recv() > 0) {
1879 if (cmd.parse_cmd(jcr->dir_bsock->msg) != bRC_OK) {
1880 Dmsg1(DT_SNAPSHOT, "Unable to decode director output %s\n", jcr->dir_bsock->msg);
1884 snapshot *s = New(snapshot(jcr));