]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/storage/lvm.c
storage/lvm: cleanup do_lvm_create()
[mirror_lxc.git] / src / lxc / storage / lvm.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE 1
5 #endif
6 #define __STDC_FORMAT_MACROS
7 #include <inttypes.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/sysmacros.h>
13 #include <sys/wait.h>
14 #include <unistd.h>
15
16 #include "config.h"
17 #include "log.h"
18 #include "lvm.h"
19 #include "memory_utils.h"
20 #include "rsync.h"
21 #include "storage.h"
22 #include "storage_utils.h"
23 #include "utils.h"
24
25 #ifdef MAJOR_IN_MKDEV
26 #include <sys/mkdev.h>
27 #endif
28
29 lxc_log_define(lvm, lxc);
30
31 struct lvcreate_args {
32 const char *size;
33 const char *vg;
34 const char *lv;
35 const char *thinpool;
36 const char *fstype;
37 bool sigwipe;
38
39 /* snapshot specific arguments */
40 const char *source_lv;
41 };
42
43 static int lvm_destroy_exec_wrapper(void *data)
44 {
45 struct lvcreate_args *args = data;
46
47 (void)setenv("LVM_SUPPRESS_FD_WARNINGS", "1", 1);
48 execlp("lvremove", "lvremove", "-f", args->lv, (char *)NULL);
49
50 return -1;
51 }
52
53 static int lvm_create_exec_wrapper(void *data)
54 {
55 struct lvcreate_args *args = data;
56
57 (void)setenv("LVM_SUPPRESS_FD_WARNINGS", "1", 1);
58 if (args->thinpool)
59 if(args->sigwipe)
60 execlp("lvcreate", "lvcreate", "-Wy", "--yes", "--thinpool", args->thinpool,
61 "-V", args->size, args->vg, "-n", args->lv, (char *)NULL);
62 else
63 execlp("lvcreate", "lvcreate", "-qq", "--thinpool", args->thinpool,
64 "-V", args->size, args->vg, "-n", args->lv, (char *)NULL);
65 else
66 if(args->sigwipe)
67 execlp("lvcreate", "lvcreate", "-Wy", "--yes", "-L", args->size, args->vg, "-n",
68 args->lv, (char *)NULL);
69 else
70 execlp("lvcreate", "lvcreate", "-qq", "-L", args->size, args->vg, "-n",
71 args->lv, (char *)NULL);
72
73 return -1;
74 }
75
76 static int lvm_snapshot_exec_wrapper(void *data)
77 {
78 struct lvcreate_args *args = data;
79
80 (void)setenv("LVM_SUPPRESS_FD_WARNINGS", "1", 1);
81 if (args->thinpool)
82 execlp("lvcreate", "lvcreate", "-s", "-n", args->lv,
83 args->source_lv, (char *)NULL);
84 else
85 execlp("lvcreate", "lvcreate", "-s", "-L", args->size, "-n",
86 args->lv, args->source_lv, (char *)NULL);
87
88 return -1;
89 }
90
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
96 * thin pool.
97 */
98 static int do_lvm_create(const char *path, uint64_t size, const char *thinpool)
99 {
100 __do_free char *pathdup = NULL;
101 int len, ret;
102 char *vg, *lv;
103 char cmd_output[PATH_MAX];
104 char sz[24];
105 __do_free char *tp = NULL;
106 struct lvcreate_args cmd_args = {0};
107
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);
111
112 pathdup = strdup(path);
113 if (!pathdup)
114 return log_error(-ENOMEM, "Failed to duplicate string \"%s\"", path);
115
116 lv = strrchr(pathdup, '/');
117 if (!lv)
118 return log_error(-EINVAL, "Failed to detect \"/\" in string \"%s\"", pathdup);
119 *lv = '\0';
120 lv++;
121 TRACE("Parsed logical volume \"%s\"", lv);
122
123 vg = strrchr(pathdup, '/');
124 if (!vg)
125 return log_error(-EINVAL, "Failed to detect \"/\" in string \"%s\"", pathdup);
126 vg++;
127 TRACE("Parsed volume group \"%s\"", vg);
128
129 if (thinpool) {
130 len = strlen(pathdup) + strlen(thinpool) + 2;
131 tp = must_realloc(NULL, len);
132
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);
136
137 ret = lvm_is_thin_pool(tp);
138 TRACE("got %d for thin pool at path: %s", ret, tp);
139 if (ret < 0) {
140 return log_error(-EINVAL, "Failed to detect whether \"%s\" is a thinpool", tp);
141 } else if (!ret) {
142 TRACE("Detected that \"%s\" is not a thinpool", tp);
143 tp = NULL;
144 } else {
145 TRACE("Detected \"%s\" is a thinpool", tp);
146 }
147 }
148
149 cmd_args.thinpool = tp;
150 cmd_args.vg = vg;
151 cmd_args.lv = lv;
152 cmd_args.size = sz;
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,
156 (void *)&cmd_args);
157
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.
160 */
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,
164 (void *)&cmd_args);
165 }
166
167 if (ret != 0)
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);
170
171 return ret;
172 }
173
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.
176 */
177 bool lvm_detect(const char *path)
178 {
179 int fd;
180 ssize_t ret;
181 struct stat statbuf;
182 char devp[PATH_MAX], buf[4];
183
184 if (!strncmp(path, "lvm:", 4))
185 return true;
186
187 ret = stat(path, &statbuf);
188 if (ret < 0)
189 return false;
190
191 if (!S_ISBLK(statbuf.st_mode))
192 return false;
193
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");
198 return false;
199 }
200
201 fd = open(devp, O_RDONLY);
202 if (fd < 0)
203 return false;
204
205 ret = read(fd, buf, sizeof(buf));
206 close(fd);
207 if (ret != sizeof(buf))
208 return false;
209
210 if (strncmp(buf, "LVM-", 4))
211 return false;
212
213 return true;
214 }
215
216 int lvm_mount(struct lxc_storage *bdev)
217 {
218 const char *src;
219
220 if (strcmp(bdev->type, "lvm"))
221 return -22;
222
223 if (!bdev->src || !bdev->dest)
224 return -22;
225
226 src = lxc_storage_get_path(bdev->src, bdev->type);
227
228 /* If we might pass in data sometime, then we'll have to enrich
229 * mount_unknown_fs().
230 */
231 return mount_unknown_fs(src, bdev->dest, bdev->mntopts);
232 }
233
234 int lvm_umount(struct lxc_storage *bdev)
235 {
236 if (strcmp(bdev->type, "lvm"))
237 return -22;
238
239 if (!bdev->src || !bdev->dest)
240 return -22;
241
242 return umount(bdev->dest);
243 }
244
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)
247 {
248 __do_free char *cmd = NULL;
249 struct lxc_popen_FILE *f;
250 int ret, status;
251 size_t len;
252 char output[12];
253 int start = 0;
254
255 len = strlen(__LVSCMD) + strlen(path) + 1;
256 cmd = must_realloc(NULL, len);
257
258 ret = snprintf(cmd, len, __LVSCMD, path);
259 if (ret < 0 || (size_t)ret >= len)
260 return -1;
261
262 f = lxc_popen(cmd);
263 if (!f) {
264 SYSERROR("popen failed");
265 return -1;
266 }
267
268 ret = 0;
269 if (!fgets(output, 12, f->f))
270 ret = 1;
271
272 status = lxc_pclose(f);
273 /* Assume either vg or lvs do not exist, default comparison to false. */
274 if (ret || WEXITSTATUS(status))
275 return 0;
276
277 len = strlen(output);
278 while (start < len && output[start] == ' ')
279 start++;
280
281 if (start + pos < len && output[start + pos] == expected)
282 return 1;
283
284 return 0;
285 }
286
287 int lvm_is_thin_volume(const char *path)
288 {
289 return lvm_compare_lv_attr(path, 6, 't');
290 }
291
292 int lvm_is_thin_pool(const char *path)
293 {
294 return lvm_compare_lv_attr(path, 0, 't');
295 }
296
297 static inline bool fs_needs_new_uuid(const char *fstype)
298 {
299 return strcmp(fstype, "xfs") == 0 || strcmp(fstype, "btrfs") == 0;
300 }
301
302 static int lvm_snapshot_create_new_uuid_wrapper(void *data)
303 {
304 struct lvcreate_args *args = data;
305
306 if (strcmp(args->fstype, "xfs") == 0)
307 execlp("xfs_admin", "xfs_admin", "-U", "generate", args->lv, (char *)NULL);
308
309 if (strcmp(args->fstype, "btrfs") == 0)
310 execlp("btrfstune", "btrfstune", "-f", "-u", args->lv, (char *)NULL);
311
312 return 0;
313 }
314
315 static int lvm_snapshot(struct lxc_storage *orig, const char *path, uint64_t size)
316 {
317 __do_free char *pathdup = NULL;
318 int ret;
319 char *lv;
320 char sz[24];
321 char fstype[100];
322 char cmd_output[PATH_MAX];
323 char repairchar;
324 const char *origsrc;
325 struct lvcreate_args cmd_args = {0};
326
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");
330
331 pathdup = strdup(path);
332 if (!pathdup)
333 return log_error_errno(-ENOMEM, ENOMEM, "Failed to duplicate string \"%s\"", path);
334
335 lv = strrchr(pathdup, '/');
336 if (!lv)
337 return log_error_errno(-ENOENT, ENOENT, "Failed to detect \"/\" in string \"%s\"", pathdup);
338 repairchar = *lv;
339 *lv = '\0';
340 lv++;
341 TRACE("Parsed logical volume \"%s\"", lv);
342
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
345 * original size.
346 */
347 origsrc = lxc_storage_get_path(orig->src, "lvm");
348 ret = lvm_is_thin_volume(origsrc);
349 if (ret < 0)
350 return -1;
351 else if (ret)
352 cmd_args.thinpool = origsrc;
353
354 cmd_args.lv = lv;
355 cmd_args.source_lv = origsrc;
356 cmd_args.size = sz;
357 TRACE("Creating new lvm snapshot \"%s\" of \"%s\" with size \"%s\"", lv,
358 origsrc, sz);
359 ret = run_command(cmd_output, sizeof(cmd_output),
360 lvm_snapshot_exec_wrapper, (void *)&cmd_args);
361 if (ret < 0)
362 return log_error_errno(-1, errno, "Failed to create logical volume \"%s\": %s",
363 lv, cmd_output);
364
365 if (detect_fs(orig, fstype, 100) < 0)
366 return log_error_errno(-EINVAL, EINVAL, "Failed to detect filesystem type for \"%s\"", origsrc);
367
368 if (!fs_needs_new_uuid(fstype))
369 return 0;
370
371 /* repair path */
372 lv--;
373 *lv = repairchar;
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);
378 if (ret < 0)
379 return log_error_errno(-1, errno, "Failed to create new uuid for volume \"%s\": %s",
380 pathdup, cmd_output);
381
382 return 0;
383 }
384
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)
389 {
390 int len, ret;
391 const char *vg;
392
393 if (!orig->src || !orig->dest)
394 return -1;
395
396 if (strcmp(orig->type, "lvm") && snap) {
397 ERROR("LVM snapshot from \"%s\" storage driver is not supported",
398 orig->type);
399 return -1;
400 }
401
402 if (strcmp(orig->type, "lvm")) {
403 vg = lxc_global_config_value("lxc.bdev.lvm.vg");
404 new->src = lxc_string_join(
405 "/",
406 (const char *[]){"lvm:", "dev", vg, cname, NULL},
407 false);
408 } else {
409 const char *src;
410 char *dup, *slider;
411
412 src = lxc_storage_get_path(orig->src, orig->type);
413
414 dup = strdup(src);
415 if (!dup) {
416 ERROR("Failed to duplicate string \"%s\"", src);
417 return -1;
418 }
419
420 slider = strrchr(dup, '/');
421 if (!slider) {
422 ERROR("Failed to detect \"/\" in string \"%s\"", dup);
423 free(dup);
424 return -1;
425 }
426 *slider = '\0';
427 slider = dup;
428
429 new->src = lxc_string_join(
430 "/",
431 (const char *[]){"lvm:", *slider == '/' ? ++slider : slider,
432 cname, NULL},
433 false);
434 free(dup);
435 }
436 if (!new->src) {
437 ERROR("Failed to create string");
438 return -1;
439 }
440
441 if (orig->mntopts) {
442 new->mntopts = strdup(orig->mntopts);
443 if (!new->mntopts) {
444 ERROR("Failed to duplicate string \"%s\"", orig->mntopts);
445 return -1;
446 }
447 }
448
449 len = strlen(lxcpath) + strlen(cname) + strlen("rootfs") + 3;
450 new->dest = malloc(len);
451 if (!new->dest) {
452 ERROR("Failed to allocate memory");
453 return -1;
454 }
455
456 ret = snprintf(new->dest, len, "%s/%s/rootfs", lxcpath, cname);
457 if (ret < 0 || ret >= len) {
458 ERROR("Failed to create string");
459 return -1;
460 }
461
462 ret = mkdir_p(new->dest, 0755);
463 if (ret < 0) {
464 SYSERROR("Failed to create directory \"%s\"", new->dest);
465 return -1;
466 }
467
468 return 0;
469 }
470
471 bool lvm_create_clone(struct lxc_conf *conf, struct lxc_storage *orig,
472 struct lxc_storage *new, uint64_t newsize)
473 {
474 int ret;
475 const char *src;
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;
482
483 if (is_blktype(orig)) {
484 /* detect size */
485 if (!newsize && blk_getsize(orig, &size) < 0) {
486 ERROR("Failed to detect size of logical volume \"%s\"",
487 orig->src);
488 return false;
489 }
490
491 /* detect filesystem */
492 if (detect_fs(orig, fstype, 100) < 0) {
493 INFO("Failed to detect filesystem type for \"%s\"", orig->src);
494 return false;
495 }
496 } else if (!newsize) {
497 size = DEFAULT_FS_SIZE;
498 }
499
500 src = lxc_storage_get_path(new->src, "lvm");
501 thinpool = lxc_global_config_value("lxc.bdev.lvm.thin_pool");
502
503 ret = do_lvm_create(src, size, thinpool);
504 if (ret < 0) {
505 ERROR("Failed to create lvm storage volume \"%s\"", src);
506 return false;
507 }
508
509 cmd_args[0] = fstype;
510 cmd_args[1] = src;
511 ret = run_command(cmd_output, sizeof(cmd_output),
512 do_mkfs_exec_wrapper, (void *)cmd_args);
513 if (ret < 0) {
514 ERROR("Failed to create new filesystem \"%s\" for lvm storage "
515 "volume \"%s\": %s", fstype, src, cmd_output);
516 return false;
517 }
518
519 data.orig = orig;
520 data.new = new;
521 ret = run_command(cmd_output, sizeof(cmd_output),
522 lxc_storage_rsync_exec_wrapper, (void *)&data);
523 if (ret < 0) {
524 ERROR("Failed to rsync from \"%s\" to \"%s\"", orig->dest,
525 new->dest);
526 return false;
527 }
528
529 TRACE("Created lvm storage volume \"%s\"", new->dest);
530 return true;
531 }
532
533 bool lvm_create_snapshot(struct lxc_conf *conf, struct lxc_storage *orig,
534 struct lxc_storage *new, uint64_t newsize)
535 {
536 int ret;
537 const char *newsrc;
538 uint64_t size = newsize;
539
540 if (is_blktype(orig)) {
541 if (!newsize && blk_getsize(orig, &size) < 0) {
542 ERROR("Failed to detect size of logical volume \"%s\"",
543 orig->src);
544 return -1;
545 }
546 } else if (!newsize) {
547 size = DEFAULT_FS_SIZE;
548 }
549
550 newsrc = lxc_storage_get_path(new->src, "lvm");
551
552 ret = lvm_snapshot(orig, newsrc, size);
553 if (ret < 0) {
554 ERROR("Failed to create lvm \"%s\" snapshot of \"%s\"",
555 new->src, orig->src);
556 return false;
557 }
558
559 TRACE("Created lvm snapshot \"%s\" from \"%s\"", new->dest, orig->dest);
560 return true;
561 }
562
563 int lvm_destroy(struct lxc_storage *orig)
564 {
565 int ret;
566 char cmd_output[PATH_MAX];
567 struct lvcreate_args cmd_args = {0};
568
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);
572 if (ret < 0) {
573 ERROR("Failed to destroy logical volume \"%s\": %s", orig->src,
574 cmd_output);
575 return -1;
576 }
577
578 TRACE("Destroyed logical volume \"%s\"", orig->src);
579 return 0;
580 }
581
582 int lvm_create(struct lxc_storage *bdev, const char *dest, const char *n,
583 struct bdev_specs *specs, const struct lxc_conf *conf)
584 {
585 const char *vg, *thinpool, *fstype, *lv = n;
586 uint64_t sz;
587 int ret, len;
588 const char *cmd_args[2];
589 char cmd_output[PATH_MAX];
590
591 if (!specs)
592 return -1;
593
594 vg = specs->lvm.vg;
595 if (!vg)
596 vg = lxc_global_config_value("lxc.bdev.lvm.vg");
597
598 thinpool = specs->lvm.thinpool;
599 if (!thinpool)
600 thinpool = lxc_global_config_value("lxc.bdev.lvm.thin_pool");
601
602 /* /dev/$vg/$lv */
603 if (specs->lvm.lv)
604 lv = specs->lvm.lv;
605
606 len = strlen(vg) + strlen(lv) + 4 + 7;
607 bdev->src = malloc(len);
608 if (!bdev->src) {
609 ERROR("Failed to allocate memory");
610 return -1;
611 }
612
613 ret = snprintf(bdev->src, len, "lvm:/dev/%s/%s", vg, lv);
614 if (ret < 0 || ret >= len) {
615 ERROR("Failed to create string");
616 return -1;
617 }
618
619 /* size is in bytes */
620 sz = specs->fssize;
621 if (!sz)
622 sz = DEFAULT_FS_SIZE;
623
624 ret = do_lvm_create(bdev->src + 4, sz, thinpool);
625 if (ret < 0) {
626 ERROR("Error creating new logical volume \"%s\" of size "
627 "\"%" PRIu64 " bytes\"", bdev->src, sz);
628 return -1;
629 }
630
631 fstype = specs->fstype;
632 if (!fstype)
633 fstype = DEFAULT_FSTYPE;
634
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,
638 (void *)cmd_args);
639 if (ret < 0) {
640 ERROR("Failed to create new logical volume \"%s\": %s",
641 bdev->src, cmd_output);
642 return -1;
643 }
644
645 bdev->dest = strdup(dest);
646 if (!bdev->dest) {
647 ERROR("Failed to duplicate string \"%s\"", dest);
648 return -1;
649 }
650
651 ret = mkdir_p(bdev->dest, 0755);
652 if (ret < 0) {
653 SYSERROR("Failed to create directory \"%s\"", bdev->dest);
654 return -1;
655 }
656
657 TRACE("Created new logical volume \"%s\"", bdev->dest);
658 return 0;
659 }