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