2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2000-2014 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
20 Written by Eric Bollengier, 2015
24 Documentation about snapshot backend
25 ----------------------------------------------------------------
27 The snapshot manager is using environment variables to communicate.
33 create, delete, list, mount, unmount, check, support, subvolume
37 ex: /dev/vgroot/home_Test-2014-01-01_00_00 (lvm)
38 /home/.snapshots/Test-2014-01-01_00_00 (btrfs)
39 /.zfs/snapshot/Test-2014-01-01_00_00 (zfs)
41 The volume name is generated by the create command
45 ex: /dev/vgroot/home (lvm)
49 The device name can be found via getmntent()
52 Snapshot name, usually the Job name
55 Device filesystem type, can be found via getmntent()
59 Snapshot backend type, generated by support command
63 Device mount point, found via getmntent()
65 SNAPSHOT_SNAPMOUNTPOINT
66 Snapshot mount point is generated by the mount command
70 ----------------------------------------------------------------
72 OK: exit code 0 and status=1 in the output
73 ERR: exit code <> 0 and/or status=0 in the output
75 status=1 keyword="value" keyword2="value2"
77 status=0 error="Error message"
81 ----------------------------------------------------------------
84 get SNAPSHOT_DEVICE, SNAPSHOT_FSTYPE, SNAPSHOT_MOUNTPOINT
86 volume="" name="" device="" createtime=""
88 2) Test if a filesystem supports snapshot feature
89 SNAPSHOT_ACTION=support
91 SNAPSHOT_MOUNTPOINT=/home
94 => status=1 type=btrfs device=/home
97 2a) Test if a filesystem contains subvolumes
98 SNAPSHOT_ACTION=subvolumes
100 SNAPSHOT_FSTYPE=btrfs
102 => dev=10 mountpoint=/home/subvol fstype=btrfs
105 SNAPSHOT_ACTION=create
106 SNAPSHOT_NAME=Test-2014-01-01_00_00
107 SNAPSHOT_DEVICE=/home
108 SNAPSHOT_MOUNTPOINT=/home
109 SNAPSHOT_FSTYPE=btrfs
112 => status=1 volume="/home/.snapshots/Test-2014-01-01_00_00" createtdate=1418935776 type=btrfs
114 4) Mount the snapshot
115 SNAPSHOT_ACTION=mount
116 SNAPSHOT_NAME=Test-2014-01-01_00_00
117 SNAPSHOT_DEVICE=/home
118 SNAPSHOT_MOUNTPOINT=/home
119 SNAPSHOT_FSTYPE=btrfs
122 => status=1 volume="/home/.snapshots/Test-2014-01-01_00_00" createtdate=1418935776 type=btrfs
125 5) Unmount the snapshot
126 SNAPSHOT_ACTION=unmount
127 SNAPSHOT_SNAPMOUNTPOINT=/home/.snapshots/Test-2014-01-01_00_00
131 6) Delete the snapshot
132 SNAPSHOT_ACTION=delete
133 SNAPSHOT_VOLUME=/home/.snapshot/Test-2014-01-01_00_00
141 #define USE_CMD_PARSER
142 #include "plugins/fd/fd_common.h"
144 #include "fd_snapshot.h"
145 #define APPMANAGER_CMD "%eappmanager"
146 #define APP_DIR "/tmp/app.d"
147 #define SNAPSHOT_CMD "%ebsnapshot"
149 /* Defined in messages.c */
150 extern char *exepath;
152 /* Catalog interface with the director */
153 static char CreateSnap[] = "CatReq Job=%s new_snapshot name=%s volume=%s device=%s tdate=%d type=%s retention=%s";
154 static char DelSnap[] = "CatReq Job=%s del_snapshot name=%s device=%s";
155 static char GetSnap[] = "CatReq Job=%s get_snapshot name=%s volume=%s";
157 /* Command line interface with the director */
158 static char LsCmd[] = "snapshot ls name=%127s volume=%s device=%s tdate=%d type=%127s path=%s";
159 static char DelCmd[] = "snapshot del name=%127s volume=%s device=%s tdate=%d type=%127s";
160 static char QueryCmd[] = "snapshot query name=%127s volume=%s device=%s tdate=%d type=%127s";
161 static char PruneCmd[] = "snapshot prune volume=%s type=%127s";
162 static char SyncCmd[] = "snapshot sync volume=%s type=%127%";
163 static char ListCmd[] = "snapshot list";
164 static char ConfCmd[] = "snapshot retention=%50s";
166 /* Small function to quickly tell us if we can do snapshot here */
167 static bool is_snapshot_supported(JCR *jcr)
171 POOLMEM *cmd = get_pool_memory(PM_FNAME);
176 /* We are not really interested by arguments, just
180 for (p=me->snapshot_command; *p; p++) {
187 str = NPRTB(exepath);
196 } else if (*p == ' ') {
207 ret = stat(cmd, &sp) == 0;
208 free_pool_memory(cmd);
209 Dmsg1(10, "Snapshot = %d\n", ret);
213 /* Return the default snapshot handler, must be freed at the end */
214 char *snapshot_get_command()
216 return bstrdup(SNAPSHOT_CMD);
219 /* Initialize the snapshot manager at the begining of the
220 * job and create snapshots
222 bool open_snapshot_backup_session(JCR *jcr)
224 if (!is_snapshot_supported(jcr)) {
225 Dmsg0(DT_SNAPSHOT, "Snapshot not supported\n");
229 jcr->snap_mgr = New(snapshot_manager(jcr));
230 /* Get all volumes and subvolumes */
231 if (!jcr->snap_mgr->scan_mtab()) {
232 berrno be; /* error probably in errno */
233 Dmsg1(DT_SNAPSHOT, "Unable to scan mtab. ERR=%s\n", be.bstrerror());
234 Jmsg(jcr, M_ERROR, 0, "Unable to scan mtab to determine devices to snapshot\n");
237 /* Match the volume list with the fileset */
238 if (!jcr->snap_mgr->scan_fileset()) {
239 Jmsg(jcr, M_ERROR,0, "Unable to scan fileset to determine devices to snapshot\n");
242 /* Create fileset needed */
243 if (!jcr->snap_mgr->create_snapshots()) {
244 /* Error message already displayed if needed */
247 return true; /* We should have some snapshots */
250 /* Command that can be called from outside */
251 bool list_all_snapshots(JCR *jcr, alist *lst)
253 snapshot_manager snap_mgr(jcr);
255 /* Get all volumes and subvolumes */
256 if (!snap_mgr.scan_mtab()) {
260 if (snap_mgr.list_snapshots(lst)) {
266 /* Cleanup the snapshot manager at the end of the job */
267 void close_snapshot_backup_session(JCR *jcr)
270 jcr->snap_mgr->cleanup_snapshots();
271 delete jcr->snap_mgr;
272 jcr->snap_mgr = NULL;
276 /* Special cmd_parser subclass to not look after plugin
277 * names when decoding the line
279 class arg_parser: public cmd_parser
282 arg_parser(): cmd_parser() { use_name = false; };
283 virtual ~arg_parser() {};
288 /* Device that exists on the system */
289 class fs_device: public SMARTALLOC
294 uint32_t dev; /* dev no */
295 char *mountpoint; /* where it's mounted */
296 char *fstype; /* ntfs, ext3, ext4... */
297 char *device; /* /dev/mapper/xxx */
299 bool supportSnapshotTested; /* True if support() was called */
300 bool isSuitableForSnapshot; /* Compatible with snapshots */
303 snapshot *snap; /* Associated snapshot */
305 dlist *include; /* Where the fs_device was found in the fileset */
306 void *node; /* At which node */
310 dev(0), mountpoint(NULL), fstype(NULL), device(NULL),
311 supportSnapshotTested(false), isSuitableForSnapshot(false), snap(NULL)
315 fs_device(uint32_t adev, const char *adevice, const char *amountpoint, const char *aftype) {
317 fstype = bstrdup(aftype);
318 device = bstrdup(adevice);
319 mountpoint = bstrdup(amountpoint);
320 supportSnapshotTested = false;
321 isSuitableForSnapshot = false;
322 inSnapshotSet = false;
333 /* Call support() and cache the result in supportSnapshotTested and isSuitableForSnapshot */
334 bool can_do_snapshot();
336 void setInFileSet(dlist *inc, void *where) {
337 include = inc; /* where we need to include subvolumes */
338 node = where; /* after which node */
340 inSnapshotSet = true;
343 void set_snap(snapshot *s) {
350 /* The device list is stored in a rblist, using the
351 * dev no as key. The devno can be found in every stat()
354 static int compare_entries(void *item1, void *item2)
356 fs_device *dev1 = (fs_device *) item1;
357 fs_device *dev2 = (fs_device *) item2;
358 if (dev1->dev > dev2->dev) {
361 } else if (dev1->dev < dev2->dev) {
369 static int search_entry(void *item1, void *item2)
371 uint32_t dev1 = (intptr_t) item1;
372 fs_device* dev2 = (fs_device *) item2;
373 if (dev1 > dev2->dev) {
376 } else if (dev1 < dev2->dev) {
384 /* List of all fd_device that are on the system
385 * Some devices are excluded automatically from
386 * the list, such as proc, sysfs, etc...
388 class mtab: public SMARTALLOC
392 int sCount; /* Snapshot count */
393 int dCount; /* Device count */
395 fs_device *elt = NULL;
396 entries = New(rblist(elt, &elt->link));
402 foreach_rblist(elt, entries) {
408 /* Have we devices for snapshot in our list ? */
413 /* Get a fs_device corresponding to a file */
414 fs_device *search(char *file);
416 /* Get subvolumes for a specific device */
417 bool get_subvolumes(uint32_t dev, alist *items, FF_PKT *ff) {
418 fs_device *elt, *elt2;
419 elt = (fs_device *)entries->search((void*)(intptr_t)dev, search_entry);
424 foreach_rblist(elt2, entries) {
425 if (elt2->dev == elt->dev) {
428 if (strncmp(elt2->mountpoint, elt->mountpoint, strlen(elt->mountpoint)) == 0) {
429 /* the mount point is included in the volume */
431 if (file_is_excluded(ff, elt2->mountpoint)) {
432 Dmsg1(DT_SNAPSHOT|50, "Looks to be excluded %s\n", elt2->mountpoint);
439 return items->size() > 0;
442 bool add_in_snapshot_set(char *file, dlist *inc, void *node) {
444 fs_device *elt = search(file);
446 Dmsg1(DT_SNAPSHOT, "%s will not be added to snapshot set\n", file);
447 return sCount == dCount; /* not found in our list, skip it */
449 return add_in_snapshot_set(elt, inc, node);
452 bool add_in_snapshot_set(fs_device *elt, dlist *inc, void *node) {
453 Dmsg4(DT_SNAPSHOT|10, "%s in=%d can=%d tested=%d\n", elt->mountpoint, elt->inSnapshotSet,
454 elt->isSuitableForSnapshot, elt->supportSnapshotTested);
455 if (!elt->inSnapshotSet && elt->can_do_snapshot()) {
456 Dmsg1(DT_SNAPSHOT, "Marking %s for snapshot\n", elt->mountpoint);
457 elt->setInFileSet(inc, node);
460 /* It will help to count when all devices are in the snapshot set */
461 Dmsg2(DT_SNAPSHOT|10, "sCount %d = dCount %d\n", sCount, dCount);
462 return sCount == dCount;
465 bool add_entry(fs_device *vol) {
466 fs_device *ret = (fs_device *) entries->insert(vol, compare_entries);
467 if (ret == vol && vol->snap) {
468 dCount++; /* We skip directly FS such as /proc, /sys or /dev */
474 /* Snapshot descriptor, used to communicate with the snapshot
475 * backend on the system.
477 class snapshot: public SMARTALLOC
483 const char *action; /* current action */
484 char Name[MAX_NAME_LENGTH]; /* Name of the snapshot */
485 char Type[MAX_NAME_LENGTH]; /* lvm, btrfs, netapp */
486 char FSType[MAX_NAME_LENGTH]; /* btrfs, zfs, ext3 */
487 char CreateDate[MAX_TIME_LENGTH]; /* Creation date */
488 time_t CreateTDate; /* Creation date in seconds */
489 int64_t size; /* Size of the snapshot */
490 int status; /* snapshot status */
491 utime_t Retention; /* Snapshot retention, might come from Pool/FileSet */
493 POOLMEM *Volume; /* Path of the volume */
494 POOLMEM *Device; /* Device path */
495 POOLMEM *MountPoint; /* Device Mount point */
496 POOLMEM *SnapMountPoint; /* Snapshot Mount point */
497 POOLMEM *path; /* path used in ls query */
498 POOLMEM *errmsg; /* Error message generated by commands */
499 POOLMEM *SnapDirectory; /* Where snapshots are stored */
501 char **env; /* Variables used to call snapshot */
502 bool mounted; /* True if mounted on SnapMountPoint */
503 bool created; /* True if the snapshot is created */
505 snapshot(JCR *ajcr) {
508 path = get_pool_memory(PM_FNAME);
509 errmsg = get_pool_memory(PM_MESSAGE);
510 Volume = get_pool_memory(PM_FNAME);
511 Device = get_pool_memory(PM_FNAME);
512 MountPoint = get_pool_memory(PM_FNAME);
513 SnapMountPoint = get_pool_memory(PM_FNAME);
514 SnapDirectory = get_pool_memory(PM_FNAME);
519 free_pool_memory(path);
520 free_pool_memory(errmsg);
521 free_pool_memory(Volume);
522 free_pool_memory(Device);
523 free_pool_memory(MountPoint);
524 free_pool_memory(SnapMountPoint);
525 free_pool_memory(SnapDirectory);
530 *SnapDirectory = *Type = *FSType = *SnapMountPoint = 0;
531 *MountPoint = *Volume = *Device = *path = *errmsg = 0;
537 Retention = jcr->snapshot_retention;
540 /* Free the env[] structure */
543 for (int i=0; env[i] ; i++) {
551 void set_device(const char *d) {
552 pm_strcpy(Device, d);
555 void set_mountpoint(const char *d) {
556 pm_strcpy(MountPoint, d);
559 void set_name(const char *n) {
560 bstrncpy(Name, n, sizeof(Name));
563 void set_fstype(const char *n) {
564 bstrncpy(FSType, n, sizeof(FSType));
567 void set_action(const char *a) {
571 /* Convert a real top path to a snapshot path
572 * and set proper variables inside ff_pkt
573 * to translate back all subfiles.
575 bool convert_path(FF_PKT *ff) {
576 if (!*MountPoint || !*SnapMountPoint) {
577 Dmsg2(DT_SNAPSHOT, "MountPoint=%s SnapMountPoint=%s\n",
578 NPRT(MountPoint), NPRT(SnapMountPoint));
581 if (!ff->snap_top_fname) {
582 ff->snap_top_fname = get_pool_memory(PM_FNAME);
584 ff->volume_path = MountPoint; /* /tmp */
585 ff->snapshot_path = SnapMountPoint; /* /tmp/.snapshot/Job.20140502.01.01.01 */
586 ff->top_fname_save = ff->top_fname;
588 int mp_first = strlen(MountPoint); /* will point to after MountPoint in top_fname */
589 int last = pm_strcpy(ff->snap_top_fname, SnapMountPoint);
590 last = MAX(last - 1, 0);
592 /* We need to concat path and avoid double / and no / */
593 if (ff->snap_top_fname[last] == '/') {
594 if (ff->top_fname[mp_first] == '/') {
595 ff->snap_top_fname[last] = 0; /* strip double / */
597 } else { /* no / at all */
598 if (ff->top_fname[mp_first] != '/') {
599 pm_strcat(ff->snap_top_fname, "/");
603 pm_strcat(ff->snap_top_fname, ff->top_fname + mp_first);
604 ff->top_fname = ff->snap_top_fname;
605 ff->strip_snap_path = true;
606 Dmsg1(DT_SNAPSHOT|50, "top_fname=%s\n", ff->top_fname);
610 /* Create a environment used in the child process */
611 int edit_snapshot_env() {
613 POOLMEM *tmp = get_pool_memory(PM_FNAME);
616 /* Update "10" to add more variables */
617 env = (char **) malloc(sizeof(char *) * 10);
620 Mmsg(tmp, "SNAPSHOT_NAME=%s", Name);
621 env[i++] = bstrdup(tmp);
624 Mmsg(tmp, "SNAPSHOT_VOLUME=%s", Volume);
625 env[i++] = bstrdup(tmp);
628 Mmsg(tmp, "SNAPSHOT_DEVICE=%s", Device);
629 env[i++] = bstrdup(tmp);
632 Mmsg(tmp, "SNAPSHOT_TYPE=%s", Type);
633 env[i++] = bstrdup(tmp);
636 Mmsg(tmp, "SNAPSHOT_FSTYPE=%s", FSType);
637 env[i++] = bstrdup(tmp);
640 Mmsg(tmp, "SNAPSHOT_MOUNTPOINT=%s", MountPoint);
641 env[i++] = bstrdup(tmp);
643 if (*SnapDirectory) {
644 Mmsg(tmp, "SNAPSHOT_SNAPDIRECTORY=%s", SnapDirectory);
645 env[i++] = bstrdup(tmp);
647 if (*SnapMountPoint) {
648 Mmsg(tmp, "SNAPSHOT_SNAPMOUNTPOINT=%s", SnapMountPoint);
649 env[i++] = bstrdup(tmp);
651 /* When adding new entries, do not forget to add more slots to env[] */
653 Mmsg(tmp, "SNAPSHOT_ACTION=%s", action);
654 env[i++] = bstrdup(tmp);
656 env[i] = NULL; /* last record */
658 if (chk_dbglvl(DT_SNAPSHOT|100)) {
659 for (i = 0; env[i] ; i++) {
660 Dmsg1(0, "%s\n", env[i]);
664 free_pool_memory(tmp);
668 /* Edit the command line if needed */
669 int edit_snapshot_codes(POOLMEM **omsg, const char *imsg) {
675 for (p=imsg; *p; p++) {
682 str = NPRTB(exepath);
709 str = SnapMountPoint;
723 pm_strcat(omsg, str);
726 if (chk_dbglvl(DT_SNAPSHOT|10)) {
728 Mmsg(tmp, " -d %d -o /tmp/bsnapshot.log ", debug_level);
729 pm_strcat(omsg, tmp.c_str());
732 Dmsg2(DT_SNAPSHOT|30, "edit_snapshot_codes: %s -> %s\n", imsg, *omsg);
736 /* Call the snapshot backend to know if we can snapshot the current FS */
737 int support_snapshot(fs_device *vol) {
742 set_device(vol->device);
743 set_mountpoint(vol->mountpoint);
744 set_fstype(vol->fstype);
746 if (!do_command("support", &cmd)) {
752 Dmsg2(DT_SNAPSHOT|50, "%s snapshot support status=%d\n", vol->mountpoint, status);
756 /* Scan sub volumes for a particular volume */
757 int scan_subvolumes(fs_device *vol, alist *lst) {
761 char *mp=NULL, *fstype=NULL, *device=Device;
764 set_device(vol->device);
765 set_mountpoint(vol->mountpoint);
766 set_fstype(vol->fstype);
768 if (!do_command("subvolumes", &cmd)) {
772 for (int i = 0; i < cmd.argc ; i++) {
773 if (strcasecmp(cmd.argk[i], "Dev") == 0 && cmd.argv[i]) {
774 dev = str_to_int64(cmd.argv[i]);
776 } else if (strcasecmp(cmd.argk[i], "MountPoint") == 0 && cmd.argv[i]) {
779 } else if (strcasecmp(cmd.argk[i], "Device") == 0 && cmd.argv[i]) {
780 device = cmd.argv[i];
782 } else if (strcasecmp(cmd.argk[i], "FSType") == 0 && cmd.argv[i]) {
783 fstype = cmd.argv[i];
784 if (mp && fstype && dev) {
785 fs_device *elt = New(fs_device(dev, device, mp, fstype));
787 /* reset variables */
800 /* Prune current snapshots
801 * - List snapshots available on the director, keep a list locally
802 * - get mtab, list snapshots for all devices, or devices that are in the director list
804 int prune(BSOCK *bs) {
808 /* List local snapshots, list director snapshots, and synchronize the two */
809 int sync(BSOCK *bs) {
813 /* List files from a snapshot
814 * Need to set the Volume and the Path
820 /* Scan common arguments */
821 int scan_arg(arg_parser *cmd) {
822 for (int i = 0; i < cmd->argc ; i++) {
823 if (strcasecmp(cmd->argk[i], "Volume") == 0 && cmd->argv[i]) {
824 pm_strcpy(Volume, cmd->argv[i]);
826 } else if (strcasecmp(cmd->argk[i], "CreateDate") == 0 && cmd->argv[i]) {
827 bstrncpy(CreateDate, cmd->argv[i], sizeof(CreateDate));
828 CreateTDate = str_to_utime(CreateDate);
830 } else if (strcasecmp(cmd->argk[i], "CreateTDate") == 0 && cmd->argv[i]) {
831 CreateTDate = str_to_int64(cmd->argv[i]);
832 bstrftimes(CreateDate, sizeof(CreateDate), CreateTDate);
834 } else if (strcasecmp(cmd->argk[i], "Type") == 0 && cmd->argv[i]) {
835 bstrncpy(Type, cmd->argv[i], sizeof(Type));
837 } else if (strcasecmp(cmd->argk[i], "SnapMountPoint") == 0 && cmd->argv[i]) {
838 pm_strcpy(SnapMountPoint, cmd->argv[i]);
840 } else if (strcasecmp(cmd->argk[i], "SnapDirectory") == 0 && cmd->argv[i]) {
841 pm_strcpy(SnapDirectory, cmd->argv[i]);
843 } else if (strcasecmp(cmd->argk[i], "status") == 0 && cmd->argv[i]) {
844 status = str_to_int64(cmd->argv[i]);
846 } else if (strcasecmp(cmd->argk[i], "Device") == 0 && cmd->argv[i]) {
847 pm_strcpy(Device, cmd->argv[i]);
853 /* Create a snapshot with already given attributes
854 * Need to set Name and Device at the minimum
860 if (!*Name || !*Device) {
864 Dmsg2(DT_SNAPSHOT, "Create Snapshot of %s %s\n", Device, Name);
866 /* TODO: see if we handle multiple snapshots per call */
867 if (!do_command("create", &cmd)) {
884 if (!*Name || !*Volume || !*Device || !*Type) {
888 Dmsg1(DT_SNAPSHOT, "Doing mount of %s\n", Volume);
890 if (!do_command("mount", &cmd)) {
898 mounted = (status > 0 && *SnapMountPoint);
908 if (!*Name || !*SnapMountPoint || !*Type || !mounted) {
912 Dmsg1(DT_SNAPSHOT, "Doing unmount of a %s\n", SnapMountPoint);
914 if (!do_command("unmount", &cmd)) {
926 /* Delete a snapshot with the given device name */
930 if (!*Name || !*Volume || !created) {
933 ret = do_command("delete", &cmd);
938 /* TODO: Need to read stdout as well */
939 int do_command(const char *act, arg_parser *cmd) {
941 POOLMEM *command = get_pool_memory(PM_FNAME);
942 POOLMEM *out = get_pool_memory(PM_FNAME);
945 edit_snapshot_codes(&command, me->snapshot_command);
948 Dmsg1(DT_SNAPSHOT|20, "Execute %s command\n", act);
951 Mmsg(errmsg, _("Error while creating command string %s.\n"), act);
952 Dmsg1(DT_SNAPSHOT, "%s", errmsg);
956 /* If the exit code is 1, we can expect to have a clear error
957 * message in the output
959 if ((rcode = run_program_full_output(command, 600, out, env)) != 0) {
960 if ((rcode & ~b_errno_exit) == 1) { /* exit 1, look the output */
961 if (cmd->parse_cmd(out) == bRC_OK) {
962 int i = cmd->find_arg_with_value("status");
964 /* If we have a status, take it */
965 status = str_to_int64(cmd->argv[i]);
969 i = cmd->find_arg_with_value("error");
971 pm_strcpy(errmsg, cmd->argv[i]);
972 Dmsg1(DT_SNAPSHOT|20, "%s\n", errmsg);
978 Mmsg(errmsg, _("Error while executing \"%s\" %s. %s %s\n"),
979 act, command, out, be.bstrerror());
980 Dmsg1(DT_SNAPSHOT, "%s", errmsg);
984 /* Need to decode the output of the script
985 * TODO: some commands will have multiple lines
987 if (cmd->parse_cmd(out) != bRC_OK) {
988 Dmsg2(DT_SNAPSHOT, "snapshot command %s output error: [%s]\n", act, out);
989 Mmsg(errmsg, _("Unable to parse snapshot command output\n"));
992 Dmsg1(DT_SNAPSHOT|50, "ret = %s\n", out);
995 free_pool_memory(command);
996 free_pool_memory(out);
1000 int list(alist *lst) {
1001 Dmsg0(DT_SNAPSHOT, "Doing list of a snapshots of a given device\n");
1004 int i, ret=0, status=1;
1005 char *volume=NULL, *name=NULL, *device=NULL, *createdate=NULL, *error=NULL;
1006 utime_t createtdate = 0;
1008 /* TODO: Here we need to loop over a list */
1009 if (!do_command("list", &cmd)) {
1015 * volume=xxx device=zzzz name=yyy createtdate=12121212 size=xx status=xx error=xx type=lvm
1017 for (i = 0; i < cmd.argc ; i++) {
1018 if (strcasecmp(cmd.argk[i], "Volume") == 0) {
1019 volume = cmd.argv[i];
1021 } else if (strcasecmp(cmd.argk[i], "Name") == 0) {
1024 } else if (strcasecmp(cmd.argk[i], "Device") == 0) {
1025 device = cmd.argv[i];
1027 } else if (strcasecmp(cmd.argk[i], "Error") == 0) {
1028 error = cmd.argv[i];
1030 } else if (strcasecmp(cmd.argk[i], "Status") == 0) {
1031 status = str_to_int64(cmd.argv[i]);
1033 } else if (strcasecmp(cmd.argk[i], "Type") == 0) {
1034 snap = New(snapshot(jcr));
1035 pm_strcpy(snap->Volume, volume);
1036 pm_strcpy(snap->Device, NPRTB(device));
1037 bstrncpy(snap->Name, NPRTB(name), sizeof(snap->Name));
1038 bstrncpy(snap->Type, cmd.argv[i], sizeof(snap->Type));
1039 bstrncpy(snap->CreateDate, createdate, sizeof(snap->CreateDate));
1040 pm_strcpy(snap->errmsg, NPRTB(error));
1041 snap->status = status;
1042 snap->CreateTDate = createtdate;
1043 error = createdate = device = name = volume = NULL;
1048 } else if (strcasecmp(cmd.argk[i], "CreateTDate") == 0) {
1049 createtdate = str_to_int64(cmd.argv[i]);
1051 } else if (strcasecmp(cmd.argk[i], "CreateDate") == 0) {
1052 createdate = cmd.argv[i];
1053 createtdate = str_to_utime(cmd.argv[i]);
1060 /* Query information about snapshot */
1062 Dmsg0(0, "Doing query of a snapshot\n");
1070 if (!do_command("query", &cmd)) {
1074 if ((i = cmd.find_arg_with_value("size")) >= 0) {
1075 size = str_to_int64(cmd.argv[i]);
1078 if ((i = cmd.find_arg_with_value("status")) >= 0) {
1079 status = str_to_int64(cmd.argv[i]);
1088 /* Quickly unbash all attributes after a sscanf */
1089 void unbash_spaces() {
1090 ::unbash_spaces(Volume);
1091 ::unbash_spaces(Device);
1092 ::unbash_spaces(path);
1093 ::unbash_spaces(Name);
1094 ::unbash_spaces(CreateDate);
1095 ::unbash_spaces(errmsg);
1098 void bash_spaces() {
1099 ::bash_spaces(Volume);
1100 ::bash_spaces(Device);
1101 ::bash_spaces(path);
1102 ::bash_spaces(Name);
1103 ::bash_spaces(CreateDate);
1104 ::bash_spaces(errmsg);
1107 /* Quicky make sure we have enough space to handle the request */
1108 void check_buffer_size(int len) {
1109 Volume = check_pool_memory_size(Volume, len);
1110 Device = check_pool_memory_size(Device, len);
1111 path = check_pool_memory_size(path, len);
1114 /* Create Catalog entry for the current snapshot */
1115 int create_catalog_entry() {
1119 jcr->dir_bsock->fsend(CreateSnap,
1120 jcr->Job, Name, Volume,
1121 Device, CreateTDate, Type, edit_uint64(Retention, ed1));
1122 if (jcr->dir_bsock->recv() < 0) {
1123 Mmsg(errmsg, _("Unable to create snapshot record. ERR=%s\n"),
1124 jcr->dir_bsock->bstrerror());
1126 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1127 Mmsg(errmsg, _("Unable to create snapshot record, got %s\n"),
1128 jcr->dir_bsock->msg);
1137 /* Delete Catalog entry of the current snapshot */
1138 int delete_catalog_entry() {
1141 jcr->dir_bsock->fsend(DelSnap, jcr->Job, Name, Device);
1143 if (jcr->dir_bsock->recv() < 0) {
1144 Mmsg(errmsg, _("Unable to delete snapshot record. ERR=%s\n"),
1145 jcr->dir_bsock->bstrerror());
1147 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1148 Mmsg(errmsg, _("Unable to delete snapshot record, got %s\n"),
1149 jcr->dir_bsock->msg);
1159 /* Get Catalog entry of the current snapshot */
1160 int get_catalog_entry() {
1164 if (!*Name || !*Volume) {
1169 jcr->dir_bsock->fsend(GetSnap, jcr->Job, Name, Volume);
1171 if (jcr->dir_bsock->recv() < 0) {
1172 Mmsg(errmsg, _("Unable to get snapshot record. ERR=%s\n"),
1173 jcr->dir_bsock->bstrerror());
1175 } else if (strncmp(jcr->dir_bsock->msg, "1000", 4) != 0) {
1176 Mmsg(errmsg, _("Unable to get snapshot record, got %s\n"),
1177 jcr->dir_bsock->msg);
1180 if (cmd.parse_cmd(jcr->dir_bsock->msg) != bRC_OK) {
1181 Mmsg(errmsg, _("Unable to parse command output\n"));
1182 scan_arg(&cmd); /* Fill all parameters from director */
1195 /* Should be after snapshot declaration */
1196 void fs_device::destroy() {
1215 bool fs_device::can_do_snapshot() {
1216 if (snap && !supportSnapshotTested) {
1217 if (snap->support_snapshot(this)) {
1218 Dmsg2(DT_SNAPSHOT, "%s suitable for snapshot, type %s\n",
1219 mountpoint, snap->Type);
1220 isSuitableForSnapshot = true;
1223 Dmsg2(DT_SNAPSHOT, "%s is not suitable for snapshot, type %s\n",
1224 mountpoint, snap->Type);
1226 supportSnapshotTested = true;
1228 return isSuitableForSnapshot;
1231 /* Should be after the snapshot declaration */
1232 fs_device *mtab::search(char *file) {
1234 if (lstat(file, &statp) != 0) {
1235 Dmsg1(DT_SNAPSHOT, "%s not found\n", file);
1236 return NULL; /* not found */
1239 fs_device *elt = (fs_device *)entries->search((void *)((intptr_t)(statp.st_dev)),
1242 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not found in our mount list\n",
1243 statp.st_dev, file);
1244 return NULL; /* not found in our list, skip it */
1247 if (!elt->can_do_snapshot()) {
1248 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not snapshotable\n",
1249 statp.st_dev, file);
1252 Dmsg2(DT_SNAPSHOT, "Found device %d for file %s\n", elt->dev, file);
1256 /* Application to quiesce/un-quiesce */
1258 BPIPE *fd; /* Communication channel */
1259 char *name; /* Pointer to the script name */
1260 char cmd[1]; /* Command line */
1263 /* In the application manager, we want to run a set
1264 * of scripts and see if applications are running or
1265 * not on our partitions.
1267 * We can specify application in the fileset, or just
1268 * try all application that are installed.
1271 class app_manager: public SMARTALLOC
1275 char *appdir; /* Where to find application scripts */
1276 alist *applst; /* Application list (script list to call) */
1277 int apptimeout; /* Timeout when trying to quiesce application */
1278 mtab *mount_list; /* snapshot set */
1281 app_manager(JCR *ajcr, mtab *m, char *dir):
1284 applst(New(alist(10, owned_by_alist))),
1292 /* Put in a file the list of all devices that
1293 * are in the snapshot set
1295 bool dump_snapshotset() {
1299 /* Scan application */
1305 struct app *elt = NULL;
1307 if (!appdir || !*appdir || stat(appdir, &sp) == -1) {
1308 Dmsg0(DT_SNAPSHOT, "app not configured\n");
1312 /* Create a file with the list of all devices that are suitable
1317 results = get_pool_memory(PM_FNAME);
1318 if (run_program_full_output((char*)APPMANAGER_CMD, apptimeout, results) != 0) {
1320 Dmsg2(DT_SNAPSHOT, "app scan error results=%s ERR=%s\n", results, be.bstrerror());
1327 /* Put each line of the output in our list */
1328 for (start = results; start && *start;) {
1329 end = strchr(start, '\n');
1332 elt = (struct app *) malloc(sizeof(struct app) + strlen(start) + 1);
1335 strcpy(elt->cmd, start);
1336 elt->name = (char *)last_path_separator(elt->cmd);
1341 elt->name = elt->cmd;
1344 applst->append(elt);
1345 Dmsg2(10, "+ %s (%s)\n", elt->name, elt->cmd);
1352 free_pool_memory(results);
1357 if (applst->size() == 0) {
1361 Jmsg(jcr, M_INFO, 0, _("Un-Quiescing applications\n"));
1365 /* Quiesce applications */
1369 if (applst->size() == 0) {
1373 Jmsg(jcr, M_INFO, 0, _("Quiescing applications\n"));
1375 for (int i = 0 ; i < applst->size() ; i++) {
1376 struct app *elt = (struct app *) applst->get(i);
1377 elt->fd = open_bpipe(elt->cmd, 0, "rw");
1379 /* Unable to execute the program */
1382 /* Send some commands here */
1388 snapshot_manager::snapshot_manager(JCR *ajcr):
1389 jcr(ajcr), mount_list(New(mtab())) {
1392 snapshot_manager::~snapshot_manager() {
1396 bool snapshot_manager::cleanup_snapshots()
1399 foreach_rblist(elt, mount_list->entries) {
1400 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1401 snapshot *s = elt->snap;
1406 /* TODO: Display an error? Can check mounted status */
1408 /* When no retention is set, we delete the snapshot
1409 * just after the backup
1411 if (s->Retention == 0) {
1413 Jmsg(jcr, M_INFO, 0, _(" Delete Snapshot for %s\n"), elt->mountpoint);
1416 Jmsg(jcr, M_ERROR, 0, _(" Unable to delete snapshot of %s ERR=%s\n"),
1417 elt->mountpoint, s->errmsg);
1425 bool snapshot_manager::create_snapshots()
1427 /* No snapshot, no quiescing */
1428 if (mount_list->empty()) {
1429 Dmsg0(DT_SNAPSHOT, "The mount list is empty, no snapshot to take\n");
1433 /* First thing to do is to quiesce application */
1434 app_manager apps(jcr, mount_list, (char *)APP_DIR);
1436 /* TODO: Let see if we really need to abort
1437 * the snapshot part if application
1443 if (!apps.quiesce()) {
1448 foreach_rblist(elt, mount_list->entries) {
1449 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1450 snapshot *s = elt->snap;
1452 Jmsg(jcr, M_INFO, 0, _(" Create Snapshot for %s\n"), elt->mountpoint);
1454 if (s->Retention > 0) {/* Create snapshot catalog entry if we need to keep them */
1455 s->create_catalog_entry();
1458 } else if (s->status == 2) { /* Use Error message */
1459 elt->isSuitableForSnapshot = false; /* Disable snapshot for this device */
1460 Jmsg(jcr, M_ERROR, 0, _(" Unable to create snapshot of %s ERR=%s\n"),
1461 elt->mountpoint, s->errmsg);
1463 } else { /* By default, an error in the creation should be fatal */
1464 Jmsg(jcr, M_FATAL, 0, _(" Unable to create snapshot of %s ERR=%s\n"),
1465 elt->mountpoint, s->errmsg);
1471 Dmsg3(DT_SNAPSHOT|20, "No Snapshot for %s suitable=%d inset=%d\n",
1472 elt->mountpoint, elt->isSuitableForSnapshot, elt->inSnapshotSet);
1476 /* Snapshots are ok, we need to release applications */
1477 if (!apps.unquiesce()) {
1481 /* Save the include context */
1483 findINCEXE *old = get_incexe(jcr);
1484 findINCEXE *exclude = NULL;
1485 foreach_rblist(elt, mount_list->entries) {
1486 if (elt->can_do_snapshot() && elt->inSnapshotSet) {
1487 snapshot *s = elt->snap;
1490 Jmsg(jcr, M_ERROR, 0, " Unable to mount snapshot %s ERR=%s\n",
1491 elt->mountpoint, s->errmsg);
1493 } else if (*s->SnapDirectory) {
1495 exclude = new_exclude(jcr);
1496 /* Set the Exclude context */
1497 set_incexe(jcr, exclude);
1499 Mmsg(t, "%s/.snapshots", elt->snap->SnapMountPoint);
1500 Dmsg1(DT_SNAPSHOT|10, "Excluding %s\n", t.c_str());
1501 add_file_to_fileset(jcr, t.c_str(), true);
1506 /* Restore the current context */
1508 set_incexe(jcr, old);
1513 /* TODO: We might want to use some filters here */
1514 bool snapshot_manager::list_snapshots(alist *lst)
1518 /* No device, no snapshot */
1519 if (mount_list->dCount == 0) {
1520 Dmsg0(DT_SNAPSHOT, "mount list is empty, no snapshot\n");
1524 foreach_rblist(elt, mount_list->entries) {
1525 if (elt->can_do_snapshot()) {
1526 elt->snap->list(lst);
1532 void snapshot_manager::add_mount_point(uint32_t dev, const char *device,
1533 const char *mountpoint, const char *fstype)
1536 alist list(10, not_owned_by_alist);
1537 fs_device *vol = New(fs_device(dev, device, mountpoint, fstype));
1539 /* These FS are not supposed to use snapshot */
1540 const char *specialmp[] = {
1545 /* These FS are not supposed to use snapshot */
1546 const char *specialfs[] = {
1572 /* We skip directly /proc, /sys */
1573 for (int i=0; specialmp[i] ; i++) {
1574 if (strncasecmp(specialmp[i], mountpoint, strlen(specialmp[i])) == 0) {
1581 for (int i=0; specialfs[i] ; i++) {
1582 if (strcasecmp(specialfs[i], fstype) == 0) {
1590 snapshot *snap = New(snapshot(jcr));
1591 snap->set_name(jcr->Job);
1594 if (snap->scan_subvolumes(vol, &list)) {
1595 for (int i = list.size() - 1 ; i >= 0 ; i--) {
1596 fs_device *d = (fs_device *)list.remove(i);
1597 add_mount_point(d->dev, d->device, d->mountpoint, d->fstype);
1603 Dmsg4(DT_SNAPSHOT|20, "Adding %s dev=%d snap?=%d to the mount list (%d)\n",
1604 mountpoint, dev, check, mount_list->dCount);
1606 if (!mount_list->add_entry(vol)) {
1607 Dmsg1(DT_SNAPSHOT, "%s Already exists in mount list\n", vol->mountpoint);
1612 /* In this handler, we need to fill a mtab structure */
1613 static void add_handler(void *user_ctx,
1616 const char *mountpoint,
1617 const char *mntopts,
1620 Dmsg5(DT_SNAPSHOT|50, "dev=%ld device=%s mountpoint=%s fstype=%s mntopts=%s\n",
1621 st->st_dev, device, mountpoint, fstype, mntopts);
1623 /* TODO: If the fstype is btrfs or zfs, the fs might contains subvolumes,
1624 * and these subvolumes may not be reported in the mntent list. In this
1625 * case, we need to call proper btrfs/zfs commands to list them.
1626 * # btrfs subvolume list /mnt
1627 * ID 256 gen 17 top level 5 path test
1628 * ID 259 gen 18 top level 5 path test/titi
1630 snapshot_manager *snapmgr = (snapshot_manager *)user_ctx;
1631 snapmgr->add_mount_point(st->st_dev, device, mountpoint, fstype);
1634 bool snapshot_manager::scan_mtab()
1636 return read_mtab(add_handler, this);
1640 /* Scan the fileset to select partitions to snapshot */
1641 bool snapshot_manager::scan_fileset()
1643 if (!jcr->ff || !jcr->ff->fileset) {
1644 Dmsg0(DT_SNAPSHOT, "No fileset associated with JCR\n");
1648 findFILESET *fileset = jcr->ff->fileset;
1652 for (int i=0; i<fileset->include_list.size(); i++) {
1655 findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
1657 /* look through all files */
1658 foreach_dlist(node, &incexe->name_list) {
1659 char *fname = node->c_str();
1660 if (mount_list->add_in_snapshot_set(fname, &incexe->name_list, node)) {
1661 /* When all volumes are selected, we can stop */
1662 Dmsg0(DT_SNAPSHOT, "All Volumes are marked, stopping the loop here\n");
1667 foreach_alist(fo, &incexe->opts_list) {
1668 flags |= fo->flags; /* We are looking for FO_MULTIFS and recurse */
1673 /* If we allow recursion and multifs, we need to include sub volumes by hand
1674 * in the backup list
1676 if (flags & FO_MULTIFS) {
1677 fs_device *elt, *elt2;
1679 foreach_rblist(elt, mount_list->entries) {
1680 if (!elt->inFileSet) {
1683 alist *lst = New(alist(10, not_owned_by_alist));
1684 mount_list->get_subvolumes(elt->dev, lst, jcr->ff);
1685 foreach_alist(elt2, lst) {
1686 if (elt2->inFileSet) {
1690 /* TODO: See how to avoid having two entries for the same directory */
1691 /* Add the directory explicitely in the fileset */
1692 elt->include->insert_after(new_dlistString(elt2->mountpoint), elt->node);
1694 if (mount_list->add_in_snapshot_set(elt2, elt->include, elt->node)) {
1695 /* When all volumes are selected, we can stop */
1696 Dmsg0(DT_SNAPSHOT, "All Volumes are marked, stopping the loop here\n");
1707 int snapshot_cmd(JCR *jcr)
1709 BSOCK *dir = jcr->dir_bsock;
1713 snap.check_buffer_size(dir->msglen);
1715 n = sscanf(dir->msg, QueryCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type);
1717 snap.unbash_spaces();
1718 Dmsg0(DT_SNAPSHOT|10, "Doing query of a snapshot\n");
1720 bash_spaces(snap.errmsg);
1721 dir->fsend("%d Snapshot status=%d size=%lld ERR=%s\n", n?2000:2999, snap.status, snap.size, snap.errmsg);
1725 n = sscanf(dir->msg, LsCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type, snap.path);
1727 snap.unbash_spaces();
1728 Dmsg0(DT_SNAPSHOT|10, "Doing ls of a snapshot\n");
1730 dir->signal(BNET_EOD);
1734 n = sscanf(dir->msg, DelCmd, snap.Name, snap.Volume, snap.Device, &snap.CreateTDate, snap.Type);
1736 Dmsg0(DT_SNAPSHOT|10, "Doing del of a snapshot\n");
1737 snap.unbash_spaces();
1739 bash_spaces(snap.errmsg);
1740 dir->fsend("%d Snapshot deleted ERR=%s\n", n?2000:2999, snap.errmsg);
1744 n = sscanf(dir->msg, PruneCmd, snap.Volume, snap.Type);
1746 snap.unbash_spaces();
1747 n = snap.prune(dir);
1748 bash_spaces(snap.errmsg);
1749 dir->fsend("%d Snapshot pruned ERR=%s\n", n?2000:2999, snap.errmsg);
1750 Dmsg0(DT_SNAPSHOT|10, "Doing pruning of snapshots\n");
1754 n = sscanf(dir->msg, SyncCmd, snap.Volume, snap.Type);
1756 snap.unbash_spaces();
1758 bash_spaces(snap.errmsg);
1759 dir->fsend("%d Snapshot synced ERR=%s\n", n?2000:2999, snap.errmsg);
1760 Dmsg0(DT_SNAPSHOT|10, "Doing sync of snapshots\n");
1764 /* TODO: Include a path name or a device name */
1765 if (strncmp(dir->msg, ListCmd, strlen(ListCmd)) == 0) {
1768 alist *lst = New(alist(10, not_owned_by_alist));
1769 list_all_snapshots(jcr, lst);
1770 foreach_alist(elt, lst) {
1772 dir->fsend("volume=\"%s\" createtdate=\"%s\" name=\"%s\" device=\"%s\" status=%d error=\"%s\" type=\"%s\"\n",
1773 elt->Volume, edit_uint64(elt->CreateTDate, ed1),
1774 elt->Name, elt->Device, elt->status, elt->errmsg, elt->Type);
1778 dir->signal(BNET_EOD);
1782 n = sscanf(dir->msg, ConfCmd, ed1);
1784 jcr->snapshot_retention = str_to_uint64(ed1);
1785 dir->fsend("2000 Snapshot retention\n");
1789 dir->fsend("2999 Snapshot command not found\n");
1790 dir->signal(BNET_EOD);
1797 bool snapshot_convert_path(JCR *jcr, FF_PKT *ff, dlist *filelist, dlistString *node)
1799 Dmsg1(DT_SNAPSHOT, "snapshot_convert_path(%s)\n", ff->top_fname);
1800 snapshot_manager *snapmgr = jcr->snap_mgr;
1801 ff->strip_snap_path = false;
1807 fs_device *elt = snapmgr->mount_list->search(ff->top_fname);
1809 return true; /* not found */
1812 if (!ff->snap_fname) {
1813 ff->snap_fname = get_pool_memory(PM_FNAME);
1816 /* Convert the filename to the original path */
1817 if (!elt->snap->convert_path(ff)) {
1818 Dmsg2(DT_SNAPSHOT, "Device %d for file %s not snapshotable\n",
1819 elt->dev, ff->top_fname);
1825 /* ListSnap[] = "CatReq Job=%s list_snapshot name=%s volume=%s device=%s tdate=%d type=%s before=%s after=%s expired=%d"; */
1827 /* List Catalog entry of the current client */
1828 int snapshot_list_catalog(JCR *jcr,
1835 if (cmd.parse_cmd(query) != bRC_OK) {
1836 Dmsg1(DT_SNAPSHOT, "Unable to decode query %s\n", query);
1839 Mmsg(q, "CatReq Job=%s list_snapshot name=", jcr->Job);
1840 if ((i = cmd.find_arg_with_value("name")) >= 0) {
1841 bash_spaces(cmd.argv[i]);
1842 pm_strcat(q, cmd.argv[i]);
1845 pm_strcat(q, " volume=");
1846 if ((i = cmd.find_arg_with_value("volume")) >= 0) {
1847 bash_spaces(cmd.argv[i]);
1848 pm_strcat(q, cmd.argv[i]);
1851 pm_strcat(q, " device=");
1852 if ((i = cmd.find_arg_with_value("device")) >= 0) {
1853 bash_spaces(cmd.argv[i]);
1854 pm_strcat(q, cmd.argv[i]);
1857 pm_strcat(q, " tdate=");
1858 if ((i = cmd.find_arg_with_value("tdate")) >= 0) {
1859 bash_spaces(cmd.argv[i]);
1860 pm_strcat(q, cmd.argv[i]);
1863 pm_strcat(q, " type=");
1864 if ((i = cmd.find_arg_with_value("type")) >= 0) {
1865 bash_spaces(cmd.argv[i]);
1866 pm_strcat(q, cmd.argv[i]);
1869 pm_strcat(q, " before=");
1870 if ((i = cmd.find_arg_with_value("before")) >= 0) {
1871 bash_spaces(cmd.argv[i]);
1872 pm_strcat(q, cmd.argv[i]);
1875 pm_strcat(q, " after=");
1876 if ((i = cmd.find_arg_with_value("after")) >= 0) {
1877 bash_spaces(cmd.argv[i]);
1878 pm_strcat(q, cmd.argv[i]);
1881 pm_strcat(q, " expired=");
1882 if ((i = cmd.find_arg_with_value("expired")) >= 0) {
1883 bash_spaces(cmd.argv[i]);
1884 pm_strcat(q, cmd.argv[i]);
1887 jcr->dir_bsock->fsend("%s\n", q.c_str());
1889 while (jcr->dir_bsock->recv() > 0) {
1890 if (cmd.parse_cmd(jcr->dir_bsock->msg) != bRC_OK) {
1891 Dmsg1(DT_SNAPSHOT, "Unable to decode director output %s\n", jcr->dir_bsock->msg);
1895 snapshot *s = New(snapshot(jcr));