]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/storage/zfs.c
d745f2e3924945f4ccbf9edc8736fc42bcd444a7
[mirror_lxc.git] / src / lxc / storage / zfs.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE 1
5 #endif
6 #include <errno.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/mount.h>
12 #include <unistd.h>
13
14 #include "config.h"
15 #include "log.h"
16 #include "parse.h"
17 #include "rsync.h"
18 #include "storage.h"
19 #include "utils.h"
20 #include "zfs.h"
21
22 lxc_log_define(zfs, lxc);
23
24 struct zfs_args {
25 const char *dataset;
26 const char *snapshot;
27 const char *options;
28 void *argv;
29 };
30
31 int zfs_detect_exec_wrapper(void *data)
32 {
33 struct zfs_args *args = data;
34
35 execlp("zfs", "zfs", "get", "-H", "-o", "name", "type", args->dataset,
36 (char *)NULL);
37
38 return -1;
39 }
40
41 int zfs_create_exec_wrapper(void *args)
42 {
43 struct zfs_args *zfs_args = args;
44
45 execvp("zfs", zfs_args->argv);
46
47 return -1;
48 }
49
50 int zfs_delete_exec_wrapper(void *args)
51 {
52 struct zfs_args *zfs_args = args;
53
54 execlp("zfs", "zfs", "destroy", "-r", zfs_args->dataset, (char *)NULL);
55
56 return -1;
57 }
58
59 int zfs_snapshot_exec_wrapper(void *args)
60 {
61 struct zfs_args *zfs_args = args;
62
63 execlp("zfs", "zfs", "snapshot", "-r", zfs_args->snapshot, (char *)NULL);
64
65 return -1;
66 }
67
68 int zfs_clone_exec_wrapper(void *args)
69 {
70 struct zfs_args *zfs_args = args;
71
72 execlp("zfs", "zfs", "clone", "-p", "-o", "canmount=noauto", "-o",
73 zfs_args->options, zfs_args->snapshot, zfs_args->dataset,
74 (char *)NULL);
75
76 return -1;
77 }
78
79 int zfs_get_parent_snapshot_exec_wrapper(void *args)
80 {
81 struct zfs_args *zfs_args = args;
82
83 execlp("zfs", "zfs", "get", "-H", "-o", "value", "origin",
84 zfs_args->dataset, (char *)NULL);
85
86 return -1;
87 }
88
89 static bool zfs_list_entry(const char *path, char *output, size_t inlen)
90 {
91 struct lxc_popen_FILE *f;
92 bool found = false;
93
94 f = lxc_popen("zfs list 2> /dev/null");
95 if (f == NULL) {
96 SYSERROR("popen failed");
97 return false;
98 }
99
100 while (fgets(output, inlen, f->f)) {
101 if (strstr(output, path)) {
102 found = true;
103 break;
104 }
105 }
106 (void)lxc_pclose(f);
107
108 return found;
109 }
110
111 bool zfs_detect(const char *path)
112 {
113 int ret;
114 char *dataset;
115 struct zfs_args cmd_args = {0};
116 char cmd_output[PATH_MAX] = {0};
117
118 if (!strncmp(path, "zfs:", 4))
119 return true;
120
121 /* This is a legacy zfs setup where the rootfs path
122 * "<lxcpath>/<lxcname>/rootfs" is given.
123 */
124 if (*path == '/') {
125 bool found;
126 char *output = malloc(LXC_LOG_BUFFER_SIZE);
127
128 if (!output) {
129 ERROR("out of memory");
130 return false;
131 }
132
133 found = zfs_list_entry(path, output, LXC_LOG_BUFFER_SIZE);
134 free(output);
135 return found;
136 }
137
138 cmd_args.dataset = path;
139 ret = run_command(cmd_output, sizeof(cmd_output),
140 zfs_detect_exec_wrapper, (void *)&cmd_args);
141 if (ret < 0) {
142 ERROR("Failed to detect zfs dataset \"%s\": %s", path, cmd_output);
143 return false;
144 }
145
146 if (cmd_output[0] == '\0')
147 return false;
148
149 /* remove any possible leading and trailing whitespace */
150 dataset = cmd_output;
151 dataset += lxc_char_left_gc(dataset, strlen(dataset));
152 dataset[lxc_char_right_gc(dataset, strlen(dataset))] = '\0';
153
154 if (strcmp(dataset, path))
155 return false;
156
157 return true;
158 }
159
160 int zfs_mount(struct lxc_storage *bdev)
161 {
162 int ret;
163 size_t oldlen, newlen, totallen;
164 char *mntdata, *tmp;
165 const char *src;
166 unsigned long mntflags;
167 char cmd_output[PATH_MAX] = {0};
168
169 if (strcmp(bdev->type, "zfs"))
170 return -22;
171
172 if (!bdev->src || !bdev->dest)
173 return -22;
174
175 ret = parse_mntopts(bdev->mntopts, &mntflags, &mntdata);
176 if (ret < 0) {
177 ERROR("Failed to parse mount options");
178 free(mntdata);
179 return -22;
180 }
181
182 /* This is a legacy zfs setup where the rootfs path
183 * "<lxcpath>/<lxcname>/rootfs" is given and we do a bind-mount.
184 */
185 src = lxc_storage_get_path(bdev->src, bdev->type);
186 if (*src == '/') {
187 bool found;
188
189 found = zfs_list_entry(src, cmd_output, sizeof(cmd_output));
190 if (!found) {
191 ERROR("Failed to find zfs entry \"%s\"", src);
192 return -1;
193 }
194
195 tmp = strchr(cmd_output, ' ');
196 if (!tmp) {
197 ERROR("Failed to detect zfs dataset associated with "
198 "\"%s\"", src);
199 return -1;
200 }
201 *tmp = '\0';
202 src = cmd_output;
203 }
204
205 /* ','
206 * +
207 * strlen("zfsutil")
208 * +
209 * ','
210 * +
211 * strlen(mntpoint=)
212 * +
213 * strlen(src)
214 * +
215 * '\0'
216 */
217 newlen = 1 + 7 + 1 + 9 + strlen(src) + 1;
218 oldlen = mntdata ? strlen(mntdata) : 0;
219 totallen = (newlen + oldlen);
220 tmp = realloc(mntdata, totallen);
221 if (!tmp) {
222 ERROR("Failed to reallocate memory");
223 free(mntdata);
224 return -1;
225 }
226 mntdata = tmp;
227
228 ret = snprintf((mntdata + oldlen), newlen, ",zfsutil,mntpoint=%s", src);
229 if (ret < 0 || (size_t)ret >= newlen) {
230 ERROR("Failed to create string");
231 free(mntdata);
232 return -1;
233 }
234
235 ret = mount(src, bdev->dest, "zfs", mntflags, mntdata);
236 free(mntdata);
237 if (ret < 0 && errno != EBUSY) {
238 SYSERROR("Failed to mount \"%s\" on \"%s\"", src, bdev->dest);
239 return -1;
240 }
241
242 TRACE("Mounted \"%s\" on \"%s\"", src, bdev->dest);
243 return 0;
244 }
245
246 int zfs_umount(struct lxc_storage *bdev)
247 {
248 int ret;
249
250 if (strcmp(bdev->type, "zfs"))
251 return -22;
252
253 if (!bdev->src || !bdev->dest)
254 return -22;
255
256 ret = umount(bdev->dest);
257 if (ret < 0)
258 SYSERROR("Failed to unmount \"%s\"", bdev->dest);
259 else
260 TRACE("Unmounted \"%s\"", bdev->dest);
261
262 return ret;
263 }
264
265 bool zfs_copy(struct lxc_conf *conf, struct lxc_storage *orig,
266 struct lxc_storage *new, uint64_t newsize)
267 {
268 int ret;
269 char cmd_output[PATH_MAX], option[PATH_MAX];
270 struct rsync_data data = {0, 0};
271 struct zfs_args cmd_args = {0};
272 const char *argv[] = {"zfs", /* 0 */
273 "create", /* 1 */
274 "-o", "", /* 2, 3 */
275 "-o", "canmount=noauto", /* 4, 5 */
276 "-p", /* 6 */
277 "", /* 7 */
278 NULL};
279
280 /* mountpoint */
281 ret = snprintf(option, PATH_MAX, "mountpoint=%s", new->dest);
282 if (ret < 0 || ret >= PATH_MAX) {
283 ERROR("Failed to create string");
284 return false;
285 }
286 argv[3] = option;
287 argv[7] = lxc_storage_get_path(new->src, new->type);
288
289 cmd_args.argv = argv;
290 ret = run_command(cmd_output, sizeof(cmd_output),
291 zfs_create_exec_wrapper, (void *)&cmd_args);
292 if (ret < 0) {
293 ERROR("Failed to create zfs dataset \"%s\": %s", new->src, cmd_output);
294 return false;
295 } else if (cmd_output[0] != '\0') {
296 INFO("Created zfs dataset \"%s\": %s", new->src, cmd_output);
297 } else {
298 TRACE("Created zfs dataset \"%s\"", new->src);
299 }
300
301 ret = mkdir_p(new->dest, 0755);
302 if (ret < 0 && errno != EEXIST) {
303 SYSERROR("Failed to create directory \"%s\"", new->dest);
304 return false;
305 }
306
307 data.orig = orig;
308 data.new = new;
309 ret = run_command(cmd_output, sizeof(cmd_output),
310 lxc_storage_rsync_exec_wrapper, (void *)&data);
311 if (ret < 0) {
312 ERROR("Failed to rsync from \"%s\" into \"%s\": %s", orig->dest,
313 new->dest, cmd_output);
314 return false;
315 }
316 TRACE("Rsynced from \"%s\" to \"%s\"", orig->dest, new->dest);
317
318 return true;
319 }
320
321 /* create read-only snapshot and create a clone from it */
322 bool zfs_snapshot(struct lxc_conf *conf, struct lxc_storage *orig,
323 struct lxc_storage *new, uint64_t newsize)
324 {
325 int ret;
326 size_t snapshot_len, len;
327 char *tmp, *snap_name, *snapshot;
328 const char *orig_src;
329 struct zfs_args cmd_args = {0};
330 char cmd_output[PATH_MAX] = {0}, option[PATH_MAX];
331
332 orig_src = lxc_storage_get_path(orig->src, orig->type);
333 if (*orig_src == '/') {
334 bool found;
335
336 found = zfs_list_entry(orig_src, cmd_output, sizeof(cmd_output));
337 if (!found) {
338 ERROR("Failed to find zfs entry \"%s\"", orig_src);
339 return false;
340 }
341
342 tmp = strchr(cmd_output, ' ');
343 if (!tmp) {
344 ERROR("Failed to detect zfs dataset associated with "
345 "\"%s\"", orig_src);
346 return false;
347 }
348 *tmp = '\0';
349 orig_src = cmd_output;
350 }
351
352 snapshot = strdup(orig_src);
353 if (!snapshot) {
354 ERROR("Failed to duplicate string \"%s\"", orig_src);
355 return false;
356 }
357
358 snap_name = strrchr(new->src, '/');
359 if (!snap_name) {
360 ERROR("Failed to detect \"/\" in \"%s\"", new->src);
361 free(snapshot);
362 return false;
363 }
364 snap_name++;
365
366 /* strlen(snapshot)
367 * +
368 * @
369 * +
370 * strlen(cname)
371 * +
372 * \0
373 */
374 snapshot_len = strlen(snapshot);
375 len = snapshot_len + 1 + strlen(snap_name) + 1;
376 tmp = realloc(snapshot, len);
377 if (!tmp) {
378 ERROR("Failed to reallocate memory");
379 free(snapshot);
380 return false;
381 }
382 snapshot = tmp;
383
384 len -= snapshot_len;
385 ret = snprintf(snapshot + snapshot_len, len, "@%s", snap_name);
386 if (ret < 0 || ret >= len) {
387 ERROR("Failed to create string");
388 free(snapshot);
389 return false;
390 }
391
392 cmd_args.snapshot = snapshot;
393 ret = run_command(cmd_output, sizeof(cmd_output),
394 zfs_snapshot_exec_wrapper, (void *)&cmd_args);
395 if (ret < 0) {
396 ERROR("Failed to create zfs snapshot \"%s\": %s", snapshot, cmd_output);
397 free(snapshot);
398 return false;
399 } else if (cmd_output[0] != '\0') {
400 INFO("Created zfs snapshot \"%s\": %s", snapshot, cmd_output);
401 } else {
402 TRACE("Created zfs snapshot \"%s\"", snapshot);
403 }
404
405 ret = snprintf(option, PATH_MAX, "mountpoint=%s", new->dest);
406 if (ret < 0 || ret >= PATH_MAX) {
407 ERROR("Failed to create string");
408 free(snapshot);
409 return false;
410 }
411
412 cmd_args.dataset = lxc_storage_get_path(new->src, new->type);
413 cmd_args.snapshot = snapshot;
414 cmd_args.options = option;
415 ret = run_command(cmd_output, sizeof(cmd_output),
416 zfs_clone_exec_wrapper, (void *)&cmd_args);
417 if (ret < 0)
418 ERROR("Failed to create zfs dataset \"%s\": %s", new->src, cmd_output);
419 else if (cmd_output[0] != '\0')
420 INFO("Created zfs dataset \"%s\": %s", new->src, cmd_output);
421 else
422 TRACE("Created zfs dataset \"%s\"", new->src);
423
424 free(snapshot);
425 return true;
426 }
427
428 int zfs_clonepaths(struct lxc_storage *orig, struct lxc_storage *new,
429 const char *oldname, const char *cname, const char *oldpath,
430 const char *lxcpath, int snap, uint64_t newsize,
431 struct lxc_conf *conf)
432 {
433 int ret;
434 char *dataset, *tmp;
435 const char *orig_src;
436 size_t dataset_len, len;
437 char cmd_output[PATH_MAX] = {0};
438
439 if (!orig->src || !orig->dest)
440 return -1;
441
442 if (snap && strcmp(orig->type, "zfs")) {
443 ERROR("zfs snapshot from %s backing store is not supported",
444 orig->type);
445 return -1;
446 }
447
448 orig_src = lxc_storage_get_path(orig->src, orig->type);
449 if (!strcmp(orig->type, "zfs")) {
450 if (*orig_src == '/') {
451 bool found;
452
453 found = zfs_list_entry(orig_src, cmd_output,
454 sizeof(cmd_output));
455 if (!found) {
456 ERROR("Failed to find zfs entry \"%s\"", orig_src);
457 return -1;
458 }
459
460 tmp = strchr(cmd_output, ' ');
461 if (!tmp) {
462 ERROR("Failed to detect zfs dataset associated "
463 "with \"%s\"", orig_src);
464 return -1;
465 }
466 *tmp = '\0';
467 orig_src = cmd_output;
468 }
469
470 tmp = strrchr(orig_src, '/');
471 if (!tmp) {
472 ERROR("Failed to detect \"/\" in \"%s\"", orig_src);
473 return -1;
474 }
475
476 len = tmp - orig_src;
477 dataset = strndup(orig_src, len);
478 if (!dataset) {
479 ERROR("Failed to duplicate string \"%zu\" "
480 "bytes of string \"%s\"", len, orig_src);
481 return -1;
482 }
483 } else {
484 tmp = (char *)lxc_global_config_value("lxc.bdev.zfs.root");
485 if (!tmp) {
486 ERROR("The \"lxc.bdev.zfs.root\" property is not set");
487 return -1;
488 }
489
490 dataset = strdup(tmp);
491 if (!dataset) {
492 ERROR("Failed to duplicate string \"%s\"", tmp);
493 return -1;
494 }
495 }
496
497 /* strlen("zfs:") = 4
498 * +
499 * strlen(dataset)
500 * +
501 * / = 1
502 * +
503 * strlen(cname)
504 * +
505 * \0
506 */
507 dataset_len = strlen(dataset);
508 len = 4 + dataset_len + 1 + strlen(cname) + 1;
509 new->src = realloc(dataset, len);
510 if (!new->src) {
511 ERROR("Failed to reallocate memory");
512 free(dataset);
513 return -1;
514 }
515 memmove(new->src + 4, new->src, dataset_len);
516 memmove(new->src, "zfs:", 4);
517
518 len -= dataset_len - 4;
519 ret = snprintf(new->src + dataset_len + 4, len, "/%s", cname);
520 if (ret < 0 || ret >= len) {
521 ERROR("Failed to create string");
522 return -1;
523 }
524
525 /* strlen(lxcpath)
526 * +
527 * /
528 * +
529 * strlen(cname)
530 * +
531 * /
532 * +
533 * strlen("rootfs")
534 * +
535 * \0
536 */
537 len = strlen(lxcpath) + 1 + strlen(cname) + 1 + strlen("rootfs") + 1;
538 new->dest = malloc(len);
539 if (!new->dest) {
540 ERROR("Failed to allocate memory");
541 return -1;
542 }
543
544 ret = snprintf(new->dest, len, "%s/%s/rootfs", lxcpath, cname);
545 if (ret < 0 || ret >= len) {
546 ERROR("Failed to create string \"%s/%s/rootfs\"", lxcpath, cname);
547 return -1;
548 }
549
550 ret = mkdir_p(new->dest, 0755);
551 if (ret < 0 && errno != EEXIST) {
552 SYSERROR("Failed to create directory \"%s\"", new->dest);
553 return -1;
554 }
555
556 return 0;
557 }
558
559 int zfs_destroy(struct lxc_storage *orig)
560 {
561 int ret;
562 char *dataset, *tmp;
563 const char *src;
564 bool found;
565 char *parent_snapshot = NULL;
566 struct zfs_args cmd_args = {0};
567 char cmd_output[PATH_MAX] = {0};
568
569 src = lxc_storage_get_path(orig->src, orig->type);
570
571 /* This is a legacy zfs setup where the rootfs path
572 * "<lxcpath>/<lxcname>/rootfs" is given.
573 */
574 if (*src == '/') {
575 found = zfs_list_entry(src, cmd_output, sizeof(cmd_output));
576 if (!found) {
577 ERROR("Failed to find zfs entry \"%s\"", orig->src);
578 return -1;
579 }
580
581 tmp = strchr(cmd_output, ' ');
582 if (!tmp) {
583 ERROR("Failed to detect zfs dataset associated with "
584 "\"%s\"", cmd_output);
585 return -1;
586 }
587 *tmp = '\0';
588 dataset = cmd_output;
589 } else {
590 cmd_args.dataset = src;
591 ret = run_command(cmd_output, sizeof(cmd_output),
592 zfs_detect_exec_wrapper, (void *)&cmd_args);
593 if (ret < 0) {
594 ERROR("Failed to detect zfs dataset \"%s\": %s", src,
595 cmd_output);
596 return -1;
597 }
598
599 if (cmd_output[0] == '\0') {
600 ERROR("Failed to detect zfs dataset \"%s\"", src);
601 return -1;
602 }
603
604 /* remove any possible leading and trailing whitespace */
605 dataset = cmd_output;
606 dataset += lxc_char_left_gc(dataset, strlen(dataset));
607 dataset[lxc_char_right_gc(dataset, strlen(dataset))] = '\0';
608
609 if (strcmp(dataset, src)) {
610 ERROR("Detected dataset \"%s\" does not match expected "
611 "dataset \"%s\"", dataset, src);
612 return -1;
613 }
614 }
615
616 cmd_args.dataset = strdup(dataset);
617 if (!cmd_args.dataset) {
618 ERROR("Failed to duplicate string \"%s\"", dataset);
619 return -1;
620 }
621
622 ret = run_command(cmd_output, sizeof(cmd_output),
623 zfs_get_parent_snapshot_exec_wrapper,
624 (void *)&cmd_args);
625 if (ret < 0) {
626 ERROR("Failed to retrieve parent snapshot of zfs dataset "
627 "\"%s\": %s", dataset, cmd_output);
628 free((void *)cmd_args.dataset);
629 return -1;
630 } else {
631 INFO("Retrieved parent snapshot of zfs dataset \"%s\": %s", src,
632 cmd_output);
633 }
634
635 /* remove any possible leading and trailing whitespace */
636 tmp = cmd_output;
637 tmp += lxc_char_left_gc(tmp, strlen(tmp));
638 tmp[lxc_char_right_gc(tmp, strlen(tmp))] = '\0';
639
640 /* check whether the dataset has a parent snapshot */
641 if (*tmp != '-' && *(tmp + 1) != '\0') {
642 parent_snapshot = strdup(tmp);
643 if (!parent_snapshot) {
644 ERROR("Failed to duplicate string \"%s\"", tmp);
645 free((void *)cmd_args.dataset);
646 return -1;
647 }
648 }
649
650 /* delete dataset */
651 ret = run_command(cmd_output, sizeof(cmd_output),
652 zfs_delete_exec_wrapper, (void *)&cmd_args);
653 if (ret < 0) {
654 ERROR("Failed to delete zfs dataset \"%s\": %s", dataset,
655 cmd_output);
656 free((void *)cmd_args.dataset);
657 free(parent_snapshot);
658 return -1;
659 } else if (cmd_output[0] != '\0') {
660 INFO("Deleted zfs dataset \"%s\": %s", src, cmd_output);
661 } else {
662 INFO("Deleted zfs dataset \"%s\"", src);
663 }
664
665 free((void *)cmd_args.dataset);
666
667 /* Not a clone so nothing more to do. */
668 if (!parent_snapshot)
669 return 0;
670
671 /* delete parent snapshot */
672 cmd_args.dataset = parent_snapshot;
673 ret = run_command(cmd_output, sizeof(cmd_output),
674 zfs_delete_exec_wrapper, (void *)&cmd_args);
675 if (ret < 0)
676 ERROR("Failed to delete zfs snapshot \"%s\": %s", dataset, cmd_output);
677 else if (cmd_output[0] != '\0')
678 INFO("Deleted zfs snapshot \"%s\": %s", src, cmd_output);
679 else
680 INFO("Deleted zfs snapshot \"%s\"", src);
681
682 free((void *)cmd_args.dataset);
683 return ret;
684 }
685
686 int zfs_create(struct lxc_storage *bdev, const char *dest, const char *n,
687 struct bdev_specs *specs, const struct lxc_conf *conf)
688 {
689 const char *zfsroot;
690 int ret;
691 size_t len;
692 struct zfs_args cmd_args = {0};
693 char cmd_output[PATH_MAX], option[PATH_MAX];
694 const char *argv[] = {"zfs", /* 0 */
695 "create", /* 1 */
696 "-o", "", /* 2, 3 */
697 "-o", "canmount=noauto", /* 4, 5 */
698 "-p", /* 6 */
699 "", /* 7 */
700 NULL};
701
702 if (!specs || !specs->zfs.zfsroot)
703 zfsroot = lxc_global_config_value("lxc.bdev.zfs.root");
704 else
705 zfsroot = specs->zfs.zfsroot;
706
707 bdev->dest = strdup(dest);
708 if (!bdev->dest) {
709 ERROR("Failed to duplicate string \"%s\"", dest);
710 return -1;
711 }
712
713 len = strlen(zfsroot) + 1 + strlen(n) + 1;
714 /* strlen("zfs:") */
715 len += 4;
716 bdev->src = malloc(len);
717 if (!bdev->src) {
718 ERROR("Failed to allocate memory");
719 return -1;
720 }
721
722 ret = snprintf(bdev->src, len, "zfs:%s/%s", zfsroot, n);
723 if (ret < 0 || ret >= len) {
724 ERROR("Failed to create string");
725 return -1;
726 }
727 argv[7] = lxc_storage_get_path(bdev->src, bdev->type);
728
729 ret = snprintf(option, PATH_MAX, "mountpoint=%s", bdev->dest);
730 if (ret < 0 || ret >= PATH_MAX) {
731 ERROR("Failed to create string");
732 return -1;
733 }
734 argv[3] = option;
735
736 cmd_args.argv = argv;
737 ret = run_command(cmd_output, sizeof(cmd_output),
738 zfs_create_exec_wrapper, (void *)&cmd_args);
739 if (ret < 0) {
740 ERROR("Failed to create zfs dataset \"%s\": %s", bdev->src, cmd_output);
741 return -1;
742 } else if (cmd_output[0] != '\0') {
743 INFO("Created zfs dataset \"%s\": %s", bdev->src, cmd_output);
744 } else {
745 TRACE("Created zfs dataset \"%s\"", bdev->src);
746 }
747
748 ret = mkdir_p(bdev->dest, 0755);
749 if (ret < 0 && errno != EEXIST) {
750 SYSERROR("Failed to create directory \"%s\"", bdev->dest);
751 return -1;
752 }
753
754 return ret;
755 }