1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #define __STDC_FORMAT_MACROS
12 #include <sys/sysmacros.h>
19 #include "memory_utils.h"
22 #include "storage_utils.h"
26 #include <sys/mkdev.h>
29 lxc_log_define(lvm
, lxc
);
31 struct lvcreate_args
{
39 /* snapshot specific arguments */
40 const char *source_lv
;
43 static int lvm_destroy_exec_wrapper(void *data
)
45 struct lvcreate_args
*args
= data
;
47 (void)setenv("LVM_SUPPRESS_FD_WARNINGS", "1", 1);
48 execlp("lvremove", "lvremove", "-f", args
->lv
, (char *)NULL
);
53 static int lvm_create_exec_wrapper(void *data
)
55 struct lvcreate_args
*args
= data
;
57 (void)setenv("LVM_SUPPRESS_FD_WARNINGS", "1", 1);
60 execlp("lvcreate", "lvcreate", "-Wy", "--yes", "--thinpool", args
->thinpool
,
61 "-V", args
->size
, args
->vg
, "-n", args
->lv
, (char *)NULL
);
63 execlp("lvcreate", "lvcreate", "-qq", "--thinpool", args
->thinpool
,
64 "-V", args
->size
, args
->vg
, "-n", args
->lv
, (char *)NULL
);
67 execlp("lvcreate", "lvcreate", "-Wy", "--yes", "-L", args
->size
, args
->vg
, "-n",
68 args
->lv
, (char *)NULL
);
70 execlp("lvcreate", "lvcreate", "-qq", "-L", args
->size
, args
->vg
, "-n",
71 args
->lv
, (char *)NULL
);
76 static int lvm_snapshot_exec_wrapper(void *data
)
78 struct lvcreate_args
*args
= data
;
80 (void)setenv("LVM_SUPPRESS_FD_WARNINGS", "1", 1);
82 execlp("lvcreate", "lvcreate", "-s", "-n", args
->lv
,
83 args
->source_lv
, (char *)NULL
);
85 execlp("lvcreate", "lvcreate", "-s", "-L", args
->size
, "-n",
86 args
->lv
, args
->source_lv
, (char *)NULL
);
91 /* The path must be "/dev/<vg>/<lv>". The volume group <vg> must be an existing
92 * volume group, and the logical volume <lv> must not yet exist.
93 * This function will attempt to create "/dev/<vg>/<lv> of size <size>. If
94 * thinpool is specified, we'll check for it's existence and if it's a valid
95 * thin pool, and if so, we'll create the requested logical volume from that
98 static int do_lvm_create(const char *path
, uint64_t size
, const char *thinpool
)
100 __do_free
char *pathdup
= NULL
;
103 char cmd_output
[PATH_MAX
];
105 __do_free
char *tp
= NULL
;
106 struct lvcreate_args cmd_args
= {0};
108 ret
= snprintf(sz
, 24, "%" PRIu64
"b", size
);
109 if (ret
< 0 || ret
>= 24)
110 return log_error(-EIO
, "Failed to create string: %d", ret
);
112 pathdup
= strdup(path
);
114 return log_error(-ENOMEM
, "Failed to duplicate string \"%s\"", path
);
116 lv
= strrchr(pathdup
, '/');
118 return log_error(-EINVAL
, "Failed to detect \"/\" in string \"%s\"", pathdup
);
121 TRACE("Parsed logical volume \"%s\"", lv
);
123 vg
= strrchr(pathdup
, '/');
125 return log_error(-EINVAL
, "Failed to detect \"/\" in string \"%s\"", pathdup
);
127 TRACE("Parsed volume group \"%s\"", vg
);
130 len
= strlen(pathdup
) + strlen(thinpool
) + 2;
131 tp
= must_realloc(NULL
, len
);
133 ret
= snprintf(tp
, len
, "%s/%s", pathdup
, thinpool
);
134 if (ret
< 0 || ret
>= len
)
135 return log_error(-EIO
, "Failed to create string: %d", ret
);
137 ret
= lvm_is_thin_pool(tp
);
138 TRACE("got %d for thin pool at path: %s", ret
, tp
);
140 return log_error(-EINVAL
, "Failed to detect whether \"%s\" is a thinpool", tp
);
142 TRACE("Detected that \"%s\" is not a thinpool", tp
);
145 TRACE("Detected \"%s\" is a thinpool", tp
);
149 cmd_args
.thinpool
= tp
;
153 cmd_args
.sigwipe
= true;
154 TRACE("Creating new lvm storage volume \"%s\" on volume group \"%s\" of size \"%s\"", lv
, vg
, sz
);
155 ret
= run_command_status(cmd_output
, sizeof(cmd_output
), lvm_create_exec_wrapper
,
158 /* If lvcreate is old and doesn't support signature wiping, try again without it.
159 * Test for exit code EINVALID_CMD_LINE(3) of lvcreate command.
161 if (WIFEXITED(ret
) && WEXITSTATUS(ret
) == 3) {
162 cmd_args
.sigwipe
= false;
163 ret
= run_command(cmd_output
, sizeof(cmd_output
), lvm_create_exec_wrapper
,
168 return log_error(-1, "Failed to create logical volume \"%s\": %s", lv
, cmd_output
);
169 TRACE("Created new lvm storage volume \"%s\" on volume group \"%s\" of size \"%s\"", lv
, vg
, sz
);
174 /* Look at "/sys/dev/block/maj:min/dm/uuid". If it contains the hardcoded LVM
175 * prefix "LVM-" then this is an lvm2 LV.
177 bool lvm_detect(const char *path
)
182 char devp
[PATH_MAX
], buf
[4];
184 if (!strncmp(path
, "lvm:", 4))
187 ret
= stat(path
, &statbuf
);
191 if (!S_ISBLK(statbuf
.st_mode
))
194 ret
= snprintf(devp
, PATH_MAX
, "/sys/dev/block/%d:%d/dm/uuid",
195 major(statbuf
.st_rdev
), minor(statbuf
.st_rdev
));
196 if (ret
< 0 || ret
>= PATH_MAX
) {
197 ERROR("Failed to create string");
201 fd
= open(devp
, O_RDONLY
);
205 ret
= read(fd
, buf
, sizeof(buf
));
207 if (ret
!= sizeof(buf
))
210 if (strncmp(buf
, "LVM-", 4))
216 int lvm_mount(struct lxc_storage
*bdev
)
220 if (strcmp(bdev
->type
, "lvm"))
223 if (!bdev
->src
|| !bdev
->dest
)
226 src
= lxc_storage_get_path(bdev
->src
, bdev
->type
);
228 /* If we might pass in data sometime, then we'll have to enrich
229 * mount_unknown_fs().
231 return mount_unknown_fs(src
, bdev
->dest
, bdev
->mntopts
);
234 int lvm_umount(struct lxc_storage
*bdev
)
236 if (strcmp(bdev
->type
, "lvm"))
239 if (!bdev
->src
|| !bdev
->dest
)
242 return umount(bdev
->dest
);
245 #define __LVSCMD "lvs --unbuffered --noheadings -o lv_attr %s 2>/dev/null"
246 int lvm_compare_lv_attr(const char *path
, int pos
, const char expected
)
248 __do_free
char *cmd
= NULL
;
249 struct lxc_popen_FILE
*f
;
255 len
= strlen(__LVSCMD
) + strlen(path
) + 1;
256 cmd
= must_realloc(NULL
, len
);
258 ret
= snprintf(cmd
, len
, __LVSCMD
, path
);
259 if (ret
< 0 || (size_t)ret
>= len
)
264 SYSERROR("popen failed");
269 if (!fgets(output
, 12, f
->f
))
272 status
= lxc_pclose(f
);
273 /* Assume either vg or lvs do not exist, default comparison to false. */
274 if (ret
|| WEXITSTATUS(status
))
277 len
= strlen(output
);
278 while (start
< len
&& output
[start
] == ' ')
281 if (start
+ pos
< len
&& output
[start
+ pos
] == expected
)
287 int lvm_is_thin_volume(const char *path
)
289 return lvm_compare_lv_attr(path
, 6, 't');
292 int lvm_is_thin_pool(const char *path
)
294 return lvm_compare_lv_attr(path
, 0, 't');
297 static inline bool fs_needs_new_uuid(const char *fstype
)
299 return strcmp(fstype
, "xfs") == 0 || strcmp(fstype
, "btrfs") == 0;
302 static int lvm_snapshot_create_new_uuid_wrapper(void *data
)
304 struct lvcreate_args
*args
= data
;
306 if (strcmp(args
->fstype
, "xfs") == 0)
307 execlp("xfs_admin", "xfs_admin", "-U", "generate", args
->lv
, (char *)NULL
);
309 if (strcmp(args
->fstype
, "btrfs") == 0)
310 execlp("btrfstune", "btrfstune", "-f", "-u", args
->lv
, (char *)NULL
);
315 static int lvm_snapshot(struct lxc_storage
*orig
, const char *path
, uint64_t size
)
317 __do_free
char *pathdup
= NULL
;
322 char cmd_output
[PATH_MAX
];
325 struct lvcreate_args cmd_args
= {0};
327 ret
= snprintf(sz
, sizeof(sz
), "%" PRIu64
"b", size
);
328 if (ret
< 0 || (size_t)ret
>= sizeof(sz
))
329 return log_error_errno(-EIO
, EIO
, "Failed to create string");
331 pathdup
= strdup(path
);
333 return log_error_errno(-ENOMEM
, ENOMEM
, "Failed to duplicate string \"%s\"", path
);
335 lv
= strrchr(pathdup
, '/');
337 return log_error_errno(-ENOENT
, ENOENT
, "Failed to detect \"/\" in string \"%s\"", pathdup
);
341 TRACE("Parsed logical volume \"%s\"", lv
);
343 /* Check if the original logical volume is backed by a thinpool, in
344 * which case we cannot specify a size that's different from the
347 origsrc
= lxc_storage_get_path(orig
->src
, "lvm");
348 ret
= lvm_is_thin_volume(origsrc
);
352 cmd_args
.thinpool
= origsrc
;
355 cmd_args
.source_lv
= origsrc
;
357 TRACE("Creating new lvm snapshot \"%s\" of \"%s\" with size \"%s\"", lv
,
359 ret
= run_command(cmd_output
, sizeof(cmd_output
),
360 lvm_snapshot_exec_wrapper
, (void *)&cmd_args
);
362 return log_error_errno(-1, errno
, "Failed to create logical volume \"%s\": %s",
365 if (detect_fs(orig
, fstype
, 100) < 0)
366 return log_error_errno(-EINVAL
, EINVAL
, "Failed to detect filesystem type for \"%s\"", origsrc
);
368 if (!fs_needs_new_uuid(fstype
))
374 cmd_args
.lv
= pathdup
;
375 cmd_args
.fstype
= fstype
;
376 ret
= run_command(cmd_output
, sizeof(cmd_output
),
377 lvm_snapshot_create_new_uuid_wrapper
, (void *)&cmd_args
);
379 return log_error_errno(-1, errno
, "Failed to create new uuid for volume \"%s\": %s",
380 pathdup
, cmd_output
);
385 int lvm_clonepaths(struct lxc_storage
*orig
, struct lxc_storage
*new,
386 const char *oldname
, const char *cname
, const char *oldpath
,
387 const char *lxcpath
, int snap
, uint64_t newsize
,
388 struct lxc_conf
*conf
)
393 if (!orig
->src
|| !orig
->dest
)
396 if (strcmp(orig
->type
, "lvm") && snap
) {
397 ERROR("LVM snapshot from \"%s\" storage driver is not supported",
402 if (strcmp(orig
->type
, "lvm")) {
403 vg
= lxc_global_config_value("lxc.bdev.lvm.vg");
404 new->src
= lxc_string_join(
406 (const char *[]){"lvm:", "dev", vg
, cname
, NULL
},
412 src
= lxc_storage_get_path(orig
->src
, orig
->type
);
416 ERROR("Failed to duplicate string \"%s\"", src
);
420 slider
= strrchr(dup
, '/');
422 ERROR("Failed to detect \"/\" in string \"%s\"", dup
);
429 new->src
= lxc_string_join(
431 (const char *[]){"lvm:", *slider
== '/' ? ++slider
: slider
,
437 ERROR("Failed to create string");
442 new->mntopts
= strdup(orig
->mntopts
);
444 ERROR("Failed to duplicate string \"%s\"", orig
->mntopts
);
449 len
= strlen(lxcpath
) + strlen(cname
) + strlen("rootfs") + 3;
450 new->dest
= malloc(len
);
452 ERROR("Failed to allocate memory");
456 ret
= snprintf(new->dest
, len
, "%s/%s/rootfs", lxcpath
, cname
);
457 if (ret
< 0 || ret
>= len
) {
458 ERROR("Failed to create string");
462 ret
= mkdir_p(new->dest
, 0755);
464 SYSERROR("Failed to create directory \"%s\"", new->dest
);
471 bool lvm_create_clone(struct lxc_conf
*conf
, struct lxc_storage
*orig
,
472 struct lxc_storage
*new, uint64_t newsize
)
476 const char *thinpool
;
477 struct rsync_data data
;
478 const char *cmd_args
[2];
479 char cmd_output
[PATH_MAX
] = {0};
480 char fstype
[100] = "ext4";
481 uint64_t size
= newsize
;
483 if (is_blktype(orig
)) {
485 if (!newsize
&& blk_getsize(orig
, &size
) < 0) {
486 ERROR("Failed to detect size of logical volume \"%s\"",
491 /* detect filesystem */
492 if (detect_fs(orig
, fstype
, 100) < 0) {
493 INFO("Failed to detect filesystem type for \"%s\"", orig
->src
);
496 } else if (!newsize
) {
497 size
= DEFAULT_FS_SIZE
;
500 src
= lxc_storage_get_path(new->src
, "lvm");
501 thinpool
= lxc_global_config_value("lxc.bdev.lvm.thin_pool");
503 ret
= do_lvm_create(src
, size
, thinpool
);
505 ERROR("Failed to create lvm storage volume \"%s\"", src
);
509 cmd_args
[0] = fstype
;
511 ret
= run_command(cmd_output
, sizeof(cmd_output
),
512 do_mkfs_exec_wrapper
, (void *)cmd_args
);
514 ERROR("Failed to create new filesystem \"%s\" for lvm storage "
515 "volume \"%s\": %s", fstype
, src
, cmd_output
);
521 ret
= run_command(cmd_output
, sizeof(cmd_output
),
522 lxc_storage_rsync_exec_wrapper
, (void *)&data
);
524 ERROR("Failed to rsync from \"%s\" to \"%s\"", orig
->dest
,
529 TRACE("Created lvm storage volume \"%s\"", new->dest
);
533 bool lvm_create_snapshot(struct lxc_conf
*conf
, struct lxc_storage
*orig
,
534 struct lxc_storage
*new, uint64_t newsize
)
538 uint64_t size
= newsize
;
540 if (is_blktype(orig
)) {
541 if (!newsize
&& blk_getsize(orig
, &size
) < 0) {
542 ERROR("Failed to detect size of logical volume \"%s\"",
546 } else if (!newsize
) {
547 size
= DEFAULT_FS_SIZE
;
550 newsrc
= lxc_storage_get_path(new->src
, "lvm");
552 ret
= lvm_snapshot(orig
, newsrc
, size
);
554 ERROR("Failed to create lvm \"%s\" snapshot of \"%s\"",
555 new->src
, orig
->src
);
559 TRACE("Created lvm snapshot \"%s\" from \"%s\"", new->dest
, orig
->dest
);
563 int lvm_destroy(struct lxc_storage
*orig
)
566 char cmd_output
[PATH_MAX
];
567 struct lvcreate_args cmd_args
= {0};
569 cmd_args
.lv
= lxc_storage_get_path(orig
->src
, "lvm");
570 ret
= run_command(cmd_output
, sizeof(cmd_output
),
571 lvm_destroy_exec_wrapper
, (void *)&cmd_args
);
573 ERROR("Failed to destroy logical volume \"%s\": %s", orig
->src
,
578 TRACE("Destroyed logical volume \"%s\"", orig
->src
);
582 int lvm_create(struct lxc_storage
*bdev
, const char *dest
, const char *n
,
583 struct bdev_specs
*specs
, const struct lxc_conf
*conf
)
585 const char *vg
, *thinpool
, *fstype
, *lv
= n
;
588 const char *cmd_args
[2];
589 char cmd_output
[PATH_MAX
];
596 vg
= lxc_global_config_value("lxc.bdev.lvm.vg");
598 thinpool
= specs
->lvm
.thinpool
;
600 thinpool
= lxc_global_config_value("lxc.bdev.lvm.thin_pool");
606 len
= strlen(vg
) + strlen(lv
) + 4 + 7;
607 bdev
->src
= malloc(len
);
609 ERROR("Failed to allocate memory");
613 ret
= snprintf(bdev
->src
, len
, "lvm:/dev/%s/%s", vg
, lv
);
614 if (ret
< 0 || ret
>= len
) {
615 ERROR("Failed to create string");
619 /* size is in bytes */
622 sz
= DEFAULT_FS_SIZE
;
624 ret
= do_lvm_create(bdev
->src
+ 4, sz
, thinpool
);
626 ERROR("Error creating new logical volume \"%s\" of size "
627 "\"%" PRIu64
" bytes\"", bdev
->src
, sz
);
631 fstype
= specs
->fstype
;
633 fstype
= DEFAULT_FSTYPE
;
635 cmd_args
[0] = fstype
;
636 cmd_args
[1] = lxc_storage_get_path(bdev
->src
, bdev
->type
);
637 ret
= run_command(cmd_output
, sizeof(cmd_output
), do_mkfs_exec_wrapper
,
640 ERROR("Failed to create new logical volume \"%s\": %s",
641 bdev
->src
, cmd_output
);
645 bdev
->dest
= strdup(dest
);
647 ERROR("Failed to duplicate string \"%s\"", dest
);
651 ret
= mkdir_p(bdev
->dest
, 0755);
653 SYSERROR("Failed to create directory \"%s\"", bdev
->dest
);
657 TRACE("Created new logical volume \"%s\"", bdev
->dest
);