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
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;
275 /* Special cmd_parser subclass to not look after plugin
276 * names when decoding the line
278 class arg_parser: public cmd_parser
281 arg_parser(): cmd_parser() { use_name = false; };
282 virtual ~arg_parser() {};
287 /* Device that exists on the system */
288 class fs_device: public SMARTALLOC
293 uint32_t dev; /* dev no */
294 char *mountpoint; /* where it's mounted */
295 char *fstype; /* ntfs, ext3, ext4... */
296 char *device; /* /dev/mapper/xxx */
298 bool supportSnapshotTested; /* True if support() was called */
299 bool isSuitableForSnapshot; /* Compatible with snapshots */
302 snapshot *snap; /* Associated snapshot */
304 dlist *include; /* Where the fs_device was found in the fileset */
305 void *node; /* At which node */
309 dev(0), mountpoint(NULL), fstype(NULL), device(NULL),
310 supportSnapshotTested(false), isSuitableForSnapshot(false), snap(NULL)
314 fs_device(uint32_t adev, const char *adevice, const char *amountpoint, const char *aftype) {
316 fstype = bstrdup(aftype);
317 device = bstrdup(adevice);
318 mountpoint = bstrdup(amountpoint);
319 supportSnapshotTested = false;
320 isSuitableForSnapshot = false;
321 inSnapshotSet = false;
332 /* Call support() and cache the result in supportSnapshotTested and isSuitableForSnapshot */
333 bool can_do_snapshot();
335 void setInFileSet(dlist *inc, void *where) {
336 include = inc; /* where we need to include subvolumes */
337 node = where; /* after which node */
339 inSnapshotSet = true;
342 void set_snap(snapshot *s) {
349 /* The device list is stored in a rblist, using the
350 * dev no as key. The devno can be found in every stat()
353 static int compare_entries(void *item1, void *item2)
355 fs_device *dev1 = (fs_device *) item1;
356 fs_device *dev2 = (fs_device *) item2;
357 if (dev1->dev > dev2->dev) {
360 } else if (dev1->dev < dev2->dev) {
368 static int search_entry(void *item1, void *item2)
370 uint32_t dev1 = (intptr_t) item1;
371 fs_device* dev2 = (fs_device *) item2;
372 if (dev1 > dev2->dev) {
375 } else if (dev1 < dev2->dev) {
383 /* List of all fd_device that are on the system
384 * Some devices are excluded automatically from
385 * the list, such as proc, sysfs, etc...
387 class mtab: public SMARTALLOC
391 int sCount; /* Snapshot count */
392 int dCount; /* Device count */
394 fs_device *elt = NULL;
395 entries = New(rblist(elt, &elt->link));
401 foreach_rblist(elt, entries) {
407 /* Have we devices for snapshot in our list ? */
412 /* Get a fs_device corresponding to a file */
413 fs_device *search(char *file);
415 /* Get subvolumes for a specific device */
416 bool get_subvolumes(uint32_t dev, alist *items, FF_PKT *ff) {
417 fs_device *elt, *elt2;
418 elt = (fs_device *)entries->search((void*)(intptr_t)dev, search_entry);
423 foreach_rblist(elt2, entries) {
424 if (elt2->dev == elt->dev) {
427 if (strncmp(elt2->mountpoint, elt->mountpoint, strlen(elt->mountpoint)) == 0) {
428 /* the mount point is included in the volume */
430 if (file_is_excluded(ff, elt2->mountpoint)) {
431 Dmsg1(DT_SNAPSHOT|50, "Looks to be excluded %s\n", elt2->mountpoint);
438 return items->size() > 0;
441 bool add_in_snapshot_set(char *file, dlist *inc, void *node) {
443 fs_device *elt = search(file);
445 Dmsg1(DT_SNAPSHOT, "%s will not be added to snapshot set\n", file);
446 return sCount == dCount; /* not found in our list, skip it */
448 return add_in_snapshot_set(elt, inc, node);
451 bool add_in_snapshot_set(fs_device *elt, dlist *inc, void *node) {
452 Dmsg4(DT_SNAPSHOT|10, "%s in=%d can=%d tested=%d\n", elt->mountpoint, elt->inSnapshotSet,
453 elt->isSuitableForSnapshot, elt->supportSnapshotTested);
454 if (!elt->inSnapshotSet && elt->can_do_snapshot()) {
455 Dmsg1(DT_SNAPSHOT, "Marking %s for snapshot\n", elt->mountpoint);
456 elt->setInFileSet(inc, node);
459 /* It will help to count when all devices are in the snapshot set */
460 Dmsg2(DT_SNAPSHOT|10, "sCount %d = dCount %d\n", sCount, dCount);
461 return sCount == dCount;
464 bool add_entry(fs_device *vol) {
465 fs_device *ret = (fs_device *) entries->insert(vol, compare_entries);
466 if (ret == vol && vol->snap) {
467 dCount++; /* We skip directly FS such as /proc, /sys or /dev */
473 /* Snapshot descriptor, used to communicate with the snapshot
474 * backend on the system.
476 class snapshot: public SMARTALLOC
482 const char *action; /* current action */
483 char Name[MAX_NAME_LENGTH]; /* Name of the snapshot */
484 char Type[MAX_NAME_LENGTH]; /* lvm, btrfs, netapp */
485 char FSType[MAX_NAME_LENGTH]; /* btrfs, zfs, ext3 */
486 char CreateDate[MAX_TIME_LENGTH]; /* Creation date */
487 time_t CreateTDate; /* Creation date in seconds */
488 int64_t size; /* Size of the snapshot */
489 int status; /* snapshot status */
490 utime_t Retention; /* Snapshot retention, might come from Pool/FileSet */
492 POOLMEM *Volume; /* Path of the volume */
493 POOLMEM *Device; /* Device path */
494 POOLMEM *MountPoint; /* Device Mount point */
495 POOLMEM *SnapMountPoint; /* Snapshot Mount point */
496 POOLMEM *path; /* path used in ls query */
497 POOLMEM *errmsg; /* Error message generated by commands */
498 POOLMEM *SnapDirectory; /* Where snapshots are stored */
500 char **env; /* Variables used to call snapshot */
501 bool mounted; /* True if mounted on SnapMountPoint */
502 bool created; /* True if the snapshot is created */
504 snapshot(JCR *ajcr) {
507 path = get_pool_memory(PM_FNAME);
508 errmsg = get_pool_memory(PM_MESSAGE);
509 Volume = get_pool_memory(PM_FNAME);
510 Device = get_pool_memory(PM_FNAME);
511 MountPoint = get_pool_memory(PM_FNAME);
512 SnapMountPoint = get_pool_memory(PM_FNAME);
513 SnapDirectory = get_pool_memory(PM_FNAME);
518 free_pool_memory(path);
519 free_pool_memory(errmsg);
520 free_pool_memory(Volume);
521 free_pool_memory(Device);
522 free_pool_memory(MountPoint);
523 free_pool_memory(SnapMountPoint);
524 free_pool_memory(SnapDirectory);
529 *SnapDirectory = *Type = *FSType = *SnapMountPoint = 0;
530 *MountPoint = *Volume = *Device = *path = *errmsg = 0;
536 Retention = jcr->snapshot_retention;
539 /* Free the env[] structure */
542 for (int i=0; env[i] ; i++) {
550 void set_device(const char *d) {
551 pm_strcpy(Device, d);
554 void set_mountpoint(const char *d) {
555 pm_strcpy(MountPoint, d);
558 void set_name(const char *n) {
559 bstrncpy(Name, n, sizeof(Name));
562 void set_fstype(const char *n) {
563 bstrncpy(FSType, n, sizeof(FSType));
566 void set_action(const char *a) {
570 /* Convert a real top path to a snapshot path
571 * and set proper variables inside ff_pkt
572 * to translate back all subfiles.
574 bool convert_path(FF_PKT *ff) {
575 if (!*MountPoint || !*SnapMountPoint) {
576 Dmsg2(DT_SNAPSHOT, "MountPoint=%s SnapMountPoint=%s\n",
577 NPRT(MountPoint), NPRT(SnapMountPoint));
580 if (!ff->snap_top_fname) {
581 ff->snap_top_fname = get_pool_memory(PM_FNAME);
583 ff->volume_path = MountPoint; /* /tmp */
584 ff->snapshot_path = SnapMountPoint; /* /tmp/.snapshot/Job.20140502.01.01.01 */
585 ff->top_fname_save = ff->top_fname;
587 int mp_first = strlen(MountPoint); /* will point to after MountPoint in top_fname */
588 int last = pm_strcpy(ff->snap_top_fname, SnapMountPoint);
589 last = MAX(last - 1, 0);
591 /* We need to concat path and avoid double / and no / */
592 if (ff->snap_top_fname[last] == '/') {
593 if (ff->top_fname[mp_first] == '/') {
594 ff->snap_top_fname[last] = 0; /* strip double / */
596 } else { /* no / at all */
597 if (ff->top_fname[mp_first] != '/') {
598 pm_strcat(ff->snap_top_fname, "/");
602 pm_strcat(ff->snap_top_fname, ff->top_fname + mp_first);
603 ff->top_fname = ff->snap_top_fname;
604 ff->strip_snap_path = true;
605 Dmsg1(DT_SNAPSHOT|50, "top_fname=%s\n", ff->top_fname);
609 /* Create a environment used in the child process */
610 int edit_snapshot_env() {
612 POOLMEM *tmp = get_pool_memory(PM_FNAME);
615 /* Update "10" to add more variables */
616 env = (char **) malloc(sizeof(char *) * 10);
619 Mmsg(tmp, "SNAPSHOT_NAME=%s", Name);
620 env[i++] = bstrdup(tmp);
623 Mmsg(tmp, "SNAPSHOT_VOLUME=%s", Volume);
624 env[i++] = bstrdup(tmp);
627 Mmsg(tmp, "SNAPSHOT_DEVICE=%s", Device);
628 env[i++] = bstrdup(tmp);
631 Mmsg(tmp, "SNAPSHOT_TYPE=%s", Type);
632 env[i++] = bstrdup(tmp);
635 Mmsg(tmp, "SNAPSHOT_FSTYPE=%s", FSType);
636 env[i++] = bstrdup(tmp);
639 Mmsg(tmp, "SNAPSHOT_MOUNTPOINT=%s", MountPoint);
640 env[i++] = bstrdup(tmp);
642 if (*SnapDirectory) {
643 Mmsg(tmp, "SNAPSHOT_SNAPDIRECTORY=%s", SnapDirectory);
644 env[i++] = bstrdup(tmp);
646 if (*SnapMountPoint) {
647 Mmsg(tmp, "SNAPSHOT_SNAPMOUNTPOINT=%s", SnapMountPoint);
648 env[i++] = bstrdup(tmp);
650 /* When adding new entries, do not forget to add more slots to env[] */
652 Mmsg(tmp, "SNAPSHOT_ACTION=%s", action);
653 env[i++] = bstrdup(tmp);
655 env[i] = NULL; /* last record */
657 if (chk_dbglvl(DT_SNAPSHOT|100)) {
658 for (i = 0; env[i] ; i++) {
659 Dmsg1(0, "%s\n", env[i]);
663 free_pool_memory(tmp);
667 /* Edit the command line if needed */
668 int edit_snapshot_codes(POOLMEM **omsg, const char *imsg) {
674 for (p=imsg; *p; p++) {
681 str = NPRTB(exepath);
708 str = SnapMountPoint;
722 pm_strcat(omsg, str);
725 if (chk_dbglvl(DT_SNAPSHOT|10)) {
727 Mmsg(tmp, " -d %d -o /tmp/bsnapshot.log ", debug_level);
728 pm_strcat(omsg, tmp.c_str());
731 Dmsg2(DT_SNAPSHOT|30, "edit_snapshot_codes: %s -> %s\n", imsg, *omsg);
735 /* Call the snapshot backend to know if we can snapshot the current FS */
736 int support_snapshot(fs_device *vol) {
741 set_device(vol->device);
742 set_mountpoint(vol->mountpoint);
743 set_fstype(vol->fstype);
745 if (!do_command("support", &cmd)) {
751 Dmsg2(DT_SNAPSHOT|50, "%s snapshot support status=%d\n", vol->mountpoint, status);
755 /* Scan sub volumes for a particular volume */
756 int scan_subvolumes(fs_device *vol, alist *lst) {
760 char *mp=NULL, *fstype=NULL, *device=Device;
763 set_device(vol->device);
764 set_mountpoint(vol->mountpoint);
765 set_fstype(vol->fstype);
767 if (!do_command("subvolumes", &cmd)) {
771 for (int i = 0; i < cmd.argc ; i++) {
772 if (strcasecmp(cmd.argk[i], "Dev") == 0 && cmd.argv[i]) {
773 dev = str_to_int64(cmd.argv[i]);
775 } else if (strcasecmp(cmd.argk[i], "MountPoint") == 0 && cmd.argv[i]) {
778 } else if (strcasecmp(cmd.argk[i], "Device") == 0 && cmd.argv[i]) {
779 device = cmd.argv[i];
781 } else if (strcasecmp(cmd.argk[i], "FSType") == 0 && cmd.argv[i]) {
782 fstype = cmd.argv[i];
783 if (mp && fstype && dev) {
784 fs_device *elt = New(fs_device(dev, device, mp, fstype));
786 /* reset variables */
799 /* Prune current snapshots
800 * - List snapshots available on the director, keep a list locally
801 * - get mtab, list snapshots for all devices, or devices that are in the director list
803 int prune(BSOCK *bs) {
807 /* List local snapshots, list director snapshots, and synchronize the two */
808 int sync(BSOCK *bs) {
812 /* List files from a snapshot
813 * Need to set the Volume and the Path
819 /* Scan common arguments */
820 int scan_arg(arg_parser *cmd) {
821 for (int i = 0; i < cmd->argc ; i++) {
822 if (strcasecmp(cmd->argk[i], "Volume") == 0 && cmd->argv[i]) {
823 pm_strcpy(Volume, cmd->argv[i]);
825 } else if (strcasecmp(cmd->argk[i], "CreateDate") == 0 && cmd->argv[i]) {
826 bstrncpy(CreateDate, cmd->argv[i], sizeof(CreateDate));
827 CreateTDate = str_to_utime(CreateDate);
829 } else if (strcasecmp(cmd->argk[i], "CreateTDate") == 0 && cmd->argv[i]) {
830 CreateTDate = str_to_int64(cmd->argv[i]);
831 bstrftimes(CreateDate, sizeof(CreateDate), CreateTDate);
833 } else if (strcasecmp(cmd->argk[i], "Type") == 0 && cmd->argv[i]) {
834 bstrncpy(Type, cmd->argv[i], sizeof(Type));
836 } else if (strcasecmp(cmd->argk[i], "SnapMountPoint") == 0 && cmd->argv[i]) {
837 pm_strcpy(SnapMountPoint, cmd->argv[i]);
839 } else if (strcasecmp(cmd->argk[i], "SnapDirectory") == 0 && cmd->argv[i]) {
840 pm_strcpy(SnapDirectory, cmd->argv[i]);
842 } else if (strcasecmp(cmd->argk[i], "status") == 0 && cmd->argv[i]) {
843 status = str_to_int64(cmd->argv[i]);
845 } else if (strcasecmp(cmd->argk[i], "Device") == 0 && cmd->argv[i]) {
846 pm_strcpy(Device, cmd->argv[i]);
852 /* Create a snapshot with already given attributes
853 * Need to set Name and Device at the minimum
859 if (!*Name || !*Device) {
863 Dmsg2(DT_SNAPSHOT, "Create Snapshot of %s %s\n", Device, Name);
865 /* TODO: see if we handle multiple snapshots per call */
866 if (!do_command("create", &cmd)) {
883 if (!*Name || !*Volume || !*Device || !*Type) {
887 Dmsg1(DT_SNAPSHOT, "Doing mount of %s\n", Volume);
889 if (!do_command("mount", &cmd)) {
897 mounted = (status > 0 && *SnapMountPoint);
907 if (!*Name || !*SnapMountPoint || !*Type || !mounted) {
911 Dmsg1(DT_SNAPSHOT, "Doing unmount of a %s\n", SnapMountPoint);
913 if (!do_command("unmount", &cmd)) {
925 /* Delete a snapshot with the given device name */
929 if (!*Name || !*Volume || !created) {
932 ret = do_command("delete", &cmd);
937 /* TODO: Need to read stdout as well */
938 int do_command(const char *act, arg_parser *cmd) {
940 POOLMEM *command = get_pool_memory(PM_FNAME);
941 POOLMEM *out = get_pool_memory(PM_FNAME);
944 edit_snapshot_codes(&command, me->snapshot_command);
947 Dmsg1(DT_SNAPSHOT|20, "Execute %s command\n", act);
950 Mmsg(errmsg, _("Error while creating command string %s.\n"), act);
951 Dmsg1(DT_SNAPSHOT, "%s", errmsg);
955 /* If the exit code is 1, we can expect to have a clear error
956 * message in the output
958 if ((rcode = run_program_full_output(command, 600, out, env)) != 0) {
959 if ((rcode & ~b_errno_exit) == 1) { /* exit 1, look the output */
960 if (cmd->parse_cmd(out) == bRC_OK) {
961 int i = cmd->find_arg_with_value("status");
963 /* If we have a status, take it */
964 status = str_to_int64(cmd->argv[i]);
968 i = cmd->find_arg_with_value("error");
970 pm_strcpy(errmsg, cmd->argv[i]);
971 Dmsg1(DT_SNAPSHOT|20, "%s\n", errmsg);
977 Mmsg(errmsg, _("Error while executing \"%s\" %s. %s %s\n"),
978 act, command, out, be.bstrerror());
979 Dmsg1(DT_SNAPSHOT, "%s", errmsg);
983 /* Need to decode the output of the script
984 * TODO: some commands will have multiple lines
986 if (cmd->parse_cmd(out) != bRC_OK) {
987 Dmsg2(DT_SNAPSHOT, "snapshot command %s output error: [%s]\n", act, out);
988 Mmsg(errmsg, _("Unable to parse snapshot command output\n"));
991 Dmsg1(DT_SNAPSHOT|50, "ret = %s\n", out);
994 free_pool_memory(command);
995 free_pool_memory(out);
999 int list(alist *lst) {
1000 Dmsg0(DT_SNAPSHOT, "Doing list of a snapshots of a given device\n");
1003 int i, ret=0, status=1;
1004 char *volume=NULL, *name=NULL, *device=NULL, *createdate=NULL, *error=NULL;
1005 utime_t createtdate = 0;
1007 /* TODO: Here we need to loop over a list */
1008 if (!do_command("list", &cmd)) {
1014 * volume=xxx device=zzzz name=yyy createtdate=12121212 size=xx status=xx error=xx type=lvm
1016 for (i = 0; i < cmd.argc ; i++) {
1017 if (strcasecmp(cmd.argk[i], "Volume") == 0) {
1018 volume = cmd.argv[i];
1020 } else if (strcasecmp(cmd.argk[i], "Name") == 0) {
1023 } else if (strcasecmp(cmd.argk[i], "Device") == 0) {
1024 device = cmd.argv[i];
1026 } else if (strcasecmp(cmd.argk[i], "Error") == 0) {
1027 error = cmd.argv[i];
1029 } else if (strcasecmp(cmd.argk[i], "Status") == 0) {
1030 status = str_to_int64(cmd.argv[i]);
1032 } else if (strcasecmp(cmd.argk[i], "Type") == 0) {
1033 snap = New(snapshot(jcr));
1034 pm_strcpy(snap->Volume, volume);
1035 pm_strcpy(snap->Device, NPRTB(device));
1036 bstrncpy(snap->Name, NPRTB(name), sizeof(snap->Name));
1037 bstrncpy(snap->Type, cmd.argv[i], sizeof(snap->Type));
1038 bstrncpy(snap->CreateDate, createdate, sizeof(snap->CreateDate));
1039 pm_strcpy(snap->errmsg, NPRTB(error));
1040 snap->status = status;
1041 snap->CreateTDate = createtdate;
1042 error = createdate = device = name = volume = NULL;
1047 } else if (strcasecmp(cmd.argk[i], "CreateTDate") == 0) {
1048 createtdate = str_to_int64(cmd.argv[i]);
1050 } else if (strcasecmp(cmd.argk[i], "CreateDate") == 0) {
1051 createdate = cmd.argv[i];
1052 createtdate = str_to_utime(cmd.argv[i]);
1059 /* Query information about snapshot */
1061 Dmsg0(0, "Doing query of a snapshot\n");
1069 if (!do_command("query", &cmd)) {
1073 if ((i = cmd.find_arg_with_value("size")) >= 0) {
1074 size = str_to_int64(cmd.argv[i]);
1077 if ((i = cmd.find_arg_with_value("status")) >= 0) {
1078 status = str_to_int64(cmd.argv[i]);
1087 /* Quickly unbash all attributes after a sscanf */
1088 void unbash_spaces() {
1089 ::unbash_spaces(Volume);
1090 ::unbash_spaces(Device);
1091 ::unbash_spaces(path);
1092 ::unbash_spaces(Name);
1093 ::unbash_spaces(CreateDate);
1094 ::unbash_spaces(errmsg);
1097 void bash_spaces() {
1098 ::bash_spaces(Volume);
1099 ::bash_spaces(Device);
1100 ::bash_spaces(path);
1101 ::bash_spaces(Name);
1102 ::bash_spaces(CreateDate);
1103 ::bash_spaces(errmsg);
1106 /* Quicky make sure we have enough space to handle the request */
1107 void check_buffer_size(int len) {
1108 Volume = check_pool_memory_size(Volume, len);
1109 Device = check_pool_memory_size(Device, len);
1110 path = check_pool_memory_size(path, len);
1113 /* Create Catalog entry for the current snapshot */
1114 int create_catalog_entry() {
1118 jcr->dir_bsock->fsend(CreateSnap,
1119 jcr->Job, Name, Volume,
1120 Device, CreateTDate, Type, edit_uint64(Retention, ed1));
1121 if (jcr->dir_bsock->recv() < 0) {
1122 Mmsg(errmsg, _("Unable to create snapshot record. ERR=%s\n"),
1123 jcr->dir_bsock->bstrerror());
1125 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1126 Mmsg(errmsg, _("Unable to create snapshot record, got %s\n"),
1127 jcr->dir_bsock->msg);
1136 /* Delete Catalog entry of the current snapshot */
1137 int delete_catalog_entry() {
1140 jcr->dir_bsock->fsend(DelSnap, jcr->Job, Name, Device);
1142 if (jcr->dir_bsock->recv() < 0) {
1143 Mmsg(errmsg, _("Unable to delete snapshot record. ERR=%s\n"),
1144 jcr->dir_bsock->bstrerror());
1146 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1147 Mmsg(errmsg, _("Unable to delete snapshot record, got %s\n"),
1148 jcr->dir_bsock->msg);
1158 /* Get Catalog entry of the current snapshot */
1159 int get_catalog_entry() {
1163 if (!*Name || !*Volume) {
1168 jcr->dir_bsock->fsend(GetSnap, jcr->Job, Name, Volume);
1170 if (jcr->dir_bsock->recv() < 0) {
1171 Mmsg(errmsg, _("Unable to get snapshot record. ERR=%s\n"),
1172 jcr->dir_bsock->bstrerror());
1174 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1175 Mmsg(errmsg, _("Unable to get snapshot record, got %s\n"),
1176 jcr->dir_bsock->msg);
1179 if (cmd.parse_cmd(jcr->dir_bsock->msg) != bRC_OK) {
1180 Mmsg(errmsg, _("Unable to parse command output\n"));
1181 scan_arg(&cmd); /* Fill all parameters from director */
1194 /* Should be after snapshot declaration */
1195 void fs_device::destroy() {
1214 bool fs_device::can_do_snapshot() {
1215 if (snap && !supportSnapshotTested) {
1216 if (snap->support_snapshot(this)) {
1217 Dmsg2(DT_SNAPSHOT, "%s suitable for snapshot, type %s\n",
1218 mountpoint, snap->Type);
1219 isSuitableForSnapshot = true;
1222 Dmsg2(DT_SNAPSHOT, "%s is not suitable for snapshot, type %s\n",
1223 mountpoint, snap->Type);
1225 supportSnapshotTested = true;
1227 return isSuitableForSnapshot;
1230 /* Should be after the snapshot declaration */
1231 fs_device *mtab::search(char *file) {
1233 if (lstat(file, &statp) != 0) {
1234 Dmsg1(DT_SNAPSHOT, "%s not found\n", file);
1235 return NULL; /* not found */
1238 fs_device *elt = (fs_device *)entries->search((void *)((intptr_t)(statp.st_dev)),
1241 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not found in our mount list\n",
1242 statp.st_dev, file);
1243 return NULL; /* not found in our list, skip it */
1246 if (!elt->can_do_snapshot()) {
1247 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not snapshotable\n",
1248 statp.st_dev, file);
1251 Dmsg2(DT_SNAPSHOT, "Found device %d for file %s\n", elt->dev, file);
1255 /* Application to quiesce/un-quiesce */
1257 BPIPE *fd; /* Communication channel */
1258 char *name; /* Pointer to the script name */
1259 char cmd[1]; /* Command line */
1262 /* In the application manager, we want to run a set
1263 * of scripts and see if applications are running or
1264 * not on our partitions.
1266 * We can specify application in the fileset, or just
1267 * try all application that are installed.
1270 class app_manager: public SMARTALLOC
1274 char *appdir; /* Where to find application scripts */
1275 alist *applst; /* Application list (script list to call) */
1276 int apptimeout; /* Timeout when trying to quiesce application */
1277 mtab *mount_list; /* snapshot set */
1280 app_manager(JCR *ajcr, mtab *m, char *dir):
1283 applst(New(alist(10, owned_by_alist))),
1291 /* Put in a file the list of all devices that
1292 * are in the snapshot set
1294 bool dump_snapshotset() {
1298 /* Scan application */
1304 struct app *elt = NULL;
1306 if (!appdir || !*appdir || stat(appdir, &sp) == -1) {
1307 Dmsg0(DT_SNAPSHOT, "app not configured\n");
1311 /* Create a file with the list of all devices that are suitable
1316 results = get_pool_memory(PM_FNAME);
1317 if (run_program_full_output((char*)APPMANAGER_CMD, apptimeout, results) != 0) {
1319 Dmsg2(DT_SNAPSHOT, "app scan error results=%s ERR=%s\n", results, be.bstrerror());
1326 /* Put each line of the output in our list */
1327 for (start = results; start && *start;) {
1328 end = strchr(start, '\n');
1331 elt = (struct app *) malloc(sizeof(struct app) + strlen(start) + 1);
1334 strcpy(elt->cmd, start);
1335 elt->name = (char *)last_path_separator(elt->cmd);
1340 elt->name = elt->cmd;
1343 applst->append(elt);
1344 Dmsg2(10, "+ %s (%s)\n", elt->name, elt->cmd);
1351 free_pool_memory(results);
1356 if (applst->size() == 0) {
1360 Jmsg(jcr, M_INFO, 0, _("Un-Quiescing applications\n"));
1364 /* Quiesce applications */
1368 if (applst->size() == 0) {
1372 Jmsg(jcr, M_INFO, 0, _("Quiescing applications\n"));
1374 for (int i = 0 ; i < applst->size() ; i++) {
1375 struct app *elt = (struct app *) applst->get(i);
1376 elt->fd = open_bpipe(elt->cmd, 0, "rw");
1378 /* Unable to execute the program */
1381 /* Send some commands here */
1387 snapshot_manager::snapshot_manager(JCR *ajcr):
1388 jcr(ajcr), mount_list(New(mtab())) {
1391 snapshot_manager::~snapshot_manager() {
1395 bool snapshot_manager::cleanup_snapshots()
1398 foreach_rblist(elt, mount_list->entries) {
1399 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1400 snapshot *s = elt->snap;
1405 /* TODO: Display an error? Can check mounted status */
1407 /* When no retention is set, we delete the snapshot
1408 * just after the backup
1410 if (s->Retention == 0) {
1412 Jmsg(jcr, M_INFO, 0, _(" Delete Snapshot for %s\n"), elt->mountpoint);
1415 Jmsg(jcr, M_ERROR, 0, _(" Unable to delete snapshot of %s ERR=%s\n"),
1416 elt->mountpoint, s->errmsg);
1424 bool snapshot_manager::create_snapshots()
1426 /* No snapshot, no quiescing */
1427 if (mount_list->empty()) {
1428 Dmsg0(DT_SNAPSHOT, "The mount list is empty, no snapshot to take\n");
1432 /* First thing to do is to quiesce application */
1433 app_manager apps(jcr, mount_list, (char *)APP_DIR);
1435 /* TODO: Let see if we really need to abort
1436 * the snapshot part if application
1442 if (!apps.quiesce()) {
1447 foreach_rblist(elt, mount_list->entries) {
1448 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1449 snapshot *s = elt->snap;
1451 Jmsg(jcr, M_INFO, 0, _(" Create Snapshot for %s\n"), elt->mountpoint);
1453 if (s->Retention > 0) {/* Create snapshot catalog entry if we need to keep them */
1454 s->create_catalog_entry();
1457 } else if (s->status == 2) { /* Use Error message */
1458 elt->isSuitableForSnapshot = false; /* Disable snapshot for this device */
1459 Jmsg(jcr, M_ERROR, 0, _(" Unable to create snapshot of %s ERR=%s\n"),
1460 elt->mountpoint, s->errmsg);
1462 } else { /* By default, an error in the creation should be fatal */
1463 Jmsg(jcr, M_FATAL, 0, _(" Unable to create snapshot of %s ERR=%s\n"),
1464 elt->mountpoint, s->errmsg);
1470 Dmsg3(DT_SNAPSHOT|20, "No Snapshot for %s suitable=%d inset=%d\n",
1471 elt->mountpoint, elt->isSuitableForSnapshot, elt->inSnapshotSet);
1475 /* Snapshots are ok, we need to release applications */
1476 if (!apps.unquiesce()) {
1480 /* Save the include context */
1482 findINCEXE *old = get_incexe(jcr);
1483 findINCEXE *exclude = NULL;
1484 foreach_rblist(elt, mount_list->entries) {
1485 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1486 snapshot *s = elt->snap;
1489 Jmsg(jcr, M_ERROR, 0, " Unable to mount snapshot %s ERR=%s\n",
1490 elt->mountpoint, s->errmsg);
1492 } else if (*s->SnapDirectory) {
1494 exclude = new_exclude(jcr);
1495 /* Set the Exclude context */
1496 set_incexe(jcr, exclude);
1498 Mmsg(t, "%s/.snapshots", elt->snap->SnapMountPoint);
1499 Dmsg1(DT_SNAPSHOT|10, "Excluding %s\n", t.c_str());
1500 add_file_to_fileset(jcr, t.c_str(), true);
1505 /* Restore the current context */
1507 set_incexe(jcr, old);
1512 /* TODO: We might want to use some filters here */
1513 bool snapshot_manager::list_snapshots(alist *lst)
1517 /* No device, no snapshot */
1518 if (mount_list->dCount == 0) {
1519 Dmsg0(DT_SNAPSHOT, "mount list is empty, no snapshot\n");
1523 foreach_rblist(elt, mount_list->entries) {
1524 if (elt->can_do_snapshot()) {
1525 elt->snap->list(lst);
1531 void snapshot_manager::add_mount_point(uint32_t dev, const char *device,
1532 const char *mountpoint, const char *fstype)
1535 alist list(10, not_owned_by_alist);
1536 fs_device *vol = New(fs_device(dev, device, mountpoint, fstype));
1538 /* These FS are not supposed to use snapshot */
1539 const char *specialmp[] = {
1544 /* These FS are not supposed to use snapshot */
1545 const char *specialfs[] = {
1571 /* We skip directly /proc, /sys */
1572 for (int i=0; specialmp[i] ; i++) {
1573 if (strncasecmp(specialmp[i], mountpoint, strlen(specialmp[i])) == 0) {
1580 for (int i=0; specialfs[i] ; i++) {
1581 if (strcasecmp(specialfs[i], fstype) == 0) {
1589 snapshot *snap = New(snapshot(jcr));
1590 snap->set_name(jcr->Job);
1593 if (snap->scan_subvolumes(vol, &list)) {
1594 for (int i = list.size() - 1 ; i >= 0 ; i--) {
1595 fs_device *d = (fs_device *)list.remove(i);
1596 add_mount_point(d->dev, d->device, d->mountpoint, d->fstype);
1602 Dmsg4(DT_SNAPSHOT|20, "Adding %s dev=%d snap?=%d to the mount list (%d)\n",
1603 mountpoint, dev, check, mount_list->dCount);
1605 if (!mount_list->add_entry(vol)) {
1606 Dmsg1(DT_SNAPSHOT, "%s Already exists in mount list\n", vol->mountpoint);
1611 /* In this handler, we need to fill a mtab structure */
1612 static void add_handler(void *user_ctx,
1615 const char *mountpoint,
1616 const char *mntopts,
1619 Dmsg5(DT_SNAPSHOT|50, "dev=%ld device=%s mountpoint=%s fstype=%s mntopts=%s\n",
1620 st->st_dev, device, mountpoint, fstype, mntopts);
1622 /* TODO: If the fstype is btrfs or zfs, the fs might contains subvolumes,
1623 * and these subvolumes may not be reported in the mntent list. In this
1624 * case, we need to call proper btrfs/zfs commands to list them.
1625 * # btrfs subvolume list /mnt
1626 * ID 256 gen 17 top level 5 path test
1627 * ID 259 gen 18 top level 5 path test/titi
1629 snapshot_manager *snapmgr = (snapshot_manager *)user_ctx;
1630 snapmgr->add_mount_point(st->st_dev, device, mountpoint, fstype);
1633 bool snapshot_manager::scan_mtab()
1635 return read_mtab(add_handler, this);
1639 /* Scan the fileset to select partitions to snapshot */
1640 bool snapshot_manager::scan_fileset()
1642 if (!jcr->ff || !jcr->ff->fileset) {
1643 Dmsg0(DT_SNAPSHOT, "No fileset associated with JCR\n");
1647 findFILESET *fileset = jcr->ff->fileset;
1651 for (int i=0; i<fileset->include_list.size(); i++) {
1654 findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
1656 /* look through all files */
1657 foreach_dlist(node, &incexe->name_list) {
1658 char *fname = node->c_str();
1659 if (mount_list->add_in_snapshot_set(fname, &incexe->name_list, node)) {
1660 /* When all volumes are selected, we can stop */
1661 Dmsg0(DT_SNAPSHOT, "All Volumes are marked, stopping the loop here\n");
1666 foreach_alist(fo, &incexe->opts_list) {
1667 flags |= fo->flags; /* We are looking for FO_MULTIFS and recurse */
1672 /* If we allow recursion and multifs, we need to include sub volumes by hand
1673 * in the backup list
1675 if (flags & FO_MULTIFS) {
1676 fs_device *elt, *elt2;
1678 foreach_rblist(elt, mount_list->entries) {
1679 if (!elt->inFileSet) {
1682 alist *lst = New(alist(10, not_owned_by_alist));
1683 mount_list->get_subvolumes(elt->dev, lst, jcr->ff);
1684 foreach_alist(elt2, lst) {
1685 if (elt2->inFileSet) {
1689 /* TODO: See how to avoid having two entries for the same directory */
1690 /* Add the directory explicitely in the fileset */
1691 elt->include->insert_after(new_dlistString(elt2->mountpoint), elt->node);
1693 if (mount_list->add_in_snapshot_set(elt2, elt->include, elt->node)) {
1694 /* When all volumes are selected, we can stop */
1695 Dmsg0(DT_SNAPSHOT, "All Volumes are marked, stopping the loop here\n");
1706 int snapshot_cmd(JCR *jcr)
1708 BSOCK *dir = jcr->dir_bsock;
1712 snap.check_buffer_size(dir->msglen);
1714 n = sscanf(dir->msg, QueryCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type);
1716 snap.unbash_spaces();
1717 Dmsg0(DT_SNAPSHOT|10, "Doing query of a snapshot\n");
1719 bash_spaces(snap.errmsg);
1720 dir->fsend("%d Snapshot status=%d size=%lld ERR=%s\n", n?2000:2999, snap.status, snap.size, snap.errmsg);
1724 n = sscanf(dir->msg, LsCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type, snap.path);
1726 snap.unbash_spaces();
1727 Dmsg0(DT_SNAPSHOT|10, "Doing ls of a snapshot\n");
1729 dir->signal(BNET_EOD);
1733 n = sscanf(dir->msg, DelCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type);
1735 Dmsg0(DT_SNAPSHOT|10, "Doing del of a snapshot\n");
1736 snap.unbash_spaces();
1738 bash_spaces(snap.errmsg);
1739 dir->fsend("%d Snapshot deleted ERR=%s\n", n?2000:2999, snap.errmsg);
1743 n = sscanf(dir->msg, PruneCmd, snap.Volume, snap.Type);
1745 snap.unbash_spaces();
1746 n = snap.prune(dir);
1747 bash_spaces(snap.errmsg);
1748 dir->fsend("%d Snapshot pruned ERR=%s\n", n?2000:2999, snap.errmsg);
1749 Dmsg0(DT_SNAPSHOT|10, "Doing pruning of snapshots\n");
1753 n = sscanf(dir->msg, SyncCmd, snap.Volume, snap.Type);
1755 snap.unbash_spaces();
1757 bash_spaces(snap.errmsg);
1758 dir->fsend("%d Snapshot synced ERR=%s\n", n?2000:2999, snap.errmsg);
1759 Dmsg0(DT_SNAPSHOT|10, "Doing sync of snapshots\n");
1763 /* TODO: Include a path name or a device name */
1764 if (strncmp(dir->msg, ListCmd, strlen(ListCmd)) == 0) {
1767 alist *lst = New(alist(10, not_owned_by_alist));
1768 list_all_snapshots(jcr, lst);
1769 foreach_alist(elt, lst) {
1771 dir->fsend("volume=\"%s\" createtdate=\"%s\" name=\"%s\" device=\"%s\" status=%d error=\"%s\" type=\"%s\"\n",
1772 elt->Volume, edit_uint64(elt->CreateTDate, ed1),
1773 elt->Name, elt->Device, elt->status, elt->errmsg, elt->Type);
1777 dir->signal(BNET_EOD);
1781 n = sscanf(dir->msg, ConfCmd, ed1);
1783 jcr->snapshot_retention = str_to_uint64(ed1);
1784 dir->fsend("2000 Snapshot retention\n");
1788 dir->fsend("2999 Snapshot command not found\n");
1789 dir->signal(BNET_EOD);
1796 bool snapshot_convert_path(JCR *jcr, FF_PKT *ff, dlist *filelist, dlistString *node)
1798 Dmsg1(DT_SNAPSHOT, "snapshot_convert_path(%s)\n", ff->top_fname);
1799 snapshot_manager *snapmgr = jcr->snap_mgr;
1800 ff->strip_snap_path = false;
1806 fs_device *elt = snapmgr->mount_list->search(ff->top_fname);
1808 return true; /* not found */
1811 if (!ff->snap_fname) {
1812 ff->snap_fname = get_pool_memory(PM_FNAME);
1815 /* Convert the filename to the original path */
1816 if (!elt->snap->convert_path(ff)) {
1817 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not snapshotable\n",
1818 elt->dev, ff->top_fname);
1824 /* ListSnap[] = "CatReq Job=%s list_snapshot name=%s volume=%s device=%s tdate=%d type=%s before=%s after=%s expired=%d"; */
1826 /* List Catalog entry of the current client */
1827 int snapshot_list_catalog(JCR *jcr,
1834 if (cmd.parse_cmd(query) != bRC_OK) {
1835 Dmsg1(DT_SNAPSHOT, "Unable to decode query %s\n", query);
1838 Mmsg(q, "CatReq Job=%s list_snapshot name=", jcr->Job);
1839 if ((i = cmd.find_arg_with_value("name")) >= 0) {
1840 bash_spaces(cmd.argv[i]);
1841 pm_strcat(q, cmd.argv[i]);
1844 pm_strcat(q, " volume=");
1845 if ((i = cmd.find_arg_with_value("volume")) >= 0) {
1846 bash_spaces(cmd.argv[i]);
1847 pm_strcat(q, cmd.argv[i]);
1850 pm_strcat(q, " device=");
1851 if ((i = cmd.find_arg_with_value("device")) >= 0) {
1852 bash_spaces(cmd.argv[i]);
1853 pm_strcat(q, cmd.argv[i]);
1856 pm_strcat(q, " tdate=");
1857 if ((i = cmd.find_arg_with_value("tdate")) >= 0) {
1858 bash_spaces(cmd.argv[i]);
1859 pm_strcat(q, cmd.argv[i]);
1862 pm_strcat(q, " type=");
1863 if ((i = cmd.find_arg_with_value("type")) >= 0) {
1864 bash_spaces(cmd.argv[i]);
1865 pm_strcat(q, cmd.argv[i]);
1868 pm_strcat(q, " before=");
1869 if ((i = cmd.find_arg_with_value("before")) >= 0) {
1870 bash_spaces(cmd.argv[i]);
1871 pm_strcat(q, cmd.argv[i]);
1874 pm_strcat(q, " after=");
1875 if ((i = cmd.find_arg_with_value("after")) >= 0) {
1876 bash_spaces(cmd.argv[i]);
1877 pm_strcat(q, cmd.argv[i]);
1880 pm_strcat(q, " expired=");
1881 if ((i = cmd.find_arg_with_value("expired")) >= 0) {
1882 bash_spaces(cmd.argv[i]);
1883 pm_strcat(q, cmd.argv[i]);
1886 jcr->dir_bsock->fsend("%s\n", q.c_str());
1888 while (jcr->dir_bsock->recv() > 0) {
1889 if (cmd.parse_cmd(jcr->dir_bsock->msg) != bRC_OK) {
1890 Dmsg1(DT_SNAPSHOT, "Unable to decode director output %s\n", jcr->dir_bsock->msg);
1894 snapshot *s = New(snapshot(jcr));