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