1 /* SPDX-License-Identifier: LGPL-2.1+ */
11 #include <sys/mount.h>
22 lxc_log_define(zfs
, lxc
);
31 int zfs_detect_exec_wrapper(void *data
)
33 struct zfs_args
*args
= data
;
35 execlp("zfs", "zfs", "get", "-H", "-o", "name", "type", args
->dataset
,
41 int zfs_create_exec_wrapper(void *args
)
43 struct zfs_args
*zfs_args
= args
;
45 execvp("zfs", zfs_args
->argv
);
50 int zfs_delete_exec_wrapper(void *args
)
52 struct zfs_args
*zfs_args
= args
;
54 execlp("zfs", "zfs", "destroy", "-r", zfs_args
->dataset
, (char *)NULL
);
59 int zfs_snapshot_exec_wrapper(void *args
)
61 struct zfs_args
*zfs_args
= args
;
63 execlp("zfs", "zfs", "snapshot", "-r", zfs_args
->snapshot
, (char *)NULL
);
68 int zfs_clone_exec_wrapper(void *args
)
70 struct zfs_args
*zfs_args
= args
;
72 execlp("zfs", "zfs", "clone", "-p", "-o", "canmount=noauto", "-o",
73 zfs_args
->options
, zfs_args
->snapshot
, zfs_args
->dataset
,
79 int zfs_get_parent_snapshot_exec_wrapper(void *args
)
81 struct zfs_args
*zfs_args
= args
;
83 execlp("zfs", "zfs", "get", "-H", "-o", "value", "origin",
84 zfs_args
->dataset
, (char *)NULL
);
89 static bool zfs_list_entry(const char *path
, char *output
, size_t inlen
)
91 struct lxc_popen_FILE
*f
;
94 f
= lxc_popen("zfs list 2> /dev/null");
96 SYSERROR("popen failed");
100 while (fgets(output
, inlen
, f
->f
)) {
101 if (strstr(output
, path
)) {
111 bool zfs_detect(const char *path
)
115 struct zfs_args cmd_args
= {0};
116 char cmd_output
[PATH_MAX
] = {0};
118 if (!strncmp(path
, "zfs:", 4))
121 /* This is a legacy zfs setup where the rootfs path
122 * "<lxcpath>/<lxcname>/rootfs" is given.
126 char *output
= malloc(LXC_LOG_BUFFER_SIZE
);
129 ERROR("out of memory");
133 found
= zfs_list_entry(path
, output
, LXC_LOG_BUFFER_SIZE
);
138 cmd_args
.dataset
= path
;
139 ret
= run_command(cmd_output
, sizeof(cmd_output
),
140 zfs_detect_exec_wrapper
, (void *)&cmd_args
);
142 ERROR("Failed to detect zfs dataset \"%s\": %s", path
, cmd_output
);
146 if (cmd_output
[0] == '\0')
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';
154 if (strcmp(dataset
, path
))
160 int zfs_mount(struct lxc_storage
*bdev
)
163 size_t oldlen
, newlen
, totallen
;
166 unsigned long mntflags
;
167 char cmd_output
[PATH_MAX
] = {0};
169 if (strcmp(bdev
->type
, "zfs"))
172 if (!bdev
->src
|| !bdev
->dest
)
175 ret
= parse_mntopts(bdev
->mntopts
, &mntflags
, &mntdata
);
177 ERROR("Failed to parse mount options");
182 /* This is a legacy zfs setup where the rootfs path
183 * "<lxcpath>/<lxcname>/rootfs" is given and we do a bind-mount.
185 src
= lxc_storage_get_path(bdev
->src
, bdev
->type
);
189 found
= zfs_list_entry(src
, cmd_output
, sizeof(cmd_output
));
191 ERROR("Failed to find zfs entry \"%s\"", src
);
195 tmp
= strchr(cmd_output
, ' ');
197 ERROR("Failed to detect zfs dataset associated with "
217 newlen
= 1 + 7 + 1 + 9 + strlen(src
) + 1;
218 oldlen
= mntdata
? strlen(mntdata
) : 0;
219 totallen
= (newlen
+ oldlen
);
220 tmp
= realloc(mntdata
, totallen
);
222 ERROR("Failed to reallocate memory");
228 ret
= snprintf((mntdata
+ oldlen
), newlen
, ",zfsutil,mntpoint=%s", src
);
229 if (ret
< 0 || (size_t)ret
>= newlen
) {
230 ERROR("Failed to create string");
235 ret
= mount(src
, bdev
->dest
, "zfs", mntflags
, mntdata
);
237 if (ret
< 0 && errno
!= EBUSY
) {
238 SYSERROR("Failed to mount \"%s\" on \"%s\"", src
, bdev
->dest
);
242 TRACE("Mounted \"%s\" on \"%s\"", src
, bdev
->dest
);
246 int zfs_umount(struct lxc_storage
*bdev
)
250 if (strcmp(bdev
->type
, "zfs"))
253 if (!bdev
->src
|| !bdev
->dest
)
256 ret
= umount(bdev
->dest
);
258 SYSERROR("Failed to unmount \"%s\"", bdev
->dest
);
260 TRACE("Unmounted \"%s\"", bdev
->dest
);
265 bool zfs_copy(struct lxc_conf
*conf
, struct lxc_storage
*orig
,
266 struct lxc_storage
*new, uint64_t newsize
)
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 */
275 "-o", "canmount=noauto", /* 4, 5 */
281 ret
= snprintf(option
, PATH_MAX
, "mountpoint=%s", new->dest
);
282 if (ret
< 0 || ret
>= PATH_MAX
) {
283 ERROR("Failed to create string");
287 argv
[7] = lxc_storage_get_path(new->src
, new->type
);
289 cmd_args
.argv
= argv
;
290 ret
= run_command(cmd_output
, sizeof(cmd_output
),
291 zfs_create_exec_wrapper
, (void *)&cmd_args
);
293 ERROR("Failed to create zfs dataset \"%s\": %s", new->src
, cmd_output
);
295 } else if (cmd_output
[0] != '\0') {
296 INFO("Created zfs dataset \"%s\": %s", new->src
, cmd_output
);
298 TRACE("Created zfs dataset \"%s\"", new->src
);
301 ret
= mkdir_p(new->dest
, 0755);
302 if (ret
< 0 && errno
!= EEXIST
) {
303 SYSERROR("Failed to create directory \"%s\"", new->dest
);
309 ret
= run_command(cmd_output
, sizeof(cmd_output
),
310 lxc_storage_rsync_exec_wrapper
, (void *)&data
);
312 ERROR("Failed to rsync from \"%s\" into \"%s\": %s", orig
->dest
,
313 new->dest
, cmd_output
);
316 TRACE("Rsynced from \"%s\" to \"%s\"", orig
->dest
, new->dest
);
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
)
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
];
332 orig_src
= lxc_storage_get_path(orig
->src
, orig
->type
);
333 if (*orig_src
== '/') {
336 found
= zfs_list_entry(orig_src
, cmd_output
, sizeof(cmd_output
));
338 ERROR("Failed to find zfs entry \"%s\"", orig_src
);
342 tmp
= strchr(cmd_output
, ' ');
344 ERROR("Failed to detect zfs dataset associated with "
349 orig_src
= cmd_output
;
352 snapshot
= strdup(orig_src
);
354 ERROR("Failed to duplicate string \"%s\"", orig_src
);
358 snap_name
= strrchr(new->src
, '/');
360 ERROR("Failed to detect \"/\" in \"%s\"", new->src
);
374 snapshot_len
= strlen(snapshot
);
375 len
= snapshot_len
+ 1 + strlen(snap_name
) + 1;
376 tmp
= realloc(snapshot
, len
);
378 ERROR("Failed to reallocate memory");
385 ret
= snprintf(snapshot
+ snapshot_len
, len
, "@%s", snap_name
);
386 if (ret
< 0 || ret
>= len
) {
387 ERROR("Failed to create string");
392 cmd_args
.snapshot
= snapshot
;
393 ret
= run_command(cmd_output
, sizeof(cmd_output
),
394 zfs_snapshot_exec_wrapper
, (void *)&cmd_args
);
396 ERROR("Failed to create zfs snapshot \"%s\": %s", snapshot
, cmd_output
);
399 } else if (cmd_output
[0] != '\0') {
400 INFO("Created zfs snapshot \"%s\": %s", snapshot
, cmd_output
);
402 TRACE("Created zfs snapshot \"%s\"", snapshot
);
405 ret
= snprintf(option
, PATH_MAX
, "mountpoint=%s", new->dest
);
406 if (ret
< 0 || ret
>= PATH_MAX
) {
407 ERROR("Failed to create string");
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
);
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
);
422 TRACE("Created zfs dataset \"%s\"", new->src
);
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
)
435 const char *orig_src
;
436 size_t dataset_len
, len
;
437 char cmd_output
[PATH_MAX
] = {0};
439 if (!orig
->src
|| !orig
->dest
)
442 if (snap
&& strcmp(orig
->type
, "zfs")) {
443 ERROR("zfs snapshot from %s backing store is not supported",
448 orig_src
= lxc_storage_get_path(orig
->src
, orig
->type
);
449 if (!strcmp(orig
->type
, "zfs")) {
450 if (*orig_src
== '/') {
453 found
= zfs_list_entry(orig_src
, cmd_output
,
456 ERROR("Failed to find zfs entry \"%s\"", orig_src
);
460 tmp
= strchr(cmd_output
, ' ');
462 ERROR("Failed to detect zfs dataset associated "
463 "with \"%s\"", orig_src
);
467 orig_src
= cmd_output
;
470 tmp
= strrchr(orig_src
, '/');
472 ERROR("Failed to detect \"/\" in \"%s\"", orig_src
);
476 len
= tmp
- orig_src
;
477 dataset
= strndup(orig_src
, len
);
479 ERROR("Failed to duplicate string \"%zu\" "
480 "bytes of string \"%s\"", len
, orig_src
);
484 tmp
= (char *)lxc_global_config_value("lxc.bdev.zfs.root");
486 ERROR("The \"lxc.bdev.zfs.root\" property is not set");
490 dataset
= strdup(tmp
);
492 ERROR("Failed to duplicate string \"%s\"", tmp
);
497 /* strlen("zfs:") = 4
507 dataset_len
= strlen(dataset
);
508 len
= 4 + dataset_len
+ 1 + strlen(cname
) + 1;
509 new->src
= realloc(dataset
, len
);
511 ERROR("Failed to reallocate memory");
515 memmove(new->src
+ 4, new->src
, dataset_len
);
516 memmove(new->src
, "zfs:", 4);
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");
537 len
= strlen(lxcpath
) + 1 + strlen(cname
) + 1 + strlen("rootfs") + 1;
538 new->dest
= malloc(len
);
540 ERROR("Failed to allocate memory");
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
);
550 ret
= mkdir_p(new->dest
, 0755);
551 if (ret
< 0 && errno
!= EEXIST
) {
552 SYSERROR("Failed to create directory \"%s\"", new->dest
);
559 int zfs_destroy(struct lxc_storage
*orig
)
565 char *parent_snapshot
= NULL
;
566 struct zfs_args cmd_args
= {0};
567 char cmd_output
[PATH_MAX
] = {0};
569 src
= lxc_storage_get_path(orig
->src
, orig
->type
);
571 /* This is a legacy zfs setup where the rootfs path
572 * "<lxcpath>/<lxcname>/rootfs" is given.
575 found
= zfs_list_entry(src
, cmd_output
, sizeof(cmd_output
));
577 ERROR("Failed to find zfs entry \"%s\"", orig
->src
);
581 tmp
= strchr(cmd_output
, ' ');
583 ERROR("Failed to detect zfs dataset associated with "
584 "\"%s\"", cmd_output
);
588 dataset
= cmd_output
;
590 cmd_args
.dataset
= src
;
591 ret
= run_command(cmd_output
, sizeof(cmd_output
),
592 zfs_detect_exec_wrapper
, (void *)&cmd_args
);
594 ERROR("Failed to detect zfs dataset \"%s\": %s", src
,
599 if (cmd_output
[0] == '\0') {
600 ERROR("Failed to detect zfs dataset \"%s\"", src
);
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';
609 if (strcmp(dataset
, src
)) {
610 ERROR("Detected dataset \"%s\" does not match expected "
611 "dataset \"%s\"", dataset
, src
);
616 cmd_args
.dataset
= strdup(dataset
);
617 if (!cmd_args
.dataset
) {
618 ERROR("Failed to duplicate string \"%s\"", dataset
);
622 ret
= run_command(cmd_output
, sizeof(cmd_output
),
623 zfs_get_parent_snapshot_exec_wrapper
,
626 ERROR("Failed to retrieve parent snapshot of zfs dataset "
627 "\"%s\": %s", dataset
, cmd_output
);
628 free((void *)cmd_args
.dataset
);
631 INFO("Retrieved parent snapshot of zfs dataset \"%s\": %s", src
,
635 /* remove any possible leading and trailing whitespace */
637 tmp
+= lxc_char_left_gc(tmp
, strlen(tmp
));
638 tmp
[lxc_char_right_gc(tmp
, strlen(tmp
))] = '\0';
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
);
651 ret
= run_command(cmd_output
, sizeof(cmd_output
),
652 zfs_delete_exec_wrapper
, (void *)&cmd_args
);
654 ERROR("Failed to delete zfs dataset \"%s\": %s", dataset
,
656 free((void *)cmd_args
.dataset
);
657 free(parent_snapshot
);
659 } else if (cmd_output
[0] != '\0') {
660 INFO("Deleted zfs dataset \"%s\": %s", src
, cmd_output
);
662 INFO("Deleted zfs dataset \"%s\"", src
);
665 free((void *)cmd_args
.dataset
);
667 /* Not a clone so nothing more to do. */
668 if (!parent_snapshot
)
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
);
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
);
680 INFO("Deleted zfs snapshot \"%s\"", src
);
682 free((void *)cmd_args
.dataset
);
686 int zfs_create(struct lxc_storage
*bdev
, const char *dest
, const char *n
,
687 struct bdev_specs
*specs
, const struct lxc_conf
*conf
)
692 struct zfs_args cmd_args
= {0};
693 char cmd_output
[PATH_MAX
], option
[PATH_MAX
];
694 const char *argv
[] = {"zfs", /* 0 */
697 "-o", "canmount=noauto", /* 4, 5 */
702 if (!specs
|| !specs
->zfs
.zfsroot
)
703 zfsroot
= lxc_global_config_value("lxc.bdev.zfs.root");
705 zfsroot
= specs
->zfs
.zfsroot
;
707 bdev
->dest
= strdup(dest
);
709 ERROR("Failed to duplicate string \"%s\"", dest
);
713 len
= strlen(zfsroot
) + 1 + strlen(n
) + 1;
716 bdev
->src
= malloc(len
);
718 ERROR("Failed to allocate memory");
722 ret
= snprintf(bdev
->src
, len
, "zfs:%s/%s", zfsroot
, n
);
723 if (ret
< 0 || ret
>= len
) {
724 ERROR("Failed to create string");
727 argv
[7] = lxc_storage_get_path(bdev
->src
, bdev
->type
);
729 ret
= snprintf(option
, PATH_MAX
, "mountpoint=%s", bdev
->dest
);
730 if (ret
< 0 || ret
>= PATH_MAX
) {
731 ERROR("Failed to create string");
736 cmd_args
.argv
= argv
;
737 ret
= run_command(cmd_output
, sizeof(cmd_output
),
738 zfs_create_exec_wrapper
, (void *)&cmd_args
);
740 ERROR("Failed to create zfs dataset \"%s\": %s", bdev
->src
, cmd_output
);
742 } else if (cmd_output
[0] != '\0') {
743 INFO("Created zfs dataset \"%s\": %s", bdev
->src
, cmd_output
);
745 TRACE("Created zfs dataset \"%s\"", bdev
->src
);
748 ret
= mkdir_p(bdev
->dest
, 0755);
749 if (ret
< 0 && errno
!= EEXIST
) {
750 SYSERROR("Failed to create directory \"%s\"", bdev
->dest
);