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