1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
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.
8 
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.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 
20 #include "bacula.h"
21 #include "lib/ini.h"
22 
23 #ifdef HAVE_SUN_OS
24 #include <sys/types.h>
25 #endif
26 
27 #ifdef MAJOR_IN_MKDEV
28 #include <sys/mkdev.h>
29 #elif defined MAJOR_IN_SYSMACROS
30 #include <sys/sysmacros.h>
31 #endif
32 
33 #define Dmsg(level,  ...) do { \
34    if (level <= debug_level) { \
35       fprintf(debug, "%s:%d ", __FILE__ , __LINE__);    \
36       fprintf(debug, __VA_ARGS__ );                          \
37    }  \
38  } while (0)
39 
40 #define Pmsg(level,  ...) do { \
41    if (level <= debug_level) { \
42       fprintf(stderr, "%s:%d ", __FILE__ , __LINE__ ); \
43       fprintf(stderr, __VA_ARGS__ );                   \
44    }  \
45  } while (0)
46 
47 #define BSNAPSHOT_CONF SYSCONFDIR "/bsnapshot.conf"
48 
49 static FILE *debug = NULL;
50 
51 static void usage(const char *msg=NULL)
52 {
53    if (msg) {
54       fprintf(stderr, _("ERROR %s\n\n"), msg);
55    }
56 
57    fprintf(stderr, _(
58            "Bacula %s (%s)\n\n"
59            "Usage: bsnapshot\n"
60            "   -d level     Set debug level\n"
61            "   -v           Verbose\n"
62            "   -s           Use sudo\n"
63            "   -o logfile   send debug to logfile\n"
64            "   -V volume    volume\n"
65            "   -T type      volume type\n"
66            "   -t           check compatibility\n"
67            "   -c           specify configuration file\n"
68            "\n"), VERSION, LSMDATE);
69    exit(2);
70 }
71 
72 static const char *Months[] = {
73    NULL,
74    "Jan",
75    "Feb",
76    "Mar",
77    "Apr",
78    "Mai",
79    "Jun",
80    "Jul",
81    "Aug",
82    "Sep",
83    "Oct",
84    "Nov",
85    "Dec"
86 };
87 
88 /* Skip leading slash(es) */
makedir(char * path)89 static bool makedir(char *path)
90 {
91    char *p = path;
92 
93    while (IsPathSeparator(*p)) {
94       p++;
95    }
96    while ((p = first_path_separator(p))) {
97       char save_p;
98       save_p = *p;
99       *p = 0;
100       mkdir(path, 0700);
101       *p = save_p;
102       while (IsPathSeparator(*p)) {
103          p++;
104       }
105    }
106    /* If not having a ending / */
107    if (!IsPathSeparator(path[strlen(path) - 1])) {
108       mkdir(path, 0700);
109    }
110    return true;
111 }
112 
113 /* Strip trailing junk and " */
strip_quotes(char * str)114 void strip_quotes(char *str)
115 {
116    strip_trailing_junk(str);
117    for(char *p = str; *p ; p++) {
118       if (*p == '"') {
119          *p = ' ';
120       }
121    }
122 }
123 
set_trace_file(const char * path)124 static void set_trace_file(const char *path)
125 {
126    char dt[MAX_TIME_LENGTH];
127    if (debug && debug != stderr) {
128       fclose(debug);
129    }
130    debug = fopen(path, "a");
131    if (!debug) {
132       debug = stderr;
133    } else {
134       Dmsg(10, "Starting bsnapshot %s\n",
135            bstrftime(dt, MAX_TIME_LENGTH, time(NULL)));
136    }
137 }
138 
139 /* Small function to avoid double // in path name */
path_concat(POOLMEM * & dest,const char * path1,const char * path2,const char * path3)140 static  void path_concat(POOLMEM *&dest, const char *path1, const char *path2, const char *path3) {
141    int last;
142 
143    last = pm_strcpy(dest, path1);
144    last = MAX(last - 1, 0);
145 
146    /* Check if the last char of dest is / and the first of path2 is / */
147    if (dest[last] == '/') {
148       if (path2[0] == '/') {
149          dest[last] = 0;
150       }
151    } else {
152       if (path2[0] != '/') {
153          pm_strcat(dest, "/");
154       }
155    }
156 
157    last = pm_strcat(dest, path2);
158    last = MAX(last - 1, 0);
159 
160    if (path3) {
161       if (dest[last] == '/') {
162          if (path3[0] == '/') {
163             dest[last] = 0;
164          }
165       } else {
166          if (path3[0] != '/') {
167             pm_strcat(dest, "/");
168          }
169       }
170       pm_strcat(dest, path3);
171    }
172 }
173 
174 static struct ini_items bsnap_cfg[] = {
175  // name                handler          comment  required   default
176  { "trace",             ini_store_str,      "",      0,        NULL},
177  { "debug",             ini_store_int32,    "",      0,        NULL},
178  { "sudo",              ini_store_bool,     "",      0,        NULL},
179  { "disabled",          ini_store_bool,     "",      0,        "no"},
180  { "retry",             ini_store_int32,    "",      0,        "3"},
181  { "lvm_snapshot_size", ini_store_alist_str,"",      0,        NULL},
182  { "skip_volume",       ini_store_alist_str,"",      0,        NULL},
183  { "snapshot_dir",      ini_store_str,      "",      0,        NULL},
184  { "fail_job_on_error", ini_store_bool,     "",      0,        "yes"},
185  { NULL,                NULL,             NULL,      0,        NULL}
186 };
187 
188 class arguments {
189 public:
190    char *action;                /* list, create, delete... */
191    char *volume;                /* snapshot device */
192    char *device;                /* original device name */
193    char *name;                  /* snapshot name */
194    char *mountpoint;            /* device mountpoint */
195    char *snapmountpoint;        /* snapshot mountpoint */
196    char *type;                  /* snapshot type */
197    char *fstype;                /* filesystem type */
198    const char *snapdir;         /* .snapshot */
199    const char *sudo;            /* prepend sudo to commands */
200    int  verbose;
201    int  retry;                  /* retry some operations */
202    bool disabled;               /* disabled by config file */
203    bool fail_job_on_error;      /* Fail job on snapshot error */
204    ConfigFile ini;              /* Configuration file */
205    POOL_MEM config_file;        /* Path to a config file */
206 
arguments()207    arguments():
208       action(getenv("SNAPSHOT_ACTION")),
209       volume(getenv("SNAPSHOT_VOLUME")),
210       device(getenv("SNAPSHOT_DEVICE")),
211       name(  getenv("SNAPSHOT_NAME")),
212       mountpoint(getenv("SNAPSHOT_MOUNTPOINT")),
213       snapmountpoint(getenv("SNAPSHOT_SNAPMOUNTPOINT")),
214       type(  getenv("SNAPSHOT_TYPE")),
215       fstype(getenv("SNAPSHOT_FSTYPE")),
216       snapdir(".snapshots"),
217       sudo(""),
218       verbose(0),
219       retry(3),
220       disabled(false),
221       fail_job_on_error(true)
222    {
223       struct stat sp;
224       ini.register_items(bsnap_cfg, sizeof(struct ini_items));
225 
226       if (stat(BSNAPSHOT_CONF, &sp) == 0) {
227          Dmsg(10, "conf=%s\n", BSNAPSHOT_CONF);
228          pm_strcpy(config_file, BSNAPSHOT_CONF);
229       }
230    };
231 
~arguments()232    ~arguments() {
233    };
234 
validate()235    bool validate() {
236       int pos;
237       if (!action) {
238          return false;
239       }
240       if (strcmp(config_file.c_str(), "") != 0) {
241          Dmsg(10, "Reading configuration from %s\n", config_file.c_str());
242          if (!ini.parse(config_file.c_str())) {
243             printf("status=1 error=\"Unable to parse %s\"\n",
244                    config_file.c_str());
245             return false;
246          }
247          pos = ini.get_item("debug");
248          if (ini.items[pos].found && debug_level == 0) {
249             debug_level = ini.items[pos].val.int32val;
250          }
251          pos = ini.get_item("trace");
252          if (ini.items[pos].found) {
253             set_trace_file(ini.items[pos].val.strval);
254          }
255          pos = ini.get_item("sudo");
256          if (ini.items[pos].found && ini.items[pos].val.boolval) {
257             sudo = "sudo ";
258          }
259          pos = ini.get_item("snapshot_dir");
260          if (ini.items[pos].found) {
261             snapdir = ini.items[pos].val.strval;
262          }
263          pos = ini.get_item("retry");
264          if (ini.items[pos].found) {
265             retry = ini.items[pos].val.int32val;
266          }
267          pos = ini.get_item("disabled");
268          if (ini.items[pos].found) {
269             disabled = ini.items[pos].val.boolval;
270          }
271          pos = ini.get_item("fail_job_on_error");
272          if (ini.items[pos].found) {
273             fail_job_on_error = ini.items[pos].val.boolval;
274          }
275       }
276       return true;
277    };
278 };
279 
280 class snapshot {
281 public:
282    const char *type;            /* snapshot type, btrfs, zfs, etc.. */
283    POOLMEM    *cmd;             /* buffer to edit a command */
284    POOLMEM    *path;            /* buffer to edit volume path */
285    POOLMEM    *fname;           /* used for split_path_and_filename */
286    POOLMEM    *errmsg;          /* buffer to edit error message */
287    arguments  *arg;             /* program argument */
288    int         pnl;             /* path length */
289    int         fnl;             /* fname length */
290 
snapshot(arguments * a,const char * t)291    snapshot(arguments *a, const char *t):
292       type(t),
293       cmd(get_pool_memory(PM_NAME)),
294       path(get_pool_memory(PM_NAME)),
295       fname(get_pool_memory(PM_NAME)),
296       errmsg(get_pool_memory(PM_NAME)),
297       arg(a),
298       pnl(0),
299       fnl(0)
300    {
301    };
302 
~snapshot()303    virtual ~snapshot() {
304       free_pool_memory(cmd);
305       free_pool_memory(path);
306       free_pool_memory(fname);
307       free_pool_memory(errmsg);
308    };
309 
310    /* Basically, we check parameters here that are
311     * common to all backends
312     */
mount()313    virtual int mount() {
314       Dmsg(10, "[%s] Doing mount command\n", type);
315       if (!arg->volume || !arg->name || !arg->device || !arg->mountpoint) {
316          Dmsg(10, "volume=%s name=%s device=%s mountpoint=%s\n",
317               NPRT(arg->volume), NPRT(arg->name),
318               NPRT(arg->device), NPRT(arg->mountpoint));
319          return 0;
320       }
321       return 1;
322    };
323 
unmount()324    virtual int unmount() {
325       Dmsg(10, "[%s] Doing unmount command on %s\n", type,
326            NPRT(arg->snapmountpoint));
327       if (!arg->snapmountpoint) {
328          Dmsg(10, "snapmountpoint=%s\n", NPRT(arg->snapmountpoint));
329          return 0;
330       }
331       return 1;
332    };
333 
support()334    virtual int support() {
335       Dmsg(10, "[%s] Doing support on %s (%s)\n", type, NPRT(arg->mountpoint),
336            NPRT(arg->device));
337       if (!arg->fstype || !arg->mountpoint || !arg->device) {
338          Dmsg(10, "fstype=%s mountpoint=%s device=%s\n",
339               NPRT(arg->fstype), NPRT(arg->mountpoint), NPRT(arg->device));
340          return 0;
341       }
342       return 1;
343    };
344 
check()345    virtual int check() {
346       Dmsg(10, "[%s] Doing check on %s\n", type, NPRT(arg->mountpoint));
347       if (!arg->mountpoint) {
348          Dmsg(10, "mountpoint=%s\n", NPRT(arg->mountpoint));
349          return 0;
350       }
351       return 1;
352    };
353 
create()354    virtual int create() {
355       Dmsg(10, "[%s] Doing create %s\n", type, NPRT(arg->mountpoint));
356       if (!arg->mountpoint || !arg->name || !arg->device) {
357          Dmsg(10, "mountpoint=%s name=%s device=%s\n",
358               NPRT(arg->mountpoint), NPRT(arg->name), NPRT(arg->device));
359          return 0;
360       }
361       return 1;
362    };
363 
del()364    virtual int del() {
365       Dmsg(10, "[%s] Doing del %s\n", type, NPRT(arg->volume));
366       if (!arg->volume || !arg->name) {
367          Dmsg(10, "volume=%s name=%s\n",
368               NPRT(arg->volume), NPRT(arg->name));
369          return 0;
370       }
371       return 1;
372    };
373 
list()374    virtual int list() {
375       Dmsg(10, "[%s] Doing list on %s\n", type, NPRT(arg->device));
376       if (!arg->type || !arg->device || !arg->mountpoint) {
377          return 0;
378       }
379       return 1;
380    };
381 
subvolumes()382    virtual int subvolumes() {
383       Dmsg(10, "[%s] Doing subvolumes %s\n", type, NPRT(arg->mountpoint));
384       if (!arg->fstype || !arg->device || !arg->mountpoint) {
385          return 0;
386       }
387       return 1;
388    };
389 
390    /* Function used in create() to know if we mark the error as FATAL */
get_error_code()391    int get_error_code() {
392       Dmsg1(0, "get_error_code = %d\n", (int)arg->fail_job_on_error);
393       /* 1 is OK */
394       if (arg->fail_job_on_error) {
395          return 0;           /* Fatal */
396       }
397       return 2;              /* Error */
398    };
399 };
400 
401 /* Structure used to sort subvolumes with btrfs backend */
402 struct vols {
403    rblink  link;
404    int64_t id;
405    int     count;
406    char    uuid[MAX_NAME_LENGTH];
407    char    puuid[MAX_NAME_LENGTH];
408    char    otime[MAX_NAME_LENGTH];
409    char    path[1];
410 };
411 
vols_compare_id(void * item1,void * item2)412 int vols_compare_id(void *item1, void *item2)
413 {
414    vols *vol1 = (vols *) item1;
415    vols *vol2 = (vols *) item2;
416 
417    if (vol1->id > vol2->id) {
418       return 1;
419 
420    } else if (vol1->id < vol2->id) {
421       return -1;
422 
423    } else {
424       return 0;
425    }
426 }
427 
vols_compare_uuid(void * item1,void * item2)428 int vols_compare_uuid(void *item1, void *item2)
429 {
430    vols *vol1 = (vols *) item1;
431    vols *vol2 = (vols *) item2;
432 
433    return strcmp(vol1->uuid, vol2->uuid);
434 }
435 
436 /* btrfs backend */
437 class btrfs: public snapshot {
438 public:
btrfs(arguments * arg)439    btrfs(arguments *arg): snapshot(arg, "btrfs")  {};
440 
441    /* With BTRFS, the volume is already mounted */
mount()442    int mount() {
443       if (!snapshot::mount()) {
444          return 0;
445       }
446       split_path_and_filename(arg->volume, &path, &pnl, &fname, &fnl);
447       fprintf(stdout, "status=1 snapmountpoint=\"%s\" snapdirectory=\"%s\"\n",
448               arg->volume, path);
449       return 1;
450    };
451 
unmount()452    int unmount() {
453       if (!snapshot::unmount()) {
454          return 0;
455       }
456       printf("status=1\n");
457       return 1;
458    };
459 
support()460    int support() {
461       if (!snapshot::support()) {
462          return 0;
463       }
464       /* If the fstype is btrfs, snapshots are supported */
465 /*
466       Mmsg(cmd, "%sbtrfs filesystem label \"%s\"", arg->sudo, arg->mountpoint);
467       if (run_program(cmd, 60, errmsg)) {
468          printf("status=0 type=btrfs\n");
469          return 0;
470       }
471       Dmsg(0, "output=%s\n", errmsg);
472 */
473       printf("status=1 device=\"%s\" type=btrfs\n", arg->mountpoint);
474       return 1;
475    };
476 
check()477    int check() {
478       if (!snapshot::check()) {
479          return 0;
480       }
481       return 1;
482    };
483 
create()484    int create() {
485       utime_t createdate = 0;
486       char ed1[50];
487       if (!snapshot::create()) {
488          return 0;
489       }
490 
491       Mmsg(path, "%s/%s", arg->mountpoint, arg->snapdir);
492       if (!makedir(path)) {
493          printf("status=%d error=\"Unable to create mountpoint directory %s errno=%d\n",
494                 get_error_code(),
495                 arg->mountpoint, errno);
496          return 0;
497       }
498 
499       Dmsg(10, "mountpoint=%s snapdir=%s name=%s\n", arg->mountpoint, arg->snapdir, arg->name);
500       path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
501       Dmsg(10, "path=%s\n", path);
502 
503       /* Create the actual btrfs snapshot */
504       Mmsg(cmd, "%sbtrfs subvolume snapshot -r \"%s\" \"%s\"",
505            arg->sudo, arg->mountpoint, path);
506 
507       if (run_program(cmd, 60, errmsg)) {
508          Dmsg(10, "Unable to create snapshot %s %s\n", arg->mountpoint, errmsg);
509          strip_quotes(errmsg);
510          printf("status=%d error=\"Unable to create snapshot %s\"\n",
511                 get_error_code(),
512                 errmsg);
513          return 0;
514       }
515 
516       /* On SLES12 btrfs 3.16, commands on "/" returns "doesn't belong to btrfs mount point" */
517       Mmsg(cmd, "%sbtrfs subvolume show \"%s\"", arg->sudo, path);
518       if (run_program_full_output(cmd, 60, errmsg)) {
519          Dmsg(10, "Unable to display snapshot stats %s %s\n", arg->mountpoint, errmsg);
520 
521       } else {
522          /* TODO: Check that btrfs subvolume show is reporting "Creation time:" */
523          char *p = strstr(errmsg, "Creation time:");
524          if (p) {
525             p += strlen("Creation time:");
526             skip_spaces(&p);
527             createdate = str_to_utime(p);
528 
529          } else {
530             Dmsg(10, "Unable to find Creation time on %s %s\n", arg->mountpoint, errmsg);
531          }
532       }
533 
534       if (!createdate) {
535          createdate = time(NULL);
536       }
537       printf("status=1 volume=\"%s\" createtdate=%s type=btrfs\n",
538              path, edit_uint64(createdate, ed1));
539       return 1;
540    };
541 
del()542    int del() {
543       if (!snapshot::del()) {
544          return 0;
545       }
546 
547       Mmsg(cmd, "%sbtrfs subvolume delete \"%s\"", arg->sudo, arg->volume);
548       if (run_program(cmd, 300, errmsg)) {
549          Dmsg(10, "Unable to delete snapshot %s\n", errmsg);
550          strip_quotes(errmsg);
551          printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
552          return 0;
553       }
554       printf("status=1\n");
555       return 1;
556    };
557 
558    /* btrfs subvolume list -u -q -s /tmp/regress/btrfs
559     * ID 259 gen 52 top level 5 parent_uuid - uuid baf4b5d7-28d0-9b4a-856e-36e6fd4fbc96 path .snapshots/aaa
560     */
list()561    int list() {
562       char *p, *p2, *end, *path;
563       char  id[50], day[50], hour[50];
564       struct vols *v = NULL, *v2;
565       rblist *lst;
566 
567       if (!snapshot::list()) {
568          return 0;
569       }
570       Mmsg(cmd, "%sbtrfs subvolume list -u -q -o -s \"%s\"", arg->sudo, arg->mountpoint);
571       if (run_program_full_output(cmd, 300, errmsg)) {
572          Dmsg(10, "Unable to list snapshot %s\n", errmsg);
573          strip_quotes(errmsg);
574          printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
575          return 0;
576       }
577 
578       lst = New(rblist(v, &v->link));
579 
580       /* ID 259 gen 52 top level 5 parent_uuid - uuid baf4b5d7-28d0-9b4a-856e-36e6fd4fbc96 path .snapshots/aaa */
581       for (p = errmsg; p && *p ;) {
582          Dmsg(20, "getting subvolumes from %s", p);
583 
584          /* Replace final \n by \0 to have strstr() happy */
585          end = strchr(p, '\n');
586 
587          /* If end=NULL, we are at the end of the buffer (without trailing \n) */
588          if (end) {
589             *end = 0;
590          }
591 
592          /* Each line is supposed to start with "ID", and end with "path" */
593          bool ok = false;
594          if (sscanf(p, "ID %49s ", id) == 1) {              /* We found ID, look for path */
595             p2 = strstr(p, "path ");
596             if (p2) {
597                path = p2 + strlen("path ");
598                v = (struct vols*) malloc(sizeof (vols) + strlen(path) + 1);
599                *v->otime = *v->uuid = *v->puuid = 0;
600                v->id = str_to_int64(id);
601                v->count = 0;
602                strcpy(v->path, path);
603 
604                p2 = strstr(p, "otime");
605                if (p2 && sscanf(p2, "otime %49s %49s", day, hour) == 2) {
606                   bsnprintf(v->otime, sizeof(v->otime), "%s %s", day, hour);
607                }
608 
609                p2 = strstr(p, "parent_uuid ");
610                if (p2 && sscanf(p2, "parent_uuid %127s", v->puuid) == 1) {
611 
612                   p2 = strstr(p, " uuid ");
613                   if (p2 && sscanf(p2, " uuid %127s", v->uuid) == 1) {
614 
615                      v2 = (struct vols *)lst->insert(v, vols_compare_uuid);
616                      if (v2 != v) {
617                         v2->count++;
618                         free(v);
619                      }
620                      ok = true;
621                      /* Replace final \n by \0 to have strstr() happy */
622                      Dmsg(10, "puuid=%s uuid=%s path=%s\n", v2->puuid, v2->uuid, v2->path);
623                   }
624                }
625             }
626          }
627          if (!ok) {
628             Dmsg(10, "Unable to decode \"%s\" line\n", p);
629          }
630          if (end) {
631             *end = '\n';
632             end++;
633          }
634          /* If end==NULL, we stop */
635          p = end;
636       }
637 
638       foreach_rblist(v, lst) {
639          char *name = v->path;
640          int   len = strlen(arg->snapdir);
641          if ((p = strstr(v->path, arg->snapdir))) {
642             name = p + len + ((arg->snapdir[len-1] == '/') ? 0 : 1);
643          }
644          printf("volume=\"%s%s%s\" name=\"%s\" device=\"%s\" createdate=\"%s\" type=\"btrfs\"\n",
645                 arg->mountpoint,
646                 arg->mountpoint[strlen(arg->mountpoint) - 1] == '/' ? "": "/",
647                 v->path,
648                 name,
649                 arg->mountpoint,
650                 v->otime
651             );
652       }
653 
654       delete lst;
655       return 1;
656    };
657 
scan_subvolumes(char * buf,rblist * lst)658    void scan_subvolumes(char *buf, rblist *lst) {
659       char *p, *end;
660       char  id[50];
661       bool  ok;
662       struct vols *elt1 = NULL, *elt2 = NULL;
663 
664       /* btrfs subvolume list /var/lib/pacman/
665        * ID 349 gen 383 top level 5 path test
666        * ID 354 gen 391 cgen 391 top level 5 otime 2014-11-05 17:49:07 path .snapshots/aa
667        */
668       for (p = buf; p && *p ;) {
669          Dmsg(20, "getting subvolumes from %s", p);
670 
671          /* Replace final \n by \0 to have strstr() happy */
672          end = strchr(p, '\n');
673          /* If end=NULL, we are at the end of the buffer (without trailing \n) */
674          if (end) {
675             *end = 0;
676          }
677 
678          /* Each line is supposed to start with "ID", and end with "path" */
679          ok = (sscanf(p, "ID %49s ", id) == 1);
680          if (ok) {              /* We found ID, look for path */
681             p = strstr(p, "path ");
682             if (p) {
683                p += strlen("path ");
684 
685                elt1 = (struct vols *) malloc(sizeof(struct vols) + strlen(p) + 1);
686                elt1->id = str_to_int64(id);
687                elt1->count = 0;
688                strcpy(elt1->path, p);
689                Dmsg(10, "Found path %s for id %s\n", elt1->path, id);
690                elt2 = (struct vols *)lst->insert(elt1, vols_compare_id);
691                if (elt2 != elt1) {
692                   elt2->count++;
693                   free(elt1);
694                }
695             } else {
696                Dmsg(10, "Unable to find the path in this line\n");
697             }
698 
699          } else {
700             Dmsg(10, "Unable to decode %s line\n", p);
701          }
702          if (end) {
703             *end = '\n';
704             end++;
705          }
706          /* If end==NULL, we stop */
707          p = end;
708       }
709    };
710 
711    /* List subvolumes, they may not be listed by mount */
subvolumes()712    int subvolumes() {
713       rblist      *lst;
714       struct stat  sp;
715       struct vols *elt1 = NULL;
716       char   ed1[50];
717 
718       Mmsg(cmd, "%sbtrfs subvolume show \"%s\"", arg->sudo, arg->mountpoint);
719       if (run_program_full_output(cmd, 300, errmsg)) {
720          Dmsg(10, "Unable to get information %s\n", errmsg);
721          strip_quotes(errmsg);
722          printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
723          return 0;
724       }
725 
726       /* TODO: Very week way to analyse FS */
727       if (!strstr(errmsg, "is btrfs root")) {
728          printf("status=0 type=btrfs error=\"Not btrfs root fs\"\n");
729          return 0;
730       }
731 
732       Mmsg(cmd, "%sbtrfs subvolume list -s \"%s\"", arg->sudo, arg->mountpoint);
733       if (run_program_full_output(cmd, 300, errmsg)) {
734          Dmsg(10, "Unable to list snapshot snapshot %s\n", errmsg);
735          strip_quotes(errmsg);
736          printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
737          return 0;
738       }
739 
740       lst = New(rblist(elt1, &elt1->link));
741       scan_subvolumes(errmsg, lst);
742 
743       Mmsg(cmd, "%sbtrfs subvolume list \"%s\"", arg->sudo, arg->mountpoint);
744       if (run_program_full_output(cmd, 300, errmsg)) {
745          Dmsg(10, "Unable to list subvolume %s\n", errmsg);
746          strip_quotes(errmsg);
747          printf("status=0 type=btrfs error=\"%s\"\n", errmsg);
748          delete lst;
749          return 0;
750       }
751       scan_subvolumes(errmsg, lst);
752 
753       foreach_rblist(elt1, lst) {
754          if (elt1->count > 0) { /* Looks to be a snapshot, we saw two entries */
755             continue;
756          }
757 
758          path_concat(path, arg->mountpoint, elt1->path, NULL);
759 
760          if (stat(path, &sp) == 0) {
761             printf("dev=%s mountpoint=\"%s\" fstype=btrfs\n",
762                    edit_uint64(sp.st_dev, ed1), path);
763 
764          } else {
765             Dmsg(10, "Unable to stat %s (%s)\n", elt1->path, path);
766          }
767       }
768       delete lst;
769       return 1;
770    };
771 };
772 
773 /* Create pool
774  * zpool create pool /dev/device
775  * zfs create pool/eric
776  * zfs set mountpoint=/mnt test/eric
777  * zfs mount pool/eric
778  */
779 /* zfs backend */
780 class zfs: public snapshot {
781 public:
zfs(arguments * arg)782    zfs(arguments *arg): snapshot(arg, "zfs")  {
783       arg->snapdir = ".zfs/snapshot";
784    };
785 
786    /* With ZFS, the volume is already mounted
787     * but on linux https://github.com/zfsonlinux/zfs/issues/173
788     * we need to use the mount command.
789     * TODO: Adapt the code for solaris
790     */
mount()791    int mount() {
792       struct stat sp;
793 
794       if (!snapshot::mount()) {
795          return 0;
796       }
797 
798       path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
799 
800       if (stat(path, &sp) != 0) {
801          /* See if we can change the snapdir attribute */
802          Mmsg(cmd, "%szfs set snapdir=visible \"%s\"", arg->sudo, arg->device);
803          if (run_program(cmd, 60, errmsg)) {
804             Dmsg(10, "Unable to change the snapdir attribute %s %s\n", arg->device, errmsg);
805             strip_quotes(errmsg);
806             printf("status=0 error=\"Unable to mount snapshot %s\"\n", errmsg);
807             return 0;
808          }
809          if (stat(path, &sp) != 0) {
810             Dmsg(10, "Unable to get the snapdir %s %s\n", arg->snapdir, arg->device);
811             strip_quotes(errmsg);
812             printf("status=0 error=\"Unable to mount snapshot, no snapdir %s\"\n", arg->snapdir);
813             return 0;
814          }
815       }
816 #if 0                           /* On linux, this function is broken for now */
817       makedir(path);
818       Mmsg(cmd, "%smount -t %s \"%s\" \"%s\"", arg->sudo, arg->fstype, arg->volume, path);
819       if (run_program(cmd, 60, errmsg)) {
820          Dmsg(10, "Unable to create mount snapshot %s %s\n", arg->volume, errmsg);
821          strip_quotes(errmsg);
822          printf("status=0 error=\"Unable to mount snapshot %s\"\n", errmsg);
823          return 0;
824       }
825 
826 #endif
827       fprintf(stdout, "status=1 snapmountpoint=\"%s\" snapdirectory=\"%s/%s\"\n",
828               path, arg->mountpoint, arg->snapdir);
829       return 1;
830    };
831 
832    /* No need to unmount something special */
unmount()833    int unmount() {
834       printf("status=1\n");
835       return 1;
836    };
837 
support()838    int support() {
839       if (!snapshot::support()) {
840          return 0;
841       }
842       Mmsg(cmd, "%szfs list -H -o name \"%s\"", arg->sudo, arg->mountpoint);
843       if (run_program(cmd, 60, errmsg)) {
844          Dmsg(10, "Unable to get device %s %s\n", arg->mountpoint, errmsg);
845          strip_quotes(errmsg);
846          printf("status=0 error=\"Unable to get device %s\"\n", errmsg);
847          return 0;
848       }
849       strip_trailing_junk(errmsg);
850       /* If the fstype is zfs, snapshots are supported */
851       printf("status=1 device=\"%s\" type=zfs\n", errmsg);
852       return 1;
853    };
854 
create()855    int create() {
856       char ed1[50];
857 
858       if (!snapshot::create()) {
859          return 0;
860       }
861 
862       Mmsg(path, "%s@%s", arg->device, arg->name);
863 
864       /* Create the actual zfs snapshot */
865       Mmsg(cmd, "%szfs snapshot \"%s\"", arg->sudo, path);
866 
867       if (run_program(cmd, 60, errmsg)) {
868          Dmsg(10, "Unable to create snapshot %s %s\n", arg->device, errmsg);
869          strip_quotes(errmsg);
870          printf("status=%d error=\"Unable to create snapshot %s\"\n",
871                 get_error_code(),
872                 errmsg);
873          return 0;
874       }
875 
876       Mmsg(cmd, "%szfs get -p creation \"%s\"", arg->sudo, path);
877       if (run_program_full_output(cmd, 60, errmsg)) {
878          Dmsg(10, "Unable to display snapshot stats %s %s\n", arg->device, errmsg);
879          strip_quotes(errmsg);
880          printf("status=%d error=\"Unable to get snapshot info %s\"\n",
881                 get_error_code(),
882                 errmsg);
883          return 0;
884       }
885 
886       /* TODO: Check that zfs get is reporting "creation" time */
887       Mmsg(cmd, "NAME PROPERTY VALUE SOURCE\n%s creation %%s", path);
888       if (sscanf(errmsg, cmd, ed1) == 1) {
889          Dmsg(10, "Found CreateTDate=%s\n", ed1);
890          printf("status=1 volume=\"%s\" createtdate=%s type=zfs\n",
891                 path, ed1);
892 
893       } else {
894          printf("status=1 volume=\"%s\" createtdate=%s type=zfs\n",
895                 path, edit_uint64(time(NULL), ed1));
896       }
897       return 1;
898    };
899 
del()900    int del() {
901       if (!snapshot::del()) {
902          return 0;
903       }
904 
905       Mmsg(cmd, "%szfs destroy \"%s\"", arg->sudo, arg->volume);
906       if (run_program(cmd, 300, errmsg)) {
907          Dmsg(10, "Unable to delete snapshot %s\n", errmsg);
908          strip_quotes(errmsg);
909          printf("status=0 type=zfs error=\"%s\"\n", errmsg);
910          return 0;
911       }
912       printf("status=1\n");
913       return 1;
914    };
915 
916    /* zfs list -t snapshot
917     * test/eric@snap1    17K      -    21K  -
918     * test/eric@snap2    17K      -    21K  -
919     *
920     * it is possible to change fields to display with -o
921     */
list()922    int list() {
923       POOL_MEM buf2;
924       if (!snapshot::list()) {
925          return 0;
926       }
927 
928       Mmsg(cmd, "%szfs list -t snapshot -H -o name,used,creation", arg->sudo);
929       /* rpool@basezone_snap00   0       Fri Mar  6  9:55 2015  */
930       if (run_program_full_output(cmd, 60, errmsg)) {
931          Dmsg(10, "Unable to list snapshot %s\n", errmsg);
932          strip_quotes(errmsg);
933          printf("status=0 error=\"Unable to list snapshot %s\"\n", errmsg);
934          return 0;
935       }
936 
937       int  i = 1, Day, Year, Hour, Min;
938       char DayW[50], Month[50], CreateDate[50];
939       const char *buf[4];
940 
941       buf[0] = errmsg;
942       for (char *p = errmsg; p && *p ; p++) {
943          if (*p == '\n') {
944             *p = 0;
945             /* Flush the current one */
946             if (!arg->device || strcmp(arg->device, buf[0]) == 0) {
947 
948                if (sscanf(buf[3], "%49s %49s %d %d:%d %d",
949                           DayW, Month, &Day, &Hour, &Min, &Year) == 6)
950                {
951                   /* Get a clean iso format */
952                   for (int j=1; j <= 12 ; j++) {
953                      if (strcmp(Month, Months[j]) == 0) {
954                         snprintf(Month, sizeof(Month), "%02d", j);
955                      }
956                   }
957                   snprintf(CreateDate, sizeof(CreateDate), "%d-%s-%02d %02d:%02d:00",
958                            Year, Month, Day, Hour, Min);
959                   buf[3] = CreateDate;
960                }
961                printf("volume=\"%s@%s\" name=\"%s\" device=\"%s\" size=\"%s\" "
962                       "createdate=\"%s\" status=1 error=\"\" type=\"zfs\"\n",
963                       buf[0], buf[1], buf[1], buf[0], buf[2], buf[3]);
964             } else {
965                Dmsg(10, "Do not list %s@%s\n", buf[0], buf[1]);
966             }
967 
968             i = 1;
969             buf[0] = p+1;
970             buf[1] = buf[2] = buf[3] = "";
971 
972          } else if ((*p == '\t' || *p == '@') && i < 4) {
973             buf[i++] = p+1;
974             *p = 0;
975          }
976       }
977 
978       return 1;
979    };
980 };
981 
982 /* Structure of the LVS output */
983 typedef struct {
984    const char *name;
985    int         pos;
986 } Header;
987 
988 /* -1 is mandatory, -2 is optionnal */
989 static Header lvs_header[] = {
990    /* KEEP FIRST */
991    {"Path",  -1},        /* Volume Path: /dev/ubuntu-vg/root */
992    {"DMPath",-2},        /* Device mapper Path /dev/mapper/ubuntu--vg-root */
993    {"LV",    -1},        /* Volume Name: root  */
994    {"Attr",  -1},        /* Attributes:  -wi-ao--- */
995    {"KMaj",  -1},        /* Kernel Major: 252 */
996    {"KMin",  -1},        /* Kernel Minor: 0 */
997    {"LSize", -1},        /* Size (b)  */
998    {"#Seg",  -1},        /* Number of segments */
999    {"Origin",-1},
1000    {"OSize", -1},
1001    {"Snap%", -1},
1002    {"Time",  -1},        /* Creation date  */
1003    {NULL,    -1}
1004 };
1005 
1006 static Header vgs_header[] = {
1007    /* KEEP FIRST */
1008    {"VG",    -1},        /* VG Name: vgroot  */
1009    {"VSize", -1},        /* Size */
1010    {"VFree", -1},        /* Space left */
1011    {"#Ext",  -1},        /* Nb Ext */
1012    {"Free",  -1},        /* Nb Ext free */
1013    {"Ext",   -1},        /* Ext size */
1014    {NULL,    -1}
1015 };
1016 
1017 /* LVM backend, not finished */
1018 class lvm: public snapshot {
1019 public:
1020    alist *lvs, *vgs;
1021    int    lvs_nbelt, vgs_nbelt;
1022 
lvm(arguments * arg)1023    lvm(arguments *arg):
1024      snapshot(arg, "lvm"), lvs(NULL), vgs(NULL), lvs_nbelt(0),
1025         vgs_nbelt(0) {};
1026 
~lvm()1027    ~lvm() {
1028       free_header(lvs, lvs_nbelt);
1029       free_header(vgs, vgs_nbelt);
1030    };
1031 
free_header(alist * lst,int nbelt)1032    void free_header(alist *lst, int nbelt) {
1033       if (lst) {
1034          char **current;
1035          /* cleanup at the end */
1036          foreach_alist(current, lst) {
1037             for (int j=0; j < nbelt ; j++) {
1038                Dmsg(50, "current[%d] = %s\n", j, current[j]);
1039                free(current[j]);
1040             }
1041             free(current);
1042          }
1043          delete lst;
1044       }
1045    };
1046 
get_vg_from_lv_path(char * path,char * vg,int max)1047    char *get_vg_from_lv_path(char *path, char *vg, int max) {
1048       char *p;
1049 
1050       if (!path) {
1051          return NULL;
1052       }
1053 
1054       /* Make a copy of the path */
1055       bstrncpy(vg, path, max);
1056       path = vg;
1057 
1058       if (strncmp(path, "/dev/", 5) != 0) {
1059          Dmsg(10, "Strange path %s\n", path);
1060          return NULL;
1061       }
1062       path += 5;             /* skip /dev/ */
1063 
1064       /* End the string at the last / */
1065       p = strchr(path, '/');
1066       if (!p) {
1067          Dmsg(10, "Strange end of path %s\n", path);
1068          return NULL;
1069       }
1070       *p = 0;
1071 
1072       return path;
1073    };
1074 
1075    /* Report the space available on VG */
get_space_available(char * lv)1076    int64_t get_space_available(char *lv) {
1077       char  buf[512];
1078       char *vgname = get_vg_from_lv_path(get_lv_value(lv, "Path"),
1079                                          buf, sizeof(buf));
1080 
1081       if (vgname) {
1082          char *s = get_vg_value(vgname, "VFree");
1083          if (s) {
1084             return str_to_int64(s);
1085 
1086          } else {
1087             Dmsg(10, "Unable to get VFree\n");
1088          }
1089 
1090       } else {
1091          Dmsg(10, "Unable to get VG from %s\n", lv);
1092       }
1093       return -1;
1094    };
1095 
1096    /* return vg_ssd-pacman */
get_lv_from_dm(char * dm,POOLMEM ** ret,uint32_t * major,uint32_t * minor)1097    char *get_lv_from_dm(char *dm, POOLMEM **ret, uint32_t *major, uint32_t *minor) {
1098       struct stat sp;
1099       char *p, *start;
1100       uint32_t maj, min;
1101 
1102       /* Looks to be a device mapper, need to convert the name */
1103       if (strncmp(dm, "/dev/dm", strlen("/dev/dm")) != 0) {
1104          return NULL;
1105       }
1106       if (stat(dm, &sp) < 0) {
1107          return NULL;
1108       }
1109 
1110       Mmsg(cmd, "%sdmsetup ls", arg->sudo);
1111       if (run_program_full_output(cmd, 60, errmsg)) {
1112          Dmsg(10, "Unable to query dmsetup %s\n", errmsg);
1113          return NULL;
1114       }
1115       /* vg_ssd-pacman-real     (254:1)
1116        * vg_ssd-pacman  (254:0)
1117        * or
1118        * vg_ssd-pacman-real     (254, 1)
1119        * vg_ssd-pacman-real     (254, 1)
1120        */
1121       *ret = check_pool_memory_size(*ret, strlen(errmsg)+1);
1122       for (start = p = errmsg; *p ; p++) {
1123          if (*p == '\n') {
1124             *p = 0;
1125             if (sscanf(start, "%s (%d:%d)", *ret, &maj, &min) == 3 ||
1126                 sscanf(start, "%s (%d, %d)", *ret, &maj, &min) == 3)
1127             {
1128                if (maj == major(sp.st_rdev) &&
1129                    min == minor(sp.st_rdev))
1130                {
1131                   return *ret;
1132                }
1133             }
1134             start = p+1;
1135          }
1136       }
1137       return NULL;
1138    };
1139 
1140    /* The LV path from name or dmpath */
get_lv(char * lv)1141    char **get_lv(char *lv) {
1142       char **elt = NULL, *dm = NULL;
1143       int path = get_value_pos(lvs_header, "Path");
1144       int dmpath = get_value_pos(lvs_header, "DMPath");
1145       int kmaj = get_value_pos(lvs_header, "KMaj");
1146       int kmin = get_value_pos(lvs_header, "KMin");
1147       uint32_t min = 0, maj = 0;
1148       POOLMEM *buf = get_pool_memory(PM_FNAME);
1149 
1150       if (!lv || (path < 0 && dmpath < 0)) {
1151          Dmsg(10, "Unable to get LV parameters\n");
1152          goto bail_out;
1153       }
1154 
1155       dm = get_lv_from_dm(lv, &buf, &maj, &min);
1156       Dmsg(50, "%s = get_lv_from_dm(%s, %s, %d, %d)\n", dm, lv, buf, maj, min);
1157 
1158       /* HERE: Need to loop over LVs */
1159       foreach_alist(elt, lvs) {
1160          if (path > 0 && strcmp(NPRT(elt[path]), lv) == 0) {
1161             goto bail_out;
1162          }
1163 
1164          if (dmpath > 0 && strcmp(NPRT(elt[dmpath]), lv) == 0) {
1165             goto bail_out;
1166          }
1167 
1168          /* Try by Minor/Major if comming from device mapper */
1169          if ((maj && kmaj && str_to_uint64(elt[kmaj]) == maj) &&
1170              (min && kmin && str_to_uint64(elt[kmin]) == min))
1171          {
1172             goto bail_out;
1173          }
1174 
1175          /* Find if /dev/mapper/vg_ssd-pacman matches vg_ssd-pacman */
1176          if (dm && dmpath && strlen(elt[dmpath]) > strlen("/dev/mapper/")) {
1177             if (strcmp(elt[dmpath] + strlen("/dev/mapper/"), dm) == 0) {
1178                goto bail_out;
1179             }
1180          }
1181 
1182          /* Special case for old LVM where mapper path doesn't exist */
1183          if (dmpath < 0 && strncmp("/dev/mapper/", lv, 12) == 0) {
1184 
1185             POOLMEM *buf2 = get_memory(strlen(elt[path])*2+10);
1186             pm_strcpy(buf2, "/dev/mapper/");
1187 
1188             char *d = buf2 + 12; /* Skip /dev/mapper/ */
1189             bool ret = false;
1190 
1191             /* Keep the same path, but escape - to -- and / to - */
1192             for (char *p = elt[path]+5; *p ; p++) {
1193                if (*p == '-') {
1194                   *d++ = *p;
1195                }
1196                /* Escape / to - if needed */
1197                *d++ = (*p == '/') ? '-' : *p;
1198             }
1199             *d = 0;
1200             ret = (strcmp(buf2, lv) == 0);
1201             free_pool_memory(buf2);
1202 
1203             if (ret) {
1204                goto bail_out;
1205             }
1206          }
1207       }
1208       Dmsg(10, "%s not found in lv list\n", lv);
1209       return NULL;              /* not found */
1210 
1211    bail_out:
1212       if (buf) {
1213          free_pool_memory(buf);
1214       }
1215       return elt;
1216    };
1217 
1218    /* Report LV Size in bytes */
get_lv_size(char * name)1219    int64_t get_lv_size(char *name) {
1220       char **elt = get_lv(arg->device);
1221       int sp;
1222 
1223       if (!elt) {
1224          return -1;
1225       }
1226 
1227       sp = get_value_pos(lvs_header, "LSize");
1228       /* Check if we have enough space on the VG */
1229       return str_to_int64(elt[sp]);
1230    };
1231 
get_lv_value(char * name,const char * value)1232    char *get_lv_value(char *name, const char *value) {
1233       return get_value(lvs_header, lvs_nbelt, lvs, name, value);
1234    };
1235 
get_value_pos(Header * header,const char * value)1236    int get_value_pos(Header *header, const char *value) {
1237       for (int i = 0; header[i].name ; i++) {
1238          if (strcmp(header[i].name, value) == 0) {
1239             return header[i].pos;
1240          }
1241       }
1242       return -1;                /* not found */
1243    };
1244 
1245    /* Return an element value */
get_value(Header * header,int nbelt,alist * lst,char * name,const char * value)1246    char *get_value(Header *header, int nbelt, alist *lst,
1247                    char *name, const char *value) {
1248       char **elt;
1249       int    pos = get_value_pos(header, value);
1250       int    id  = header[0].pos; /* position name */
1251 
1252       if (pos < 0 || id == -1) {
1253          return NULL;
1254       }
1255       /* Loop over elements we have, and return the value that is asked */
1256       foreach_alist(elt, lst) {
1257          if (strcmp(NPRT(elt[id]), name) == 0) {
1258             return elt[pos];
1259          }
1260       }
1261       return NULL;
1262    };
1263 
1264    /* Return a parameter for a VolumeGroup */
get_vg_value(char * vg,const char * value)1265    char *get_vg_value(char *vg, const char *value) {
1266       return get_value(vgs_header, vgs_nbelt, vgs, vg, value);
1267    };
1268 
1269    /* Get snapshot size, look in config file if needed */
get_lvm_snapshot_size(char * lv)1270    int get_lvm_snapshot_size(char *lv) {
1271       char *tmp, **elt;
1272       uint64_t s, size;
1273       int    sp;
1274       alist *lst;
1275 
1276       int pos = arg->ini.get_item("lvm_snapshot_size");
1277       if (!arg->ini.items[pos].found) {
1278          return -1;             /* Nothing specified, stop here */
1279       }
1280 
1281       lst = arg->ini.items[pos].val.alistval;
1282       if (lst) {
1283          /* /dev/ubuntu-vg/root:100M
1284           * /dev/ubuntu-vg/home:10%
1285           * /dev/ubuntu-vg/var:200GB
1286           */
1287          foreach_alist(tmp, lst) {
1288             char *p = strchr(tmp, ':');
1289 
1290             /* Check the LV name */
1291             if (p && strncmp(tmp, lv, p - tmp) != 0) {
1292                continue;
1293             }
1294 
1295             /* This is a percent */
1296             if (strchr(p+1, '%') != NULL) {
1297                Dmsg(10, "Found a %%\n");
1298                s = str_to_int64(p+1);
1299 
1300                /* Compute the requested size */
1301                sp = get_value_pos(lvs_header, "LSize");
1302                elt = get_lv(lv);
1303                size = str_to_int64(elt[sp]);
1304                return size * (s / 100);
1305             }
1306 
1307             /* It might be a size */
1308             if (size_to_uint64(p+1, strlen(p+1), &s)) {
1309                Dmsg(10, "Found size %ld\n", s);
1310                return s;
1311             }
1312             Dmsg(10, "Unable to use %s\n", tmp);
1313             return -1;
1314          }
1315       }
1316       return -1;
1317    };
1318 
create()1319    int create() {
1320       char   *name, *ts, buf[128], *lvname;
1321       int64_t size, ssize, maxsize;
1322       if (!snapshot::create()) {
1323          return 0;
1324       }
1325 
1326       if (!parse_lvs_output() ||
1327           !parse_vgs_output())
1328       {
1329          printf("status=%d error=\"Unable parse lvs or vgs output\"\n",
1330                 get_error_code());
1331          return 0;
1332       }
1333 
1334       path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
1335 
1336       if (!makedir(path)) {
1337          printf("status=%d error=\"Unable to create mountpoint directory %s errno=%d\n",
1338                 get_error_code(),
1339                 arg->mountpoint, errno);
1340          return 0;
1341       }
1342 
1343       name = get_lv_value(arg->device, "LV");
1344       size = get_lv_size(arg->device);
1345       if (size < 0) {
1346          printf("status=%d error=\"Unable to get lv size\"\n",
1347                 get_error_code());
1348          return 0;
1349       }
1350 
1351       ssize = get_lvm_snapshot_size(arg->device);
1352       if (ssize > 0) {
1353          size = ssize;
1354       } else {
1355          size = size / 10;         /* Ask to get 10% */
1356       }
1357 
1358       size = (size / 512L) * 512L;
1359 
1360       lvname = get_lv_value(arg->device, "Path");
1361       maxsize = get_space_available(lvname);
1362       Dmsg(10, "maxsize=%ld size=%ld\n", maxsize, size);
1363 
1364       if (maxsize < 0) {
1365          printf("status=%d error=\"Unable to detect maxsize\" type=lvm\n",
1366                 get_error_code());
1367          return 0;
1368       }
1369 
1370       if (size > maxsize) {
1371          char ed1[50], ed2[50];
1372          printf("status=%d error=\"Not enough space left on VG %sB, "
1373                 "%sB is required\" type=lvm\n",
1374                 get_error_code(),
1375                 edit_uint64_with_suffix(maxsize, ed1),
1376                 edit_uint64_with_suffix(size, ed2));
1377          return 0;
1378       }
1379 
1380       /* TODO: Need to get the volume name and add the snapshot
1381        * name at the end
1382        */
1383       Mmsg(cmd, "%slvcreate -s -n \"%s_%s\" -L %lldb \"%s\"",
1384            arg->sudo, name, arg->name, size, arg->device);
1385       if (run_program(cmd, 60, errmsg)) {
1386          Dmsg(10, "Unable to create snapshot %s %s\n", arg->name, errmsg);
1387          strip_quotes(errmsg);
1388          printf("status=0 error=\"Unable to create snapshot %s\"\n", errmsg);
1389          return 0;
1390       }
1391       if (!parse_lvs_output()) {
1392          Dmsg(10, "Unable to parse lvm output after snapshot creation\n");
1393          printf("status=0 error=\"Unable to parse lvs\"\n");
1394          return 0;
1395       }
1396 
1397       Mmsg(cmd, "%s_%s", arg->device, arg->name);
1398       ts = get_lv_value(cmd, "Time");
1399       if (!ts) {
1400          Dmsg(10, "Unable to find snapshot in lvs output\n");
1401          bstrftimes(buf, sizeof(buf), time(NULL));
1402          ts = buf;
1403       }
1404       Dmsg(10, "status=1 volume=\"%s_%s\" createdate=\"%s\" type=lvm\n",
1405              arg->device, arg->name, ts);
1406       printf("status=1 volume=\"%s_%s\" createdate=\"%s\" type=lvm\n",
1407              arg->device, arg->name, ts);
1408       return 1;
1409    };
1410 
del()1411    int del() {
1412       if (!snapshot::del()) {
1413          return 0;
1414       }
1415       Mmsg(cmd, "%slvremove -f \"%s\"",
1416            arg->sudo, arg->volume);
1417 
1418       if (run_program(cmd, 60, errmsg)) {
1419          Dmsg(10, "Unable to delete snapshot %s %s\n", arg->name, errmsg);
1420          strip_quotes(errmsg);
1421          printf("status=0 error=\"Unable to delete snapshot %s\"\n", errmsg);
1422          return 0;
1423       }
1424 
1425       printf("status=1\n");
1426       return 1;
1427    };
1428 
check()1429    int check() {
1430       if (!snapshot::check()) {
1431          return 0;
1432       }
1433       parse_vgs_output();
1434       for (int i = 0; vgs_header[i].name ; i++) {
1435          if (vgs_header[i].pos == -1) {
1436             printf("status=0 error=\"Unable to use output of vgs command."
1437                    " %s is missing.\"\n",
1438                    vgs_header[i].name);
1439             return 0;
1440          }
1441       }
1442 
1443       parse_lvs_output();
1444       for (int i = 0; lvs_header[i].name ; i++) {
1445          if (lvs_header[i].pos == -1) {
1446             printf("status=0 error=\"Unable to use output of lvs command."
1447                    " %s is missing.\"\n",
1448                    lvs_header[i].name);
1449             return 0;
1450          }
1451       }
1452       return 1;
1453    };
1454 
strip_double_slashes(char * fname)1455    void strip_double_slashes(char *fname)
1456    {
1457       char *p = fname;
1458       while (p && *p) {
1459          p = strpbrk(p, "/\\");
1460          if (p != NULL) {
1461             if (IsPathSeparator(p[1])) {
1462                strcpy(p, p+1);
1463             }
1464             p++;
1465          }
1466       }
1467    };
1468 
mount()1469    int mount() {
1470       if (!snapshot::mount()) {
1471          return 0;
1472       }
1473 
1474       path_concat(path, arg->mountpoint, arg->snapdir, arg->name);
1475 
1476       if (!makedir(path)) {
1477          printf("status=0 error=\"Unable to create mount point %s errno=%d\"\n",
1478                 path, errno);
1479          return 0;
1480       }
1481 
1482       Mmsg(cmd, "%smount -o ro \"%s\" \"%s\"", arg->sudo, arg->volume, path);
1483       if (run_program(cmd, 60, errmsg) != 0) {
1484          Dmsg(10, "Unable to mount volume. ERR=%s\n", errmsg);
1485          strip_quotes(errmsg);
1486          printf("status=0 error=\"Unable to mount the device %s\"\n", errmsg);
1487          return 0;
1488       }
1489 
1490       Dmsg(10, "status=1 snapmountpoint=\"%s\" snapdirectory=\"%s/%s\"\n",
1491             path, arg->mountpoint, arg->snapdir);
1492       printf("status=1 snapmountpoint=\"%s\" snapdirectory=\"%s/%s\"\n",
1493              path, arg->mountpoint, arg->snapdir);
1494       return 1;
1495    };
1496 
unmount()1497    int unmount() {
1498       int ret, retry = arg->retry;
1499 
1500       if (!snapshot::unmount()) {
1501          return 0;
1502       }
1503 
1504       Mmsg(cmd, "%sumount \"%s\"", arg->sudo, arg->snapmountpoint);
1505       do {
1506          ret = run_program(cmd, 60, errmsg);
1507          if (ret != 0) {
1508             Dmsg(10, "Unable to unmount the directory. ERR=%s\n", errmsg);
1509             sleep(3);
1510          }
1511       } while (ret != 0 && retry-- > 0);
1512 
1513       if (ret != 0) {
1514          Dmsg(10, "Unable to mount volume. ERR=%s\n", errmsg);
1515          strip_quotes(errmsg);
1516          printf("status=0 error=\"Unable to umount the device %s\"\n", errmsg);
1517          return 0;
1518       }
1519 
1520       retry = arg->retry;
1521       do {
1522          Dmsg(10, "Trying to delete mountpoint %s\n", arg->snapmountpoint);
1523          if ((ret = rmdir(arg->snapmountpoint)) != 0) {
1524             sleep(3);
1525          }
1526       } while (retry-- > 0 && ret != 0);
1527 
1528       if (ret != 0) {
1529          berrno be;
1530          Dmsg(10, "Unable to delete mountpoint after unmount\n");
1531          printf("error=\"Unable to delete mountpoint after unmount errno=%s\"",
1532                 be.bstrerror(errno));
1533       }
1534       printf(" status=1\n");
1535       return 1;
1536    };
1537 
1538    /* TODO: Here we need to check LVM settings */
support()1539    int support() {
1540       char **elt;
1541       int  mp;
1542 
1543       if (!snapshot::support()) {
1544          return 0;
1545       }
1546       if (!check()) {
1547          return 0;
1548       }
1549 
1550       elt = get_lv(arg->device);
1551 
1552       if (!elt) {
1553          Dmsg(10, "Not detected as LVM\n");
1554          printf("status=0 error=\"Not detected as LVM\"\n");
1555          return 0;
1556       }
1557       mp = get_value_pos(lvs_header ,"Path");
1558       printf("status=1 device=\"%s\" type=lvm\n", elt[mp]);
1559       return 1;
1560    };
1561 
1562    /* count the number of column in the output */
count_col(char * l,char sep)1563    int count_col(char *l, char sep) {
1564       int nb=0;
1565       for (char *p = l ; *p ; p++) {
1566          if (*p == sep) {
1567             nb++;
1568          }
1569       }
1570       return nb;
1571    };
1572 
1573    /* Decode the Attr field */
decode_attr(char * l)1574    int decode_attr(char *l) {
1575       /*
1576        * Volume  type:  (m)irrored,  (M)irrored  without initial sync,
1577        * (o)rigin, (O)rigin  with  merging  snapshot,  (r)aid,  (R)aid
1578        * without   initial   sync,   (s)napshot,  merging  (S)napshot,
1579        * (p)vmove, (v)irtual, mirror or raid (i)mage, mirror  or  raid
1580        * (I)mage out-of-sync, mirror (l)og device, under (c)onversion,
1581        * thin (V)olume, (t)hin pool, (T)hin pool data,  raid  or  thin
1582        * pool m(e)tadata
1583        */
1584 
1585       return 0;
1586    };
1587 
parse_vgs_output()1588    bool parse_vgs_output() {
1589       Mmsg(cmd, "%svgs -o vg_all --separator=; --units b --nosuffix", arg->sudo);
1590       if (vgs) {
1591          free_header(vgs, vgs_nbelt);
1592          vgs_nbelt=0;
1593       }
1594       vgs = New(alist(10, not_owned_by_alist));
1595       if (!parse_output(cmd, vgs, &vgs_nbelt, vgs_header)) {
1596          return false;
1597       }
1598       return true;
1599    };
1600 
parse_lvs_output()1601    bool parse_lvs_output() {
1602       Mmsg(cmd, "%slvs -o lv_all --separator=; --units b --nosuffix", arg->sudo);
1603       if (lvs) {
1604          free_header(lvs, lvs_nbelt);
1605          lvs_nbelt=0;
1606       }
1607       lvs = New(alist(10, not_owned_by_alist));
1608       if (!parse_output(cmd, lvs, &lvs_nbelt, lvs_header)) {
1609          return false;
1610       }
1611       return true;
1612    };
1613 
1614    /* Function to parse LVM command output */
parse_output(char * cmd,alist * ret,int * ret_nbelt,Header * hdr)1615    bool parse_output(char *cmd, alist *ret, int *ret_nbelt, Header *hdr) {
1616       char *p;
1617       int   i=0;
1618       int   pos=0;
1619       int   nbelt=0;
1620       char  buf[2048];          /* Size for a single line */
1621       bool  header_done=false;
1622 
1623       if (run_program_full_output(cmd, 60, errmsg)) {
1624          strip_quotes(errmsg);
1625          Dmsg(10, "Unable to run lvs. ERR=%s\n", errmsg);
1626          return false;
1627       }
1628 
1629       char **current = NULL;
1630 
1631       for (p = errmsg; *p ; p++) {
1632          if (*p == ';') {        /* We have a separator, handle current value */
1633             buf[i]=0;
1634             if (!header_done) {
1635                nbelt++; /* Keep the number of element in the line */
1636 
1637                /* Find if we need this value, and where to store it */
1638                for (int j=0; hdr[j].name ; j++) {
1639                   if (strcasecmp(buf, hdr[j].name) == 0) {
1640                      hdr[j].pos = pos;
1641                      break;
1642                   }
1643                }
1644 
1645             } else {
1646                if (pos == 0) {
1647                   /* First item, need to allocate new array */
1648                   current = (char **)malloc(nbelt * sizeof(char *) + 1);
1649                   memset(current, 0, nbelt * sizeof(char *) + 1);
1650                   ret->append(current);
1651                }
1652                /* Keep the current value */
1653                current[pos] = bstrdup(buf);
1654             }
1655             pos++;
1656             i = 0;
1657          } else if (*p == '\n') {
1658             /* We deal with a new line, so the header is done (if in) */
1659             header_done = true;
1660             i = 0;
1661             pos = 0;
1662 
1663          } else if (i < (int)sizeof(buf)) {
1664             buf[i++] = *p;
1665 
1666          } else {
1667             Dmsg(10, "Output too big !!! %s\n", errmsg);
1668             break;
1669          }
1670       }
1671       *ret_nbelt = nbelt;
1672       return true;
1673    };
1674 
list()1675    int list() {
1676       char **elt, **elt2 = NULL;
1677       const char *err = NULL;
1678       int    p_attr, p_path, p_origin, p_time, p_size;
1679       POOLMEM *p, *f, *d;
1680       int    fnl, pnl, status;
1681 
1682       if (!snapshot::list()) {
1683          return false;
1684       }
1685 
1686       if (!parse_lvs_output()) {
1687          return false;
1688       }
1689 
1690       p_attr = get_value_pos(lvs_header, "Attr");
1691       p_path = get_value_pos(lvs_header, "Path");
1692       p_time = get_value_pos(lvs_header, "Time");
1693       p_size = get_value_pos(lvs_header, "Snap%");
1694       p_origin = get_value_pos(lvs_header, "Origin");
1695 
1696       if (p_time < 0 || p_origin < 0) {
1697          printf("status=1 error=\"Unable to get snapshot Origin from lvs command\"\n");
1698          return false;
1699       }
1700 
1701       p = get_pool_memory(PM_FNAME);
1702       f = get_pool_memory(PM_FNAME);
1703       d = get_pool_memory(PM_FNAME);
1704 
1705       elt2 = get_lv(arg->device);
1706 
1707       /* TODO: We need to get the device name from the mount point */
1708       foreach_alist(elt, lvs) {
1709          char *attr = elt[p_attr];
1710          /* swi-a-s-- */
1711          if (attr[0] == 's') {
1712             if (attr[4] == 'I') {
1713                /* 5  State:  (a)ctive, (s)uspended, (I)nvalid snapshot, invalid (S)uspended
1714                 *            snapshot, snapshot (m)erge failed, suspended snapshot (M)erge
1715                 *            failed, mapped (d)evice present without tables, mapped device
1716                 *            present with (i)nactive table, (X) unknown
1717                 */
1718                status = 0;
1719                err = "Invalid snapshot";
1720             } else {
1721                status = 1;
1722                err = "";
1723             }
1724 
1725             split_path_and_filename(elt[p_path], &p, &pnl, &f, &fnl);
1726             Mmsg(d, "%s%s", p, elt[p_origin]);
1727 
1728             if ((!arg->device || strcmp(arg->device, d) == 0) ||
1729                 (elt2 && strcmp(elt2[p_path], d) == 0))
1730             {
1731                /* On LVM, the name is LV_SnapshotName, we can strip the LV_ if we find it */
1732                Mmsg(p, "%s_", d); /* /dev/mapper/vg_ssd/test_ */
1733                if (strncmp(p, elt[p_path], strlen(p)) == 0) {
1734                   pm_strcpy(f, elt[p_path] + strlen(p));/* test_MySnapshot_2020.. => MySnapshot_2020 */
1735                }
1736 
1737                printf("volume=\"%s\" device=\"%s\" name=\"%s\" createdate=\"%s\" size=\"%s\" "
1738                       "status=%d error=\"%s\" type=lvm\n",
1739                       elt[p_path], d, f, elt[p_time], elt[p_size], status, err);
1740             }
1741          }
1742       }
1743       free_pool_memory(p);
1744       free_pool_memory(f);
1745       free_pool_memory(d);
1746       return true;
1747    };
1748 };
1749 
1750 /* The simulator is using a simple symlink */
1751 class simulator: public snapshot {
1752 public:
simulator(arguments * arg)1753    simulator(arguments *arg): snapshot(arg, "simulator") {};
1754 
mount()1755    int mount() {
1756       if (!snapshot::mount()) {
1757          return 0;
1758       }
1759       split_path_and_filename(arg->volume, &path, &pnl, &fname, &fnl);
1760       printf("status=1 snapmountpoint=\"%s\" snapdirectory=\"%s\"\n",
1761              arg->volume, path);
1762       return 1;
1763    };
1764 
unmount()1765    int unmount() {
1766       printf("status=1\n");
1767       return 1;
1768    };
1769 
support()1770    int support() {
1771       if (!snapshot::support()) {
1772          return 0;
1773       }
1774       if (access(arg->mountpoint, W_OK) != 0) {
1775          printf("status=0 device=\"%s\" type=simulator "
1776                 "error=\"Unable to access mountpoint\"\n",
1777                 arg->mountpoint);
1778          return 0;
1779       }
1780       printf("status=1 device=\"%s\" type=simulator\n", arg->mountpoint);
1781       return 1;
1782    };
1783 
create()1784    int create() {
1785       char    ed1[50];
1786       utime_t now;
1787 
1788       if (!snapshot::create()) {
1789          return 0;
1790       }
1791       Mmsg(path, "%s/%s", arg->mountpoint, arg->snapdir);
1792       makedir(path);
1793       now = time(NULL);
1794       Mmsg(cmd, "ln -vsf \"%s\" \"%s\"", arg->mountpoint, path);
1795       if (run_program(cmd, 60, errmsg)) {
1796          Dmsg(10, "Unable to create symlink. ERR=%s\n", errmsg);
1797          strip_quotes(errmsg);
1798          printf("status=%d error=\"Unable to umount the device %s\"\n",
1799                 get_error_code(),
1800                 errmsg);
1801       }
1802       printf("status=1 volume=\"%s\" createtdate=%s type=simulator\n",
1803              path, edit_uint64(now, ed1));
1804       return 1;
1805    };
1806 
del()1807    int del() {
1808       int ret;
1809       if (!snapshot::del()) {
1810          return 0;
1811       }
1812       ret = unlink(arg->volume);
1813       printf("status=%d\n", (ret == 0)? 1 : 0);
1814       return 1;
1815    };
1816 };
1817 
detect_snapshot_backend(arguments * arg)1818 snapshot *detect_snapshot_backend(arguments *arg)
1819 {
1820    if (arg->type) {
1821       if (strcasecmp(arg->type, "btrfs") == 0) {
1822          return new btrfs(arg);
1823 
1824       } else if (strcasecmp(arg->type, "lvm") == 0) {
1825          return new lvm(arg);
1826 
1827       } else if (strcasecmp(arg->type, "simulator") == 0) {
1828          return new simulator(arg);
1829 
1830       } else if (strcasecmp(arg->type, "zfs") == 0) {
1831          return new zfs(arg);
1832       }
1833    }
1834    if (arg->fstype) {
1835       if (strcasecmp(arg->fstype, "btrfs") == 0) {
1836          return new btrfs(arg);
1837 
1838       } else if (strcasecmp(arg->fstype, "tmpfs") == 0) {
1839          return new simulator(arg);
1840 
1841       /* TODO: Need to find something smarter here */
1842       } else if (strcasecmp(arg->fstype, "ext4") == 0) {
1843          return new lvm(arg);
1844 
1845       } else if (strcasecmp(arg->fstype, "xfs") == 0) {
1846          return new lvm(arg);
1847 
1848       } else if (strcasecmp(arg->fstype, "ext3") == 0) {
1849          return new lvm(arg);
1850 
1851       } else if (strcasecmp(arg->fstype, "zfs") == 0 ||
1852                  strcasecmp(arg->fstype, "fuse.zfs") == 0)
1853       {
1854          return new zfs(arg);
1855       }
1856    }
1857    Dmsg(10, "Backend not found\n");
1858    return NULL;
1859 }
1860 
1861 /* defined in jcr.c */
1862 void create_jcr_key();
1863 
main(int argc,char ** argv)1864 int main(int argc, char **argv)
1865 {
1866    snapshot *snap;
1867    arguments arg;
1868    char      ch;
1869    int       ret=0;
1870    struct stat sp;
1871 
1872    set_trace_file("/dev/null");
1873    setlocale(LC_ALL, "");
1874    setenv("LANG", "C", true);
1875    bindtextdomain("bacula", LOCALEDIR);
1876    textdomain("bacula");
1877    lmgr_init_thread();
1878    OSDependentInit();
1879    init_stack_dump();
1880    my_name_is(argc, argv, "bsnapshot");
1881    create_jcr_key();
1882 
1883    while ((ch = getopt(argc, argv, "?d:vc:so:V:T:t")) != -1) {
1884       switch (ch) {
1885       case 'd':                       /* set debug level */
1886          debug_level = atoi(optarg);
1887          if (debug_level <= 0) {
1888             debug_level = 1;
1889          }
1890          break;
1891 
1892       case 'v':
1893          arg.verbose++;
1894          break;
1895 
1896       case 's':                 /* use sudo */
1897          arg.sudo = "sudo ";
1898          break;
1899 
1900       case 'c':                 /* config file */
1901          pm_strcpy(arg.config_file, optarg);
1902          if (stat(optarg, &sp) < 0) {
1903             Pmsg(000, "Unable to access %s. ERR=%s\n",optarg, strerror(errno));
1904             usage(_("Unable to open -p argument for reading"));
1905          }
1906          break;
1907 
1908       case 'o':                 /* where to send the debug output */
1909          set_trace_file(optarg);
1910          break;
1911 
1912       case 't':
1913          arg.action = (char *)"check";
1914          break;
1915 
1916       case 'V':                 /* set volume name */
1917          arg.volume = optarg;
1918          break;
1919 
1920       case 'T':                 /* device type */
1921          arg.type = optarg;
1922          break;
1923       default:
1924          usage();
1925       }
1926    }
1927 
1928    argc -= optind;
1929    argv += optind;
1930 
1931    if (!arg.validate()) {
1932       usage();
1933    }
1934 
1935    if (arg.disabled) {
1936       Dmsg(10, "disabled from config file\n");
1937       exit (1);
1938    }
1939 
1940    snap = detect_snapshot_backend(&arg);
1941 
1942    if (!snap) {
1943       printf("status=0 error=\"Unable to detect snapshot backend\"");
1944       exit(0);
1945    }
1946 
1947    start_watchdog();
1948 
1949    if (strcasecmp(arg.action, "mount") == 0) {
1950       ret = snap->mount();
1951 
1952    } else if (strcasecmp(arg.action, "support") == 0) {
1953       ret = snap->support();
1954 
1955    } else if (strcasecmp(arg.action, "create") == 0) {
1956       ret = snap->create();
1957 
1958    } else if (strcasecmp(arg.action, "delete") == 0) {
1959       ret = snap->del();
1960 
1961    } else if (strcasecmp(arg.action, "subvolumes") == 0) {
1962       ret = snap->subvolumes();
1963 
1964    } else if (strcasecmp(arg.action, "list") == 0) {
1965       ret = snap->list();
1966 
1967    } else if (strcasecmp(arg.action, "check") == 0) {
1968       ret = snap->check();
1969 
1970    } else if (strcasecmp(arg.action, "unmount") == 0) {
1971       ret = snap->unmount();
1972    }
1973 
1974    delete snap;
1975    stop_watchdog();
1976    close_memory_pool();
1977    lmgr_cleanup_main();
1978 
1979    Dmsg(10, "exit code = %d\n", (ret == 1) ? 0 : 1);
1980    return (ret == 1)? 0 : 1;
1981 }
1982