2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2016 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.
24 #include <sys/types.h>
25 #include <sys/mkdev.h> /* Define major() and minor() */
28 #define Dmsg(level, ...) do { \
29 if (level <= debug_level) { \
30 fprintf(debug, "%s:%d ", __FILE__ , __LINE__); \
31 fprintf(debug, __VA_ARGS__ ); \
35 #define Pmsg(level, ...) do { \
36 if (level <= debug_level) { \
37 fprintf(stderr, "%s:%d ", __FILE__ , __LINE__ ); \
38 fprintf(stderr, __VA_ARGS__ ); \
42 #define BSNAPSHOT_CONF SYSCONFDIR "/bsnapshot.conf"
44 static FILE *debug = NULL;
46 static void usage(const char *msg=NULL)
49 fprintf(stderr, _("ERROR %s\n\n"), msg);
55 " -d level Set debug level\n"
58 " -o logfile send debug to logfile\n"
60 " -T type volume type\n"
61 " -t check compatibility\n"
62 " -c specify configuration file\n"
63 "\n"), VERSION, LSMDATE);
67 static const char *Months[] = {
83 /* Skip leading slash(es) */
84 static bool makedir(char *path)
88 while (IsPathSeparator(*p)) {
91 while ((p = first_path_separator(p))) {
97 while (IsPathSeparator(*p)) {
101 /* If not having a ending / */
102 if (!IsPathSeparator(path[strlen(path) - 1])) {
108 /* Strip trailing junk and " */
109 void strip_quotes(char *str)
111 strip_trailing_junk(str);
112 for(char *p = str; *p ; p++) {
119 static void set_trace_file(const char *path)
121 char dt[MAX_TIME_LENGTH];
122 if (debug && debug != stderr) {
125 debug = fopen(path, "a");
129 Dmsg(10, "Starting bsnapshot %s\n",
130 bstrftime(dt, MAX_TIME_LENGTH, time(NULL)));
134 /* Small function to avoid double // in path name */
135 static void path_concat(POOLMEM *&dest, const char *path1, const char *path2, const char *path3) {
138 last = pm_strcpy(dest, path1);
139 last = MAX(last - 1, 0);
141 /* Check if the last char of dest is / and the first of path2 is / */
142 if (dest[last] == '/') {
143 if (path2[0] == '/') {
147 if (path2[0] != '/') {
148 pm_strcat(dest, "/");
152 last = pm_strcat(dest, path2);
153 last = MAX(last - 1, 0);
156 if (dest[last] == '/') {
157 if (path3[0] == '/') {
161 if (path3[0] != '/') {
162 pm_strcat(dest, "/");
165 pm_strcat(dest, path3);
169 static struct ini_items bsnap_cfg[] = {
170 // name handler comment required default
171 { "trace", ini_store_str, "", 0, NULL},
172 { "debug", ini_store_int32, "", 0, NULL},
173 { "sudo", ini_store_bool, "", 0, NULL},
174 { "disabled", ini_store_bool, "", 0, "no"},
175 { "retry", ini_store_int32, "", 0, "3"},
176 { "lvm_snapshot_size", ini_store_alist_str,"", 0, NULL},
177 { "skip_volume", ini_store_alist_str,"", 0, NULL},
178 { "snapshot_dir", ini_store_str, "", 0, NULL},
179 { "fail_job_on_error", ini_store_bool, "", 0, "yes"},
180 { NULL, NULL, NULL, 0, NULL}
185 char *action; /* list, create, delete... */
186 char *volume; /* snapshot device */
187 char *device; /* original device name */
188 char *name; /* snapshot name */
189 char *mountpoint; /* device mountpoint */
190 char *snapmountpoint; /* snapshot mountpoint */
191 char *type; /* snapshot type */
192 char *fstype; /* filesystem type */
193 const char *snapdir; /* .snapshot */
194 const char *sudo; /* prepend sudo to commands */
196 int retry; /* retry some operations */
197 bool disabled; /* disabled by config file */
198 bool fail_job_on_error; /* Fail job on snapshot error */
199 ConfigFile ini; /* Configuration file */
200 POOL_MEM config_file; /* Path to a config file */
203 action(getenv("SNAPSHOT_ACTION")),
204 volume(getenv("SNAPSHOT_VOLUME")),
205 device(getenv("SNAPSHOT_DEVICE")),
206 name( getenv("SNAPSHOT_NAME")),
207 mountpoint(getenv("SNAPSHOT_MOUNTPOINT")),
208 snapmountpoint(getenv("SNAPSHOT_SNAPMOUNTPOINT")),
209 type( getenv("SNAPSHOT_TYPE")),
210 fstype(getenv("SNAPSHOT_FSTYPE")),
211 snapdir(".snapshots"),
216 fail_job_on_error(true)
219 ini.register_items(bsnap_cfg, sizeof(struct ini_items));
221 if (stat(BSNAPSHOT_CONF, &sp) == 0) {
222 Dmsg(10, "conf=%s\n", BSNAPSHOT_CONF);
223 pm_strcpy(config_file, BSNAPSHOT_CONF);
235 if (strcmp(config_file.c_str(), "") != 0) {
236 Dmsg(10, "Reading configuration from %s\n", config_file.c_str());
237 if (!ini.parse(config_file.c_str())) {
238 printf("status=1 error=\"Unable to parse %s\"\n",
239 config_file.c_str());
242 pos = ini.get_item("debug");
243 if (ini.items[pos].found && debug_level == 0) {
244 debug_level = ini.items[pos].val.int32val;
246 pos = ini.get_item("trace");
247 if (ini.items[pos].found) {
248 set_trace_file(ini.items[pos].val.strval);
250 pos = ini.get_item("sudo");
251 if (ini.items[pos].found && ini.items[pos].val.boolval) {
254 pos = ini.get_item("snapshot_dir");
255 if (ini.items[pos].found) {
256 snapdir = ini.items[pos].val.strval;
258 pos = ini.get_item("retry");
259 if (ini.items[pos].found) {
260 retry = ini.items[pos].val.int32val;
262 pos = ini.get_item("disabled");
263 if (ini.items[pos].found) {
264 disabled = ini.items[pos].val.boolval;
266 pos = ini.get_item("fail_job_on_error");
267 if (ini.items[pos].found) {
268 fail_job_on_error = ini.items[pos].val.boolval;
277 const char *type; /* snapshot type, btrfs, zfs, etc.. */
278 POOLMEM *cmd; /* buffer to edit a command */
279 POOLMEM *path; /* buffer to edit volume path */
280 POOLMEM *fname; /* used for split_path_and_filename */
281 POOLMEM *errmsg; /* buffer to edit error message */
282 arguments *arg; /* program argument */
283 int pnl; /* path length */
284 int fnl; /* fname length */
286 snapshot(arguments *a, const char *t):
288 cmd(get_pool_memory(PM_NAME)),
289 path(get_pool_memory(PM_NAME)),
290 fname(get_pool_memory(PM_NAME)),
291 errmsg(get_pool_memory(PM_NAME)),
298 virtual ~snapshot() {
299 free_pool_memory(cmd);
300 free_pool_memory(path);
301 free_pool_memory(fname);
302 free_pool_memory(errmsg);
305 /* Basically, we check parameters here that are
306 * common to all backends
308 virtual int mount() {
309 Dmsg(10, "[%s] Doing mount command\n", type);
310 if (!arg->volume || !arg->name || !arg->device || !arg->mountpoint) {
311 Dmsg(10, "volume=%s name=%s device=%s mountpoint=%s\n",
312 NPRT(arg->volume), NPRT(arg->name),
313 NPRT(arg->device), NPRT(arg->mountpoint));
319 virtual int unmount() {
320 Dmsg(10, "[%s] Doing unmount command on %s\n", type,
321 NPRT(arg->snapmountpoint));
322 if (!arg->snapmountpoint) {
323 Dmsg(10, "snapmountpoint=%s\n", NPRT(arg->snapmountpoint));
329 virtual int support() {
330 Dmsg(10, "[%s] Doing support on %s (%s)\n", type, NPRT(arg->mountpoint),
332 if (!arg->fstype || !arg->mountpoint || !arg->device) {
333 Dmsg(10, "fstype=%s mountpoint=%s device=%s\n",
334 NPRT(arg->fstype), NPRT(arg->mountpoint), NPRT(arg->device));
340 virtual int check() {
341 Dmsg(10, "[%s] Doing check on %s\n", type, NPRT(arg->mountpoint));
342 if (!arg->mountpoint) {
343 Dmsg(10, "mountpoint=%s\n", NPRT(arg->mountpoint));
349 virtual int create() {
350 Dmsg(10, "[%s] Doing create %s\n", type, NPRT(arg->mountpoint));
351 if (!arg->mountpoint || !arg->name || !arg->device) {
352 Dmsg(10, "mountpoint=%s name=%s device=%s\n",
353 NPRT(arg->mountpoint), NPRT(arg->name), NPRT(arg->device));
360 Dmsg(10, "[%s] Doing del %s\n", type, NPRT(arg->volume));
361 if (!arg->volume || !arg->name) {
362 Dmsg(10, "volume=%s name=%s\n",
363 NPRT(arg->volume), NPRT(arg->name));
370 Dmsg(10, "[%s] Doing list on %s\n", type, NPRT(arg->device));
371 if (!arg->type || !arg->device || !arg->mountpoint) {
377 virtual int subvolumes() {
378 Dmsg(10, "[%s] Doing subvolumes %s\n", type, NPRT(arg->mountpoint));
379 if (!arg->fstype || !arg->device || !arg->mountpoint) {
385 /* Function used in create() to know if we mark the error as FATAL */
386 int get_error_code() {
387 Dmsg1(0, "get_error_code = %d\n", (int)arg->fail_job_on_error);
389 if (arg->fail_job_on_error) {
390 return 0; /* Fatal */
392 return 2; /* Error */
396 /* Structure used to sort subvolumes with btrfs backend */
401 char uuid[MAX_NAME_LENGTH];
402 char puuid[MAX_NAME_LENGTH];
403 char otime[MAX_NAME_LENGTH];
407 int vols_compare_id(void *item1, void *item2)
409 vols *vol1 = (vols *) item1;
410 vols *vol2 = (vols *) item2;
412 if (vol1->id > vol2->id) {
415 } else if (vol1->id < vol2->id) {
423 int vols_compare_uuid(void *item1, void *item2)
425 vols *vol1 = (vols *) item1;
426 vols *vol2 = (vols *) item2;
428 return strcmp(vol1->uuid, vol2->uuid);
432 class btrfs: public snapshot {
434 btrfs(arguments *arg): snapshot(arg, "btrfs") {};
436 /* With BTRFS, the volume is already mounted */
438 if (!snapshot::mount()) {
441 split_path_and_filename(arg->volume, &path, &pnl, &fname, &fnl);
442 fprintf(stdout, "status=1 snapmountpoint=\"%s\" snapdirectory=\"%s\"\n",
448 if (!snapshot::unmount()) {
451 printf("status=1\n");
456 if (!snapshot::support()) {
459 /* If the fstype is btrfs, snapshots are supported */
461 Mmsg(cmd, "%sbtrfs filesystem label \"%s\"", arg->sudo, arg->mountpoint);
462 if (run_program(cmd, 60, errmsg)) {
463 printf("status=0 type=btrfs\n");
466 Dmsg(0, "output=%s\n", errmsg);
468 printf("status=1 device=\"%s\" type=btrfs\n", arg->mountpoint);
473 if (!snapshot::check()) {
480 utime_t createdate = 0;
482 if (!snapshot::create()) {
486 Mmsg(path, "%s/%s", arg->mountpoint, arg->snapdir);
487 if (!makedir(path)) {
488 printf("status=%d error=\"Unable to create mountpoint directory %s errno=%d\n",
490 arg->mountpoint, errno);
494 Dmsg(10, "mountpoint=%s snapdir=%s name=%s\n", arg->mountpoint, arg->snapdir, arg->name);
495 path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
496 Dmsg(10, "path=%s\n", path);
498 /* Create the actual btrfs snapshot */
499 Mmsg(cmd, "%sbtrfs subvolume snapshot -r \"%s\" \"%s\"",
500 arg->sudo, arg->mountpoint, path);
502 if (run_program(cmd, 60, errmsg)) {
503 Dmsg(10, "Unable to create snapshot %s %s\n", arg->mountpoint, errmsg);
504 strip_quotes(errmsg);
505 printf("status=%d error=\"Unable to create snapshot %s\"\n",
511 /* On SLES12 btrfs 3.16, commands on "/" returns "doesn't belong to btrfs mount point" */
512 Mmsg(cmd, "%sbtrfs subvolume show \"%s\"", arg->sudo, path);
513 if (run_program_full_output(cmd, 60, errmsg)) {
514 Dmsg(10, "Unable to display snapshot stats %s %s\n", arg->mountpoint, errmsg);
517 /* TODO: Check that btrfs subvolume show is reporting "Creation time:" */
518 char *p = strstr(errmsg, "Creation time:");
520 p += strlen("Creation time:");
522 createdate = str_to_utime(p);
525 Dmsg(10, "Unable to find Creation time on %s %s\n", arg->mountpoint, errmsg);
530 createdate = time(NULL);
532 printf("status=1 volume=\"%s\" createtdate=%s type=btrfs\n",
533 path, edit_uint64(createdate, ed1));
538 if (!snapshot::del()) {
542 Mmsg(cmd, "%sbtrfs subvolume delete \"%s\"", arg->sudo, arg->volume);
543 if (run_program(cmd, 300, errmsg)) {
544 Dmsg(10, "Unable to delete snapshot %s\n", errmsg);
545 strip_quotes(errmsg);
546 printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
549 printf("status=1\n");
553 /* btrfs subvolume list -u -q -s /tmp/regress/btrfs
554 * ID 259 gen 52 top level 5 parent_uuid - uuid baf4b5d7-28d0-9b4a-856e-36e6fd4fbc96 path .snapshots/aaa
557 char *p, *p2, *end, *path;
558 char id[50], day[50], hour[50];
559 struct vols *v = NULL, *v2;
562 if (!snapshot::list()) {
565 Mmsg(cmd, "%sbtrfs subvolume list -u -q -o -s \"%s\"", arg->sudo, arg->mountpoint);
566 if (run_program_full_output(cmd, 300, errmsg)) {
567 Dmsg(10, "Unable to list snapshot %s\n", errmsg);
568 strip_quotes(errmsg);
569 printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
573 lst = New(rblist(v, &v->link));
575 /* ID 259 gen 52 top level 5 parent_uuid - uuid baf4b5d7-28d0-9b4a-856e-36e6fd4fbc96 path .snapshots/aaa */
576 for (p = errmsg; p && *p ;) {
577 Dmsg(20, "getting subvolumes from %s", p);
579 /* Replace final \n by \0 to have strstr() happy */
580 end = strchr(p, '\n');
582 /* If end=NULL, we are at the end of the buffer (without trailing \n) */
587 /* Each line is supposed to start with "ID", and end with "path" */
589 if (sscanf(p, "ID %50s ", id) == 1) { /* We found ID, look for path */
590 p2 = strstr(p, "path ");
592 path = p2 + strlen("path ");
593 v = (struct vols*) malloc(sizeof (vols) + strlen(path) + 1);
594 *v->otime = *v->uuid = *v->puuid = 0;
595 v->id = str_to_int64(id);
597 strcpy(v->path, path);
599 p2 = strstr(p, "otime");
600 if (p2 && sscanf(p2, "otime %50s %50s", day, hour) == 2) {
601 bsnprintf(v->otime, sizeof(v->otime), "%s %s", day, hour);
604 p2 = strstr(p, "parent_uuid ");
605 if (p2 && sscanf(p2, "parent_uuid %127s", v->puuid) == 1) {
607 p2 = strstr(p, " uuid ");
608 if (p2 && sscanf(p2, " uuid %127s", v->uuid) == 1) {
610 v2 = (struct vols *)lst->insert(v, vols_compare_uuid);
616 /* Replace final \n by \0 to have strstr() happy */
617 Dmsg(10, "puuid=%s uuid=%s path=%s\n", v2->puuid, v2->uuid, v2->path);
623 Dmsg(10, "Unable to decode \"%s\" line\n", p);
629 /* If end==NULL, we stop */
633 foreach_rblist(v, lst) {
634 char *name = v->path;
635 int len = strlen(arg->snapdir);
636 if ((p = strstr(v->path, arg->snapdir))) {
637 name = p + len + ((arg->snapdir[len-1] == '/') ? 0 : 1);
639 printf("volume=\"%s%s%s\" name=\"%s\" device=\"%s\" createdate=\"%s\" type=\"btrfs\"\n",
641 arg->mountpoint[strlen(arg->mountpoint) - 1] == '/' ? "": "/",
653 void scan_subvolumes(char *buf, rblist *lst) {
657 struct vols *elt1 = NULL, *elt2 = NULL;
659 /* btrfs subvolume list /var/lib/pacman/
660 * ID 349 gen 383 top level 5 path test
661 * ID 354 gen 391 cgen 391 top level 5 otime 2014-11-05 17:49:07 path .snapshots/aa
663 for (p = buf; p && *p ;) {
664 Dmsg(20, "getting subvolumes from %s", p);
666 /* Replace final \n by \0 to have strstr() happy */
667 end = strchr(p, '\n');
668 /* If end=NULL, we are at the end of the buffer (without trailing \n) */
673 /* Each line is supposed to start with "ID", and end with "path" */
674 ok = (sscanf(p, "ID %50s ", id) == 1);
675 if (ok) { /* We found ID, look for path */
676 p = strstr(p, "path ");
678 p += strlen("path ");
680 elt1 = (struct vols *) malloc(sizeof(struct vols) + strlen(p) + 1);
681 elt1->id = str_to_int64(id);
683 strcpy(elt1->path, p);
684 Dmsg(10, "Found path %s for id %s\n", elt1->path, id);
685 elt2 = (struct vols *)lst->insert(elt1, vols_compare_id);
691 Dmsg(10, "Unable to find the path in this line\n");
695 Dmsg(10, "Unable to decode %s line\n", p);
701 /* If end==NULL, we stop */
706 /* List subvolumes, they may not be listed by mount */
710 struct vols *elt1 = NULL;
713 Mmsg(cmd, "%sbtrfs subvolume show \"%s\"", arg->sudo, arg->mountpoint);
714 if (run_program_full_output(cmd, 300, errmsg)) {
715 Dmsg(10, "Unable to get information %s\n", errmsg);
716 strip_quotes(errmsg);
717 printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
721 /* TODO: Very week way to analyse FS */
722 if (!strstr(errmsg, "is btrfs root")) {
723 printf("status=0 type=btrfs error=\"Not btrfs root fs\"\n");
727 Mmsg(cmd, "%sbtrfs subvolume list -s \"%s\"", arg->sudo, arg->mountpoint);
728 if (run_program_full_output(cmd, 300, errmsg)) {
729 Dmsg(10, "Unable to list snapshot snapshot %s\n", errmsg);
730 strip_quotes(errmsg);
731 printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
735 lst = New(rblist(elt1, &elt1->link));
736 scan_subvolumes(errmsg, lst);
738 Mmsg(cmd, "%sbtrfs subvolume list \"%s\"", arg->sudo, arg->mountpoint);
739 if (run_program_full_output(cmd, 300, errmsg)) {
740 Dmsg(10, "Unable to list subvolume %s\n", errmsg);
741 strip_quotes(errmsg);
742 printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
746 scan_subvolumes(errmsg, lst);
748 foreach_rblist(elt1, lst) {
749 if (elt1->count > 0) { /* Looks to be a snapshot, we saw two entries */
753 path_concat(path, arg->mountpoint, elt1->path, NULL);
755 if (stat(path, &sp) == 0) {
756 printf("dev=%s mountpoint=\"%s\" fstype=btrfs\n",
757 edit_uint64(sp.st_dev, ed1), path);
760 Dmsg(10, "Unable to stat %s (%s)\n", elt1->path, path);
769 * zpool create pool /dev/device
770 * zfs create pool/eric
771 * zfs set mountpoint=/mnt test/eric
772 * zfs mount pool/eric
775 class zfs: public snapshot {
777 zfs(arguments *arg): snapshot(arg, "zfs") {
778 arg->snapdir = ".zfs/snapshot";
781 /* With ZFS, the volume is already mounted
782 * but on linux https://github.com/zfsonlinux/zfs/issues/173
783 * we need to use the mount command.
784 * TODO: Adapt the code for solaris
789 if (!snapshot::mount()) {
793 path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
795 if (stat(path, &sp) != 0) {
796 /* See if we can change the snapdir attribute */
797 Mmsg(cmd, "%szfs set snapdir=visible \"%s\"", arg->sudo, arg->device);
798 if (run_program(cmd, 60, errmsg)) {
799 Dmsg(10, "Unable to change the snapdir attribute %s %s\n", arg->device, errmsg);
800 strip_quotes(errmsg);
801 printf("status=0 error=\"Unable to mount snapshot %s\"\n", errmsg);
804 if (stat(path, &sp) != 0) {
805 Dmsg(10, "Unable to get the snapdir %s %s\n", arg->snapdir, arg->device);
806 strip_quotes(errmsg);
807 printf("status=0 error=\"Unable to mount snapshot, no snapdir %s\"\n", arg->snapdir);
811 #if 0 /* On linux, this function is broken for now */
813 Mmsg(cmd, "%smount -t %s \"%s\" \"%s\"", arg->sudo, arg->fstype, arg->volume, path);
814 if (run_program(cmd, 60, errmsg)) {
815 Dmsg(10, "Unable to create mount snapshot %s %s\n", arg->volume, errmsg);
816 strip_quotes(errmsg);
817 printf("status=0 error=\"Unable to mount snapshot %s\"\n", errmsg);
822 fprintf(stdout, "status=1 snapmountpoint=\"%s\" snapdirectory=\"%s/%s\"\n",
823 path, arg->mountpoint, arg->snapdir);
827 /* No need to unmount something special */
829 printf("status=1\n");
834 if (!snapshot::support()) {
837 Mmsg(cmd, "%szfs list -H -o name \"%s\"", arg->sudo, arg->mountpoint);
838 if (run_program(cmd, 60, errmsg)) {
839 Dmsg(10, "Unable to get device %s %s\n", arg->mountpoint, errmsg);
840 strip_quotes(errmsg);
841 printf("status=0 error=\"Unable to get device %s\"\n", errmsg);
844 strip_trailing_junk(errmsg);
845 /* If the fstype is zfs, snapshots are supported */
846 printf("status=1 device=\"%s\" type=zfs\n", errmsg);
853 if (!snapshot::create()) {
857 Mmsg(path, "%s@%s", arg->device, arg->name);
859 /* Create the actual zfs snapshot */
860 Mmsg(cmd, "%szfs snapshot \"%s\"", arg->sudo, path);
862 if (run_program(cmd, 60, errmsg)) {
863 Dmsg(10, "Unable to create snapshot %s %s\n", arg->device, errmsg);
864 strip_quotes(errmsg);
865 printf("status=%d error=\"Unable to create snapshot %s\"\n",
871 Mmsg(cmd, "%szfs get -p creation \"%s\"", arg->sudo, path);
872 if (run_program_full_output(cmd, 60, errmsg)) {
873 Dmsg(10, "Unable to display snapshot stats %s %s\n", arg->device, errmsg);
874 strip_quotes(errmsg);
875 printf("status=%d error=\"Unable to get snapshot info %s\"\n",
881 /* TODO: Check that zfs get is reporting "creation" time */
882 Mmsg(cmd, "NAME PROPERTY VALUE SOURCE\n%s creation %%s", path);
883 if (sscanf(errmsg, cmd, ed1) == 1) {
884 Dmsg(10, "Found CreateTDate=%s\n", ed1);
885 printf("status=1 volume=\"%s\" createtdate=%s type=zfs\n",
889 printf("status=1 volume=\"%s\" createtdate=%s type=zfs\n",
890 path, edit_uint64(time(NULL), ed1));
896 if (!snapshot::del()) {
900 Mmsg(cmd, "%szfs destroy \"%s\"", arg->sudo, arg->volume);
901 if (run_program(cmd, 300, errmsg)) {
902 Dmsg(10, "Unable to delete snapshot %s\n", errmsg);
903 strip_quotes(errmsg);
904 printf("status=0 type=zfs error=\"%s\"\n", errmsg);
907 printf("status=1\n");
911 /* zfs list -t snapshot
912 * test/eric@snap1 17K - 21K -
913 * test/eric@snap2 17K - 21K -
915 * it is possible to change fields to display with -o
919 if (!snapshot::list()) {
923 Mmsg(cmd, "%szfs list -t snapshot -H -o name,used,creation", arg->sudo);
924 /* rpool@basezone_snap00 0 Fri Mar 6 9:55 2015 */
925 if (run_program_full_output(cmd, 60, errmsg)) {
926 Dmsg(10, "Unable to list snapshot %s\n", errmsg);
927 strip_quotes(errmsg);
928 printf("status=0 error=\"Unable to list snapshot %s\"\n", errmsg);
932 int i = 1, Day, Year, Hour, Min;
933 char DayW[50], Month[50], CreateDate[50];
937 for (char *p = errmsg; p && *p ; p++) {
940 /* Flush the current one */
941 if (!arg->device || strcmp(arg->device, buf[0]) == 0) {
943 if (sscanf(buf[3], "%s %s %d %d:%d %d",
944 DayW, Month, &Day, &Hour, &Min, &Year) == 6)
946 /* Get a clean iso format */
947 for (int j=1; j <= 12 ; j++) {
948 if (strcmp(Month, Months[j]) == 0) {
949 snprintf(Month, sizeof(Month), "%02d", j);
952 snprintf(CreateDate, sizeof(CreateDate), "%d-%s-%02d %02d:%02d:00",
953 Year, Month, Day, Hour, Min);
956 printf("volume=\"%s@%s\" name=\"%s\" device=\"%s\" size=\"%s\" "
957 "createdate=\"%s\" status=1 error=\"\" type=\"zfs\"\n",
958 buf[0], buf[1], buf[1], buf[0], buf[2], buf[3]);
960 Dmsg(10, "Do not list %s@%s\n", buf[0], buf[1]);
965 buf[1] = buf[2] = buf[3] = "";
967 } else if ((*p == '\t' || *p == '@') && i < 4) {
977 /* Structure of the LVS output */
983 /* -1 is mandatory, -2 is optionnal */
984 static Header lvs_header[] = {
986 {"Path", -1}, /* Volume Path: /dev/ubuntu-vg/root */
987 {"DMPath",-2}, /* Device mapper Path /dev/mapper/ubuntu--vg-root */
988 {"LV", -1}, /* Volume Name: root */
989 {"Attr", -1}, /* Attributes: -wi-ao--- */
990 {"KMaj", -1}, /* Kernel Major: 252 */
991 {"KMin", -1}, /* Kernel Minor: 0 */
992 {"LSize", -1}, /* Size (b) */
993 {"#Seg", -1}, /* Number of segments */
997 {"Time", -1}, /* Creation date */
1001 static Header vgs_header[] = {
1003 {"VG", -1}, /* VG Name: vgroot */
1004 {"VSize", -1}, /* Size */
1005 {"VFree", -1}, /* Space left */
1006 {"#Ext", -1}, /* Nb Ext */
1007 {"Free", -1}, /* Nb Ext free */
1008 {"Ext", -1}, /* Ext size */
1012 /* LVM backend, not finished */
1013 class lvm: public snapshot {
1016 int lvs_nbelt, vgs_nbelt;
1018 lvm(arguments *arg):
1019 snapshot(arg, "lvm"), lvs(NULL), vgs(NULL), lvs_nbelt(0),
1023 free_header(lvs, lvs_nbelt);
1024 free_header(vgs, vgs_nbelt);
1027 void free_header(alist *lst, int nbelt) {
1030 /* cleanup at the end */
1031 foreach_alist(current, lst) {
1032 for (int j=0; j < nbelt ; j++) {
1033 Dmsg(50, "current[%d] = %s\n", j, current[j]);
1042 char *get_vg_from_lv_path(char *path, char *vg, int max) {
1049 /* Make a copy of the path */
1050 bstrncpy(vg, path, max);
1053 if (strncmp(path, "/dev/", 5) != 0) {
1054 Dmsg(10, "Strange path %s\n", path);
1057 path += 5; /* skip /dev/ */
1059 /* End the string at the last / */
1060 p = strchr(path, '/');
1062 Dmsg(10, "Strange end of path %s\n", path);
1070 /* Report the space available on VG */
1071 int64_t get_space_available(char *lv) {
1073 char *vgname = get_vg_from_lv_path(get_lv_value(lv, "Path"),
1077 char *s = get_vg_value(vgname, "VFree");
1079 return str_to_int64(s);
1082 Dmsg(10, "Unable to get VFree\n");
1086 Dmsg(10, "Unable to get VG from %s\n", lv);
1091 /* return vg_ssd-pacman */
1092 char *get_lv_from_dm(char *dm, POOLMEM **ret, uint32_t *major, uint32_t *minor) {
1097 /* Looks to be a device mapper, need to convert the name */
1098 if (strncmp(dm, "/dev/dm", strlen("/dev/dm")) != 0) {
1101 if (stat(dm, &sp) < 0) {
1105 Mmsg(cmd, "%sdmsetup ls", arg->sudo);
1106 if (run_program_full_output(cmd, 60, errmsg)) {
1107 Dmsg(10, "Unable to query dmsetup %s\n", errmsg);
1110 /* vg_ssd-pacman-real (254:1)
1111 * vg_ssd-pacman (254:0)
1113 * vg_ssd-pacman-real (254, 1)
1114 * vg_ssd-pacman-real (254, 1)
1116 *ret = check_pool_memory_size(*ret, strlen(errmsg)+1);
1117 for (start = p = errmsg; *p ; p++) {
1120 if (sscanf(start, "%s (%d:%d)", *ret, &maj, &min) == 3 ||
1121 sscanf(start, "%s (%d, %d)", *ret, &maj, &min) == 3)
1123 if (maj == major(sp.st_rdev) &&
1124 min == minor(sp.st_rdev))
1135 /* The LV path from name or dmpath */
1136 char **get_lv(char *lv) {
1137 char **elt = NULL, *dm = NULL;
1138 int path = get_value_pos(lvs_header, "Path");
1139 int dmpath = get_value_pos(lvs_header, "DMPath");
1140 int kmaj = get_value_pos(lvs_header, "KMaj");
1141 int kmin = get_value_pos(lvs_header, "KMin");
1142 uint32_t min = 0, maj = 0;
1143 POOLMEM *buf = get_pool_memory(PM_FNAME);
1145 if (!lv || (path < 0 && dmpath < 0)) {
1146 Dmsg(10, "Unable to get LV parameters\n");
1150 dm = get_lv_from_dm(lv, &buf, &maj, &min);
1151 Dmsg(50, "%s = get_lv_from_dm(%s, %s, %d, %d)\n", dm, lv, buf, maj, min);
1153 /* HERE: Need to loop over LVs */
1154 foreach_alist(elt, lvs) {
1155 if (path > 0 && strcmp(NPRT(elt[path]), lv) == 0) {
1159 if (dmpath > 0 && strcmp(NPRT(elt[dmpath]), lv) == 0) {
1163 /* Try by Minor/Major if comming from device mapper */
1164 if ((maj && kmaj && str_to_uint64(elt[kmaj]) == maj) &&
1165 (min && kmin && str_to_uint64(elt[kmin]) == min))
1170 /* Find if /dev/mapper/vg_ssd-pacman matches vg_ssd-pacman */
1171 if (dm && dmpath && strlen(elt[dmpath]) > strlen("/dev/mapper/")) {
1172 if (strcmp(elt[dmpath] + strlen("/dev/mapper/"), dm) == 0) {
1177 /* Special case for old LVM where mapper path doesn't exist */
1178 if (dmpath < 0 && strncmp("/dev/mapper/", lv, 12) == 0) {
1180 POOLMEM *buf2 = get_memory(strlen(elt[path])*2+10);
1181 pm_strcpy(buf2, "/dev/mapper/");
1183 char *d = buf2 + 12; /* Skip /dev/mapper/ */
1186 /* Keep the same path, but escape - to -- and / to - */
1187 for (char *p = elt[path]+5; *p ; p++) {
1191 /* Escape / to - if needed */
1192 *d++ = (*p == '/') ? '-' : *p;
1195 ret = (strcmp(buf2, lv) == 0);
1196 free_pool_memory(buf2);
1203 Dmsg(10, "%s not found in lv list\n", lv);
1204 return NULL; /* not found */
1208 free_pool_memory(buf);
1213 /* Report LV Size in bytes */
1214 int64_t get_lv_size(char *name) {
1215 char **elt = get_lv(arg->device);
1222 sp = get_value_pos(lvs_header, "LSize");
1223 /* Check if we have enough space on the VG */
1224 return str_to_int64(elt[sp]);
1227 char *get_lv_value(char *name, const char *value) {
1228 return get_value(lvs_header, lvs_nbelt, lvs, name, value);
1231 int get_value_pos(Header *header, const char *value) {
1232 for (int i = 0; header[i].name ; i++) {
1233 if (strcmp(header[i].name, value) == 0) {
1234 return header[i].pos;
1237 return -1; /* not found */
1240 /* Return an element value */
1241 char *get_value(Header *header, int nbelt, alist *lst,
1242 char *name, const char *value) {
1244 int pos = get_value_pos(header, value);
1245 int id = header[0].pos; /* position name */
1247 if (pos < 0 || id == -1) {
1250 /* Loop over elements we have, and return the value that is asked */
1251 foreach_alist(elt, lst) {
1252 if (strcmp(NPRT(elt[id]), name) == 0) {
1259 /* Return a parameter for a VolumeGroup */
1260 char *get_vg_value(char *vg, const char *value) {
1261 return get_value(vgs_header, vgs_nbelt, vgs, vg, value);
1264 /* Get snapshot size, look in config file if needed */
1265 int get_lvm_snapshot_size(char *lv) {
1271 int pos = arg->ini.get_item("lvm_snapshot_size");
1272 if (!arg->ini.items[pos].found) {
1273 return -1; /* Nothing specified, stop here */
1276 lst = arg->ini.items[pos].val.alistval;
1278 /* /dev/ubuntu-vg/root:100M
1279 * /dev/ubuntu-vg/home:10%
1280 * /dev/ubuntu-vg/var:200GB
1282 foreach_alist(tmp, lst) {
1283 char *p = strchr(tmp, ':');
1285 /* Check the LV name */
1286 if (p && strncmp(tmp, lv, p - tmp) != 0) {
1290 /* This is a percent */
1291 if (strchr(p+1, '%') != NULL) {
1292 Dmsg(10, "Found a %%\n");
1293 s = str_to_int64(p+1);
1295 /* Compute the requested size */
1296 sp = get_value_pos(lvs_header, "LSize");
1298 size = str_to_int64(elt[sp]);
1299 return size * (s / 100);
1302 /* It might be a size */
1303 if (size_to_uint64(p+1, strlen(p+1), &s)) {
1304 Dmsg(10, "Found size %ld\n", s);
1307 Dmsg(10, "Unable to use %s\n", tmp);
1315 char *name, *ts, buf[128], *lvname;
1316 int64_t size, ssize, maxsize;
1317 if (!snapshot::create()) {
1321 if (!parse_lvs_output() ||
1322 !parse_vgs_output())
1324 printf("status=%d error=\"Unable parse lvs or vgs output\"\n",
1329 path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
1331 if (!makedir(path)) {
1332 printf("status=%d error=\"Unable to create mountpoint directory %s errno=%d\n",
1334 arg->mountpoint, errno);
1338 name = get_lv_value(arg->device, "LV");
1339 size = get_lv_size(arg->device);
1341 printf("status=%d error=\"Unable to get lv size\"\n",
1346 ssize = get_lvm_snapshot_size(arg->device);
1350 size = size / 10; /* Ask to get 10% */
1353 size = (size / 512L) * 512L;
1355 lvname = get_lv_value(arg->device, "Path");
1356 maxsize = get_space_available(lvname);
1357 Dmsg(10, "maxsize=%ld size=%ld\n", maxsize, size);
1360 printf("status=%d error=\"Unable to detect maxsize\" type=lvm\n",
1365 if (size > maxsize) {
1366 char ed1[50], ed2[50];
1367 printf("status=%d error=\"Not enough space left on VG %sB, "
1368 "%sB is required\" type=lvm\n",
1370 edit_uint64_with_suffix(maxsize, ed1),
1371 edit_uint64_with_suffix(size, ed2));
1375 /* TODO: Need to get the volume name and add the snapshot
1378 Mmsg(cmd, "%slvcreate -s -n \"%s_%s\" -L %lldb \"%s\"",
1379 arg->sudo, name, arg->name, size, arg->device);
1380 if (run_program(cmd, 60, errmsg)) {
1381 Dmsg(10, "Unable to create snapshot %s %s\n", arg->name, errmsg);
1382 strip_quotes(errmsg);
1383 printf("status=0 error=\"Unable to create snapshot %s\"\n", errmsg);
1386 if (!parse_lvs_output()) {
1387 Dmsg(10, "Unable to parse lvm output after snapshot creation\n");
1388 printf("status=0 error=\"Unable to parse lvs\"\n");
1392 Mmsg(cmd, "%s_%s", arg->device, arg->name);
1393 ts = get_lv_value(cmd, "Time");
1395 Dmsg(10, "Unable to find snapshot in lvs output\n");
1396 bstrftimes(buf, sizeof(buf), time(NULL));
1399 Dmsg(10, "status=1 volume=\"%s_%s\" createdate=\"%s\" type=lvm\n",
1400 arg->device, arg->name, ts);
1401 printf("status=1 volume=\"%s_%s\" createdate=\"%s\" type=lvm\n",
1402 arg->device, arg->name, ts);
1407 if (!snapshot::del()) {
1410 Mmsg(cmd, "%slvremove -f \"%s\"",
1411 arg->sudo, arg->volume);
1413 if (run_program(cmd, 60, errmsg)) {
1414 Dmsg(10, "Unable to delete snapshot %s %s\n", arg->name, errmsg);
1415 strip_quotes(errmsg);
1416 printf("status=0 error=\"Unable to delete snapshot %s\"\n", errmsg);
1420 printf("status=1\n");
1425 if (!snapshot::check()) {
1429 for (int i = 0; vgs_header[i].name ; i++) {
1430 if (vgs_header[i].pos == -1) {
1431 printf("status=0 error=\"Unable to use output of vgs command."
1432 " %s is missing.\"\n",
1433 vgs_header[i].name);
1439 for (int i = 0; lvs_header[i].name ; i++) {
1440 if (lvs_header[i].pos == -1) {
1441 printf("status=0 error=\"Unable to use output of lvs command."
1442 " %s is missing.\"\n",
1443 lvs_header[i].name);
1450 void strip_double_slashes(char *fname)
1454 p = strpbrk(p, "/\\");
1456 if (IsPathSeparator(p[1])) {
1465 if (!snapshot::mount()) {
1469 path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
1471 if (!makedir(path)) {
1472 printf("status=0 error=\"Unable to create mount point %s errno=%d\"\n",
1477 Mmsg(cmd, "%smount -o ro \"%s\" \"%s\"", arg->sudo, arg->volume, path);
1478 if (run_program(cmd, 60, errmsg) != 0) {
1479 Dmsg(10, "Unable to mount volume. ERR=%s\n", errmsg);
1480 strip_quotes(errmsg);
1481 printf("status=0 error=\"Unable to mount the device %s\"\n", errmsg);
1485 Dmsg(10, "status=1 snapmountpoint=\"%s\" snapdirectory=\"%s/%s\"\n",
1486 path, arg->mountpoint, arg->snapdir);
1487 printf("status=1 snapmountpoint=\"%s\" snapdirectory=\"%s/%s\"\n",
1488 path, arg->mountpoint, arg->snapdir);
1493 int ret, retry = arg->retry;
1495 if (!snapshot::unmount()) {
1499 Mmsg(cmd, "%sumount \"%s\"", arg->sudo, arg->snapmountpoint);
1501 ret = run_program(cmd, 60, errmsg);
1503 Dmsg(10, "Unable to unmount the directory. ERR=%s\n", errmsg);
1506 } while (ret != 0 && retry-- > 0);
1509 Dmsg(10, "Unable to mount volume. ERR=%s\n", errmsg);
1510 strip_quotes(errmsg);
1511 printf("status=0 error=\"Unable to umount the device %s\"\n", errmsg);
1517 Dmsg(10, "Trying to delete mountpoint %s\n", arg->snapmountpoint);
1518 if ((ret = rmdir(arg->snapmountpoint)) != 0) {
1521 } while (retry-- > 0 && ret != 0);
1525 Dmsg(10, "Unable to delete mountpoint after unmount\n");
1526 printf("error=\"Unable to delete mountpoint after unmount errno=%s\"",
1527 be.bstrerror(errno));
1529 printf(" status=1\n");
1533 /* TODO: Here we need to check LVM settings */
1538 if (!snapshot::support()) {
1545 elt = get_lv(arg->device);
1548 Dmsg(10, "Not detected as LVM\n");
1549 printf("status=0 error=\"Not detected as LVM\"\n");
1552 mp = get_value_pos(lvs_header ,"Path");
1553 printf("status=1 device=\"%s\" type=lvm\n", elt[mp]);
1557 /* count the number of column in the output */
1558 int count_col(char *l, char sep) {
1560 for (char *p = l ; *p ; p++) {
1568 /* Decode the Attr field */
1569 int decode_attr(char *l) {
1571 * Volume type: (m)irrored, (M)irrored without initial sync,
1572 * (o)rigin, (O)rigin with merging snapshot, (r)aid, (R)aid
1573 * without initial sync, (s)napshot, merging (S)napshot,
1574 * (p)vmove, (v)irtual, mirror or raid (i)mage, mirror or raid
1575 * (I)mage out-of-sync, mirror (l)og device, under (c)onversion,
1576 * thin (V)olume, (t)hin pool, (T)hin pool data, raid or thin
1583 bool parse_vgs_output() {
1584 Mmsg(cmd, "%svgs -o vg_all --separator=; --units b --nosuffix", arg->sudo);
1586 free_header(vgs, vgs_nbelt);
1589 vgs = New(alist(10, not_owned_by_alist));
1590 if (!parse_output(cmd, vgs, &vgs_nbelt, vgs_header)) {
1596 bool parse_lvs_output() {
1597 Mmsg(cmd, "%slvs -o lv_all --separator=; --units b --nosuffix", arg->sudo);
1599 free_header(lvs, lvs_nbelt);
1602 lvs = New(alist(10, not_owned_by_alist));
1603 if (!parse_output(cmd, lvs, &lvs_nbelt, lvs_header)) {
1609 /* Function to parse LVM command output */
1610 bool parse_output(char *cmd, alist *ret, int *ret_nbelt, Header *hdr) {
1615 char buf[2048]; /* Size for a single line */
1616 bool header_done=false;
1618 if (run_program_full_output(cmd, 60, errmsg)) {
1619 strip_quotes(errmsg);
1620 Dmsg(10, "Unable to run lvs. ERR=%s\n", errmsg);
1624 char **current = NULL;
1626 for (p = errmsg; *p ; p++) {
1627 if (*p == ';') { /* We have a separator, handle current value */
1630 nbelt++; /* Keep the number of element in the line */
1632 /* Find if we need this value, and where to store it */
1633 for (int j=0; hdr[j].name ; j++) {
1634 if (strcasecmp(buf, hdr[j].name) == 0) {
1642 /* First item, need to allocate new array */
1643 current = (char **)malloc(nbelt * sizeof(char *) + 1);
1644 memset(current, 0, nbelt * sizeof(char *) + 1);
1645 ret->append(current);
1647 /* Keep the current value */
1648 current[pos] = bstrdup(buf);
1652 } else if (*p == '\n') {
1653 /* We deal with a new line, so the header is done (if in) */
1658 } else if (i < (int)sizeof(buf)) {
1662 Dmsg(10, "Output too big !!! %s\n", errmsg);
1671 char **elt, **elt2 = NULL;
1672 const char *err = NULL;
1673 int p_attr, p_path, p_origin, p_time, p_size;
1675 int fnl, pnl, status;
1677 if (!snapshot::list()) {
1681 if (!parse_lvs_output()) {
1685 p_attr = get_value_pos(lvs_header, "Attr");
1686 p_path = get_value_pos(lvs_header, "Path");
1687 p_time = get_value_pos(lvs_header, "Time");
1688 p_size = get_value_pos(lvs_header, "Snap%");
1689 p_origin = get_value_pos(lvs_header, "Origin");
1691 if (p_time < 0 || p_origin < 0) {
1692 printf("status=1 error=\"Unable to get snapshot Origin from lvs command\"\n");
1696 p = get_pool_memory(PM_FNAME);
1697 f = get_pool_memory(PM_FNAME);
1698 d = get_pool_memory(PM_FNAME);
1700 elt2 = get_lv(arg->device);
1702 /* TODO: We need to get the device name from the mount point */
1703 foreach_alist(elt, lvs) {
1704 char *attr = elt[p_attr];
1706 if (attr[0] == 's') {
1707 if (attr[4] == 'I') {
1708 /* 5 State: (a)ctive, (s)uspended, (I)nvalid snapshot, invalid (S)uspended
1709 * snapshot, snapshot (m)erge failed, suspended snapshot (M)erge
1710 * failed, mapped (d)evice present without tables, mapped device
1711 * present with (i)nactive table, (X) unknown
1714 err = "Invalid snapshot";
1720 split_path_and_filename(elt[p_path], &p, &pnl, &f, &fnl);
1721 Mmsg(d, "%s%s", p, elt[p_origin]);
1723 if ((!arg->device || strcmp(arg->device, d) == 0) ||
1724 (elt2 && strcmp(elt2[p_path], d) == 0))
1726 /* On LVM, the name is LV_SnapshotName, we can strip the LV_ if we find it */
1727 Mmsg(p, "%s_", d); /* /dev/mapper/vg_ssd/test_ */
1728 if (strncmp(p, elt[p_path], strlen(p)) == 0) {
1729 pm_strcpy(f, elt[p_path] + strlen(p));/* test_MySnapshot_2020.. => MySnapshot_2020 */
1732 printf("volume=\"%s\" device=\"%s\" name=\"%s\" createdate=\"%s\" size=\"%s\" "
1733 "status=%d error=\"%s\" type=lvm\n",
1734 elt[p_path], d, f, elt[p_time], elt[p_size], status, err);
1738 free_pool_memory(p);
1739 free_pool_memory(f);
1740 free_pool_memory(d);
1745 /* The simulator is using a simple symlink */
1746 class simulator: public snapshot {
1748 simulator(arguments *arg): snapshot(arg, "simulator") {};
1751 if (!snapshot::mount()) {
1754 split_path_and_filename(arg->volume, &path, &pnl, &fname, &fnl);
1755 printf("status=1 snapmountpoint=\"%s\" snapdirectory=\"%s\"\n",
1761 printf("status=1\n");
1766 if (!snapshot::support()) {
1769 if (access(arg->mountpoint, W_OK) != 0) {
1770 printf("status=0 device=\"%s\" type=simulator "
1771 "error=\"Unable to access mountpoint\"\n",
1775 printf("status=1 device=\"%s\" type=simulator\n", arg->mountpoint);
1783 if (!snapshot::create()) {
1786 Mmsg(path, "%s/%s", arg->mountpoint, arg->snapdir);
1789 Mmsg(cmd, "ln -vsf \"%s\" \"%s\"", arg->mountpoint, path);
1790 if (run_program(cmd, 60, errmsg)) {
1791 Dmsg(10, "Unable to create symlink. ERR=%s\n", errmsg);
1792 strip_quotes(errmsg);
1793 printf("status=%d error=\"Unable to umount the device %s\"\n",
1797 printf("status=1 volume=\"%s\" createtdate=%s type=simulator\n",
1798 path, edit_uint64(now, ed1));
1804 if (!snapshot::del()) {
1807 ret = unlink(arg->volume);
1808 printf("status=%d\n", (ret == 0)? 1 : 0);
1813 snapshot *detect_snapshot_backend(arguments *arg)
1816 if (strcasecmp(arg->type, "btrfs") == 0) {
1817 return new btrfs(arg);
1819 } else if (strcasecmp(arg->type, "lvm") == 0) {
1820 return new lvm(arg);
1822 } else if (strcasecmp(arg->type, "simulator") == 0) {
1823 return new simulator(arg);
1825 } else if (strcasecmp(arg->type, "zfs") == 0) {
1826 return new zfs(arg);
1830 if (strcasecmp(arg->fstype, "btrfs") == 0) {
1831 return new btrfs(arg);
1833 } else if (strcasecmp(arg->fstype, "tmpfs") == 0) {
1834 return new simulator(arg);
1836 /* TODO: Need to find something smarter here */
1837 } else if (strcasecmp(arg->fstype, "ext4") == 0) {
1838 return new lvm(arg);
1840 } else if (strcasecmp(arg->fstype, "xfs") == 0) {
1841 return new lvm(arg);
1843 } else if (strcasecmp(arg->fstype, "ext3") == 0) {
1844 return new lvm(arg);
1846 } else if (strcasecmp(arg->fstype, "zfs") == 0 ||
1847 strcasecmp(arg->fstype, "fuse.zfs") == 0)
1849 return new zfs(arg);
1852 Dmsg(10, "Backend not found\n");
1856 /* defined in jcr.c */
1857 void create_jcr_key();
1859 int main(int argc, char **argv)
1867 set_trace_file("/dev/null");
1868 setlocale(LC_ALL, "");
1869 setenv("LANG", "C", true);
1870 bindtextdomain("bacula", LOCALEDIR);
1871 textdomain("bacula");
1875 my_name_is(argc, argv, "bsnapshot");
1878 while ((ch = getopt(argc, argv, "?d:vc:so:V:T:t")) != -1) {
1880 case 'd': /* set debug level */
1881 debug_level = atoi(optarg);
1882 if (debug_level <= 0) {
1891 case 's': /* use sudo */
1895 case 'c': /* config file */
1896 pm_strcpy(arg.config_file, optarg);
1897 if (stat(optarg, &sp) < 0) {
1898 Pmsg(000, "Unable to access %s. ERR=%s\n",optarg, strerror(errno));
1899 usage(_("Unable to open -p argument for reading"));
1903 case 'o': /* where to send the debug output */
1904 set_trace_file(optarg);
1908 arg.action = (char *)"check";
1911 case 'V': /* set volume name */
1912 arg.volume = optarg;
1915 case 'T': /* device type */
1926 if (!arg.validate()) {
1931 Dmsg(10, "disabled from config file\n");
1935 snap = detect_snapshot_backend(&arg);
1938 printf("status=0 error=\"Unable to detect snapshot backend\"");
1944 if (strcasecmp(arg.action, "mount") == 0) {
1945 ret = snap->mount();
1947 } else if (strcasecmp(arg.action, "support") == 0) {
1948 ret = snap->support();
1950 } else if (strcasecmp(arg.action, "create") == 0) {
1951 ret = snap->create();
1953 } else if (strcasecmp(arg.action, "delete") == 0) {
1956 } else if (strcasecmp(arg.action, "subvolumes") == 0) {
1957 ret = snap->subvolumes();
1959 } else if (strcasecmp(arg.action, "list") == 0) {
1962 } else if (strcasecmp(arg.action, "check") == 0) {
1963 ret = snap->check();
1965 } else if (strcasecmp(arg.action, "unmount") == 0) {
1966 ret = snap->unmount();
1971 close_memory_pool();
1972 lmgr_cleanup_main();
1974 Dmsg(10, "exit code = %d\n", (ret == 1) ? 0 : 1);
1975 return (ret == 1)? 0 : 1;