1/*	$NetBSD$	*/
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
22struct 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
51static 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
90static 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
135enum 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 */
143static 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
189static 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
278static 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
667int 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