1 /* $NetBSD: lvresize.c,v 1.1.1.3 2009/12/02 00:25:53 haad Exp $ */
2
3 /*
4 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
5 * Copyright (C) 2004-2009 Red Hat, Inc. All rights reserved.
6 *
7 * This file is part of LVM2.
8 *
9 * This copyrighted material is made available to anyone wishing to use,
10 * modify, copy, or redistribute it subject to the terms and conditions
11 * of the GNU Lesser General Public License v.2.1.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, write to the Free Software Foundation,
15 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 */
17
18 #include "tools.h"
19
20 #define SIZE_BUF 128
21
22 struct lvresize_params {
23 const char *vg_name;
24 const char *lv_name;
25
26 uint32_t stripes;
27 uint32_t stripe_size;
28 uint32_t mirrors;
29
30 const struct segment_type *segtype;
31
32 /* size */
33 uint32_t extents;
34 uint64_t size;
35 sign_t sign;
36 percent_t percent;
37
38 enum {
39 LV_ANY = 0,
40 LV_REDUCE = 1,
41 LV_EXTEND = 2
42 } resize;
43
44 int resizefs;
45 int nofsck;
46
47 int argc;
48 char **argv;
49 };
50
_validate_stripesize(struct cmd_context * cmd,const struct volume_group * vg,struct lvresize_params * lp)51 static int _validate_stripesize(struct cmd_context *cmd,
52 const struct volume_group *vg,
53 struct lvresize_params *lp)
54 {
55 if (arg_sign_value(cmd, stripesize_ARG, 0) == SIGN_MINUS) {
56 log_error("Stripesize may not be negative.");
57 return 0;
58 }
59
60 if (arg_uint_value(cmd, stripesize_ARG, 0) > STRIPE_SIZE_LIMIT * 2) {
61 log_error("Stripe size cannot be larger than %s",
62 display_size(cmd, (uint64_t) STRIPE_SIZE_LIMIT));
63 return 0;
64 }
65
66 if (!(vg->fid->fmt->features & FMT_SEGMENTS))
67 log_warn("Varied stripesize not supported. Ignoring.");
68 else if (arg_uint_value(cmd, stripesize_ARG, 0) > vg->extent_size * 2) {
69 log_error("Reducing stripe size %s to maximum, "
70 "physical extent size %s",
71 display_size(cmd,
72 (uint64_t) arg_uint_value(cmd, stripesize_ARG, 0)),
73 display_size(cmd, (uint64_t) vg->extent_size));
74 lp->stripe_size = vg->extent_size;
75 } else
76 lp->stripe_size = arg_uint_value(cmd, stripesize_ARG, 0);
77
78 if (lp->mirrors) {
79 log_error("Mirrors and striping cannot be combined yet.");
80 return 0;
81 }
82 if (lp->stripe_size & (lp->stripe_size - 1)) {
83 log_error("Stripe size must be power of 2");
84 return 0;
85 }
86
87 return 1;
88 }
89
_request_confirmation(struct cmd_context * cmd,const struct volume_group * vg,const struct logical_volume * lv,const struct lvresize_params * lp)90 static int _request_confirmation(struct cmd_context *cmd,
91 const struct volume_group *vg,
92 const struct logical_volume *lv,
93 const struct lvresize_params *lp)
94 {
95 struct lvinfo info;
96
97 memset(&info, 0, sizeof(info));
98
99 if (!lv_info(cmd, lv, &info, 1, 0) && driver_version(NULL, 0)) {
100 log_error("lv_info failed: aborting");
101 return 0;
102 }
103
104 if (lp->resizefs) {
105 if (!info.exists) {
106 log_error("Logical volume %s must be activated "
107 "before resizing filesystem", lp->lv_name);
108 return 0;
109 }
110 return 1;
111 }
112
113 if (!info.exists)
114 return 1;
115
116 log_warn("WARNING: Reducing active%s logical volume to %s",
117 info.open_count ? " and open" : "",
118 display_size(cmd, (uint64_t) lp->extents * vg->extent_size));
119
120 log_warn("THIS MAY DESTROY YOUR DATA (filesystem etc.)");
121
122 if (!arg_count(cmd, force_ARG)) {
123 if (yes_no_prompt("Do you really want to reduce %s? [y/n]: ",
124 lp->lv_name) == 'n') {
125 log_print("Logical volume %s NOT reduced", lp->lv_name);
126 return 0;
127 }
128 if (sigint_caught())
129 return 0;
130 }
131
132 return 1;
133 }
134
135 enum fsadm_cmd_e { FSADM_CMD_CHECK, FSADM_CMD_RESIZE };
136 #define FSADM_CMD "fsadm"
137 #define FSADM_CMD_MAX_ARGS 6
138
139 /*
140 * FSADM_CMD --dry-run --verbose --force check lv_path
141 * FSADM_CMD --dry-run --verbose --force resize lv_path size
142 */
_fsadm_cmd(struct cmd_context * cmd,const struct volume_group * vg,const struct lvresize_params * lp,enum fsadm_cmd_e fcmd)143 static int _fsadm_cmd(struct cmd_context *cmd,
144 const struct volume_group *vg,
145 const struct lvresize_params *lp,
146 enum fsadm_cmd_e fcmd)
147 {
148 char lv_path[PATH_MAX];
149 char size_buf[SIZE_BUF];
150 const char *argv[FSADM_CMD_MAX_ARGS + 2];
151 unsigned i = 0;
152
153 argv[i++] = FSADM_CMD;
154
155 if (test_mode())
156 argv[i++] = "--dry-run";
157
158 if (verbose_level() >= _LOG_NOTICE)
159 argv[i++] = "--verbose";
160
161 if (arg_count(cmd, force_ARG))
162 argv[i++] = "--force";
163
164 argv[i++] = (fcmd == FSADM_CMD_RESIZE) ? "resize" : "check";
165
166 if (dm_snprintf(lv_path, PATH_MAX, "%s%s/%s", cmd->dev_dir, lp->vg_name,
167 lp->lv_name) < 0) {
168 log_error("Couldn't create LV path for %s", lp->lv_name);
169 return 0;
170 }
171
172 argv[i++] = lv_path;
173
174 if (fcmd == FSADM_CMD_RESIZE) {
175 if (dm_snprintf(size_buf, SIZE_BUF, "%" PRIu64 "K",
176 (uint64_t) lp->extents * vg->extent_size / 2) < 0) {
177 log_error("Couldn't generate new LV size string");
178 return 0;
179 }
180
181 argv[i++] = size_buf;
182 }
183
184 argv[i] = NULL;
185
186 return exec_cmd(cmd, argv);
187 }
188
_lvresize_params(struct cmd_context * cmd,int argc,char ** argv,struct lvresize_params * lp)189 static int _lvresize_params(struct cmd_context *cmd, int argc, char **argv,
190 struct lvresize_params *lp)
191 {
192 const char *cmd_name;
193 char *st;
194 unsigned dev_dir_found = 0;
195
196 lp->sign = SIGN_NONE;
197 lp->resize = LV_ANY;
198
199 cmd_name = command_name(cmd);
200 if (!strcmp(cmd_name, "lvreduce"))
201 lp->resize = LV_REDUCE;
202 if (!strcmp(cmd_name, "lvextend"))
203 lp->resize = LV_EXTEND;
204
205 /*
206 * Allow omission of extents and size if the user has given us
207 * one or more PVs. Most likely, the intent was "resize this
208 * LV the best you can with these PVs"
209 */
210 if ((arg_count(cmd, extents_ARG) + arg_count(cmd, size_ARG) == 0) &&
211 (argc >= 2)) {
212 lp->extents = 100;
213 lp->percent = PERCENT_PVS;
214 lp->sign = SIGN_PLUS;
215 } else if ((arg_count(cmd, extents_ARG) +
216 arg_count(cmd, size_ARG) != 1)) {
217 log_error("Please specify either size or extents but not "
218 "both.");
219 return 0;
220 }
221
222 if (arg_count(cmd, extents_ARG)) {
223 lp->extents = arg_uint_value(cmd, extents_ARG, 0);
224 lp->sign = arg_sign_value(cmd, extents_ARG, SIGN_NONE);
225 lp->percent = arg_percent_value(cmd, extents_ARG, PERCENT_NONE);
226 }
227
228 /* Size returned in kilobyte units; held in sectors */
229 if (arg_count(cmd, size_ARG)) {
230 lp->size = arg_uint64_value(cmd, size_ARG, UINT64_C(0));
231 lp->sign = arg_sign_value(cmd, size_ARG, SIGN_NONE);
232 lp->percent = PERCENT_NONE;
233 }
234
235 if (lp->resize == LV_EXTEND && lp->sign == SIGN_MINUS) {
236 log_error("Negative argument not permitted - use lvreduce");
237 return 0;
238 }
239
240 if (lp->resize == LV_REDUCE && lp->sign == SIGN_PLUS) {
241 log_error("Positive sign not permitted - use lvextend");
242 return 0;
243 }
244
245 lp->resizefs = arg_is_set(cmd, resizefs_ARG);
246 lp->nofsck = arg_is_set(cmd, nofsck_ARG);
247
248 if (!argc) {
249 log_error("Please provide the logical volume name");
250 return 0;
251 }
252
253 lp->lv_name = argv[0];
254 argv++;
255 argc--;
256
257 if (!(lp->lv_name = skip_dev_dir(cmd, lp->lv_name, &dev_dir_found)) ||
258 !(lp->vg_name = extract_vgname(cmd, lp->lv_name))) {
259 log_error("Please provide a volume group name");
260 return 0;
261 }
262
263 if (!validate_name(lp->vg_name)) {
264 log_error("Volume group name %s has invalid characters",
265 lp->vg_name);
266 return 0;
267 }
268
269 if ((st = strrchr(lp->lv_name, '/')))
270 lp->lv_name = st + 1;
271
272 lp->argc = argc;
273 lp->argv = argv;
274
275 return 1;
276 }
277
_lvresize(struct cmd_context * cmd,struct volume_group * vg,struct lvresize_params * lp)278 static int _lvresize(struct cmd_context *cmd, struct volume_group *vg,
279 struct lvresize_params *lp)
280 {
281 struct logical_volume *lv;
282 struct lvinfo info;
283 uint32_t stripesize_extents = 0;
284 uint32_t seg_stripes = 0, seg_stripesize = 0, seg_size = 0;
285 uint32_t seg_mirrors = 0;
286 uint32_t extents_used = 0;
287 uint32_t size_rest;
288 uint32_t pv_extent_count = 0;
289 alloc_policy_t alloc;
290 struct logical_volume *lock_lv;
291 struct lv_list *lvl;
292 struct lv_segment *seg;
293 uint32_t seg_extents;
294 uint32_t sz, str;
295 struct dm_list *pvh = NULL;
296
297 /* does LV exist? */
298 if (!(lvl = find_lv_in_vg(vg, lp->lv_name))) {
299 log_error("Logical volume %s not found in volume group %s",
300 lp->lv_name, lp->vg_name);
301 return ECMD_FAILED;
302 }
303
304 if (arg_count(cmd, stripes_ARG)) {
305 if (vg->fid->fmt->features & FMT_SEGMENTS)
306 lp->stripes = arg_uint_value(cmd, stripes_ARG, 1);
307 else
308 log_warn("Varied striping not supported. Ignoring.");
309 }
310
311 if (arg_count(cmd, mirrors_ARG)) {
312 if (vg->fid->fmt->features & FMT_SEGMENTS)
313 lp->mirrors = arg_uint_value(cmd, mirrors_ARG, 1) + 1;
314 else
315 log_warn("Mirrors not supported. Ignoring.");
316 if (arg_sign_value(cmd, mirrors_ARG, 0) == SIGN_MINUS) {
317 log_error("Mirrors argument may not be negative");
318 return EINVALID_CMD_LINE;
319 }
320 }
321
322 if (arg_count(cmd, stripesize_ARG) &&
323 !_validate_stripesize(cmd, vg, lp))
324 return EINVALID_CMD_LINE;
325
326 lv = lvl->lv;
327
328 if (lv->status & LOCKED) {
329 log_error("Can't resize locked LV %s", lv->name);
330 return ECMD_FAILED;
331 }
332
333 if (lv->status & CONVERTING) {
334 log_error("Can't resize %s while lvconvert in progress", lv->name);
335 return ECMD_FAILED;
336 }
337
338 alloc = arg_uint_value(cmd, alloc_ARG, lv->alloc);
339
340 if (lp->size) {
341 if (lp->size % vg->extent_size) {
342 if (lp->sign == SIGN_MINUS)
343 lp->size -= lp->size % vg->extent_size;
344 else
345 lp->size += vg->extent_size -
346 (lp->size % vg->extent_size);
347
348 log_print("Rounding up size to full physical extent %s",
349 display_size(cmd, (uint64_t) lp->size));
350 }
351
352 lp->extents = lp->size / vg->extent_size;
353 }
354
355 if (!(pvh = lp->argc ? create_pv_list(cmd->mem, vg, lp->argc,
356 lp->argv, 1) : &vg->pvs)) {
357 stack;
358 return ECMD_FAILED;
359 }
360
361 switch(lp->percent) {
362 case PERCENT_VG:
363 lp->extents = lp->extents * vg->extent_count / 100;
364 break;
365 case PERCENT_FREE:
366 lp->extents = lp->extents * vg->free_count / 100;
367 break;
368 case PERCENT_LV:
369 lp->extents = lp->extents * lv->le_count / 100;
370 break;
371 case PERCENT_PVS:
372 if (lp->argc) {
373 pv_extent_count = pv_list_extents_free(pvh);
374 lp->extents = lp->extents * pv_extent_count / 100;
375 } else
376 lp->extents = lp->extents * vg->extent_count / 100;
377 break;
378 case PERCENT_NONE:
379 break;
380 }
381
382 if (lp->sign == SIGN_PLUS)
383 lp->extents += lv->le_count;
384
385 if (lp->sign == SIGN_MINUS) {
386 if (lp->extents >= lv->le_count) {
387 log_error("Unable to reduce %s below 1 extent",
388 lp->lv_name);
389 return EINVALID_CMD_LINE;
390 }
391
392 lp->extents = lv->le_count - lp->extents;
393 }
394
395 if (!lp->extents) {
396 log_error("New size of 0 not permitted");
397 return EINVALID_CMD_LINE;
398 }
399
400 if (lp->extents == lv->le_count) {
401 if (!lp->resizefs) {
402 log_error("New size (%d extents) matches existing size "
403 "(%d extents)", lp->extents, lv->le_count);
404 return EINVALID_CMD_LINE;
405 }
406 lp->resize = LV_EXTEND; /* lets pretend zero size extension */
407 }
408
409 seg_size = lp->extents - lv->le_count;
410
411 /* Use segment type of last segment */
412 dm_list_iterate_items(seg, &lv->segments) {
413 lp->segtype = seg->segtype;
414 }
415
416 /* FIXME Support LVs with mixed segment types */
417 if (lp->segtype != arg_ptr_value(cmd, type_ARG, lp->segtype)) {
418 log_error("VolumeType does not match (%s)", lp->segtype->name);
419 return EINVALID_CMD_LINE;
420 }
421
422 /* If extending, find stripes, stripesize & size of last segment */
423 if ((lp->extents > lv->le_count) &&
424 !(lp->stripes == 1 || (lp->stripes > 1 && lp->stripe_size))) {
425 dm_list_iterate_items(seg, &lv->segments) {
426 if (!seg_is_striped(seg))
427 continue;
428
429 sz = seg->stripe_size;
430 str = seg->area_count;
431
432 if ((seg_stripesize && seg_stripesize != sz &&
433 !lp->stripe_size) ||
434 (seg_stripes && seg_stripes != str && !lp->stripes)) {
435 log_error("Please specify number of "
436 "stripes (-i) and stripesize (-I)");
437 return EINVALID_CMD_LINE;
438 }
439
440 seg_stripesize = sz;
441 seg_stripes = str;
442 }
443
444 if (!lp->stripes)
445 lp->stripes = seg_stripes;
446
447 if (!lp->stripe_size && lp->stripes > 1) {
448 if (seg_stripesize) {
449 log_print("Using stripesize of last segment %s",
450 display_size(cmd, (uint64_t) seg_stripesize));
451 lp->stripe_size = seg_stripesize;
452 } else {
453 lp->stripe_size =
454 find_config_tree_int(cmd,
455 "metadata/stripesize",
456 DEFAULT_STRIPESIZE) * 2;
457 log_print("Using default stripesize %s",
458 display_size(cmd, (uint64_t) lp->stripe_size));
459 }
460 }
461 }
462
463 /* If extending, find mirrors of last segment */
464 if ((lp->extents > lv->le_count)) {
465 dm_list_iterate_back_items(seg, &lv->segments) {
466 if (seg_is_mirrored(seg))
467 seg_mirrors = lv_mirror_count(seg->lv);
468 else
469 seg_mirrors = 0;
470 break;
471 }
472 if (!arg_count(cmd, mirrors_ARG) && seg_mirrors) {
473 log_print("Extending %" PRIu32 " mirror images.",
474 seg_mirrors);
475 lp->mirrors = seg_mirrors;
476 }
477 if ((arg_count(cmd, mirrors_ARG) || seg_mirrors) &&
478 (lp->mirrors != seg_mirrors)) {
479 log_error("Cannot vary number of mirrors in LV yet.");
480 return EINVALID_CMD_LINE;
481 }
482 }
483
484 /* If reducing, find stripes, stripesize & size of last segment */
485 if (lp->extents < lv->le_count) {
486 extents_used = 0;
487
488 if (lp->stripes || lp->stripe_size || lp->mirrors)
489 log_error("Ignoring stripes, stripesize and mirrors "
490 "arguments when reducing");
491
492 dm_list_iterate_items(seg, &lv->segments) {
493 seg_extents = seg->len;
494
495 if (seg_is_striped(seg)) {
496 seg_stripesize = seg->stripe_size;
497 seg_stripes = seg->area_count;
498 }
499
500 if (seg_is_mirrored(seg))
501 seg_mirrors = lv_mirror_count(seg->lv);
502 else
503 seg_mirrors = 0;
504
505 if (lp->extents <= extents_used + seg_extents)
506 break;
507
508 extents_used += seg_extents;
509 }
510
511 seg_size = lp->extents - extents_used;
512 lp->stripe_size = seg_stripesize;
513 lp->stripes = seg_stripes;
514 lp->mirrors = seg_mirrors;
515 }
516
517 if (lp->stripes > 1 && !lp->stripe_size) {
518 log_error("Stripesize for striped segment should not be 0!");
519 return EINVALID_CMD_LINE;
520 }
521
522 if ((lp->stripes > 1)) {
523 if (!(stripesize_extents = lp->stripe_size / vg->extent_size))
524 stripesize_extents = 1;
525
526 if ((size_rest = seg_size % (lp->stripes * stripesize_extents))) {
527 log_print("Rounding size (%d extents) down to stripe "
528 "boundary size for segment (%d extents)",
529 lp->extents, lp->extents - size_rest);
530 lp->extents = lp->extents - size_rest;
531 }
532
533 if (lp->stripe_size < STRIPE_SIZE_MIN) {
534 log_error("Invalid stripe size %s",
535 display_size(cmd, (uint64_t) lp->stripe_size));
536 return EINVALID_CMD_LINE;
537 }
538 }
539
540 if (lp->extents < lv->le_count) {
541 if (lp->resize == LV_EXTEND) {
542 log_error("New size given (%d extents) not larger "
543 "than existing size (%d extents)",
544 lp->extents, lv->le_count);
545 return EINVALID_CMD_LINE;
546 }
547 lp->resize = LV_REDUCE;
548 } else if (lp->extents > lv->le_count) {
549 if (lp->resize == LV_REDUCE) {
550 log_error("New size given (%d extents) not less than "
551 "existing size (%d extents)", lp->extents,
552 lv->le_count);
553 return EINVALID_CMD_LINE;
554 }
555 lp->resize = LV_EXTEND;
556 }
557
558 if (lv_is_origin(lv)) {
559 if (lp->resize == LV_REDUCE) {
560 log_error("Snapshot origin volumes cannot be reduced "
561 "in size yet.");
562 return ECMD_FAILED;
563 }
564
565 memset(&info, 0, sizeof(info));
566
567 if (lv_info(cmd, lv, &info, 0, 0) && info.exists) {
568 log_error("Snapshot origin volumes can be resized "
569 "only while inactive: try lvchange -an");
570 return ECMD_FAILED;
571 }
572 }
573
574 if ((lp->resize == LV_REDUCE) && lp->argc)
575 log_warn("Ignoring PVs on command line when reducing");
576
577 /* Request confirmation before operations that are often mistakes. */
578 if ((lp->resizefs || (lp->resize == LV_REDUCE)) &&
579 !_request_confirmation(cmd, vg, lv, lp)) {
580 stack;
581 return ECMD_FAILED;
582 }
583
584 if (lp->resizefs) {
585 if (!lp->nofsck &&
586 !_fsadm_cmd(cmd, vg, lp, FSADM_CMD_CHECK)) {
587 stack;
588 return ECMD_FAILED;
589 }
590
591 if ((lp->resize == LV_REDUCE) &&
592 !_fsadm_cmd(cmd, vg, lp, FSADM_CMD_RESIZE)) {
593 stack;
594 return ECMD_FAILED;
595 }
596 }
597
598 if (!archive(vg)) {
599 stack;
600 return ECMD_FAILED;
601 }
602
603 log_print("%sing logical volume %s to %s",
604 (lp->resize == LV_REDUCE) ? "Reduc" : "Extend",
605 lp->lv_name,
606 display_size(cmd, (uint64_t) lp->extents * vg->extent_size));
607
608 if (lp->resize == LV_REDUCE) {
609 if (!lv_reduce(lv, lv->le_count - lp->extents)) {
610 stack;
611 return ECMD_FAILED;
612 }
613 } else if ((lp->extents > lv->le_count) && /* Ensure we extend */
614 !lv_extend(lv, lp->segtype, lp->stripes,
615 lp->stripe_size, lp->mirrors,
616 lp->extents - lv->le_count,
617 NULL, 0u, 0u, pvh, alloc)) {
618 stack;
619 return ECMD_FAILED;
620 }
621
622 /* store vg on disk(s) */
623 if (!vg_write(vg)) {
624 stack;
625 return ECMD_FAILED;
626 }
627
628 /* If snapshot, must suspend all associated devices */
629 if (lv_is_cow(lv))
630 lock_lv = origin_from_cow(lv);
631 else
632 lock_lv = lv;
633
634 if (!suspend_lv(cmd, lock_lv)) {
635 log_error("Failed to suspend %s", lp->lv_name);
636 vg_revert(vg);
637 backup(vg);
638 return ECMD_FAILED;
639 }
640
641 if (!vg_commit(vg)) {
642 stack;
643 resume_lv(cmd, lock_lv);
644 backup(vg);
645 return ECMD_FAILED;
646 }
647
648 if (!resume_lv(cmd, lock_lv)) {
649 log_error("Problem reactivating %s", lp->lv_name);
650 backup(vg);
651 return ECMD_FAILED;
652 }
653
654 backup(vg);
655
656 log_print("Logical volume %s successfully resized", lp->lv_name);
657
658 if (lp->resizefs && (lp->resize == LV_EXTEND) &&
659 !_fsadm_cmd(cmd, vg, lp, FSADM_CMD_RESIZE)) {
660 stack;
661 return ECMD_FAILED;
662 }
663
664 return ECMD_PROCESSED;
665 }
666
lvresize(struct cmd_context * cmd,int argc,char ** argv)667 int lvresize(struct cmd_context *cmd, int argc, char **argv)
668 {
669 struct lvresize_params lp;
670 struct volume_group *vg;
671 int r;
672
673 memset(&lp, 0, sizeof(lp));
674
675 if (!_lvresize_params(cmd, argc, argv, &lp))
676 return EINVALID_CMD_LINE;
677
678 log_verbose("Finding volume group %s", lp.vg_name);
679 vg = vg_read_for_update(cmd, lp.vg_name, NULL, 0);
680 if (vg_read_error(vg)) {
681 vg_release(vg);
682 stack;
683 return ECMD_FAILED;
684 }
685
686 if (!(r = _lvresize(cmd, vg, &lp)))
687 stack;
688
689 unlock_and_release_vg(cmd, vg, lp.vg_name);
690
691 return r;
692 }
693