xref: /netbsd-src/usr.sbin/sysinst/part_edit.c (revision 404ee5b9334f618040b6cdef96a0ff35a6fc4636)
1 /*	$NetBSD: part_edit.c,v 1.13 2019/11/16 15:50:45 joerg Exp $ */
2 
3 /*
4  * Copyright (c) 2019 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 
30 /* part_edit.c -- generic partition editing code */
31 
32 #include <sys/param.h>
33 #include <sys/types.h>
34 #include <assert.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <util.h>
39 #include "defs.h"
40 #include "md.h"
41 #include "msg_defs.h"
42 #include "menu_defs.h"
43 #include "defsizes.h"
44 #include "endian.h"
45 
46 
47 /*
48  * A structure passed to various menu functions for partition editing
49  */
50 struct part_edit_info {
51 	struct disk_partitions *parts;	/* the partitions we edit */
52 	struct disk_part_info cur;	/* current value (maybe incomplete) */
53 	part_id cur_id;			/* which partition is it? */
54 	int first_custom_opt;		/* scheme specific menu options
55 					 * start here */
56 	bool cancelled;			/* do not apply changes */
57 	bool num_changed;		/* number of partitions has changed */
58 };
59 
60 #ifndef NO_CLONES
61 struct single_clone_data {
62 	struct selected_partitions clone_src;
63 	part_id *clone_ids;	/* partition IDs in target */
64 };
65 #endif
66 struct outer_parts_data {
67 	struct arg_rv av;
68 #ifndef NO_CLONES
69 	struct single_clone_data *clones;
70 	size_t num_clone_entries;
71 #endif
72 };
73 
74 static menu_ent *part_menu_opts;		/* the currently edited partitions */
75 static menu_ent *outer_fill_part_menu_opts(const struct disk_partitions *parts, size_t *cnt);
76 static void draw_outer_part_line(menudesc *m, int opt, void *arg);
77 
78 static char 	outer_part_sep_line[MENUSTRSIZE],
79 		outer_part_title[2*MENUSTRSIZE];
80 
81 static int
82 maxline(const char *p, int *count)
83 {
84 	int m = 0, i = 0;
85 
86 	for (;; p++) {
87 		if (*p == '\n' || *p == 0) {
88 			if (i > m)
89 				m = i;
90 			(*count)++;
91 			if (*p == 0)
92 				return m;
93 			i = 0;
94 		} else {
95 			i++;
96 		}
97 	}
98 }
99 
100 int
101 err_msg_win(const char *errmsg)
102 {
103 	const char *cont;
104 	int l, l1, lines;
105 
106 	errmsg = msg_string(errmsg);
107 	cont = msg_string(MSG_Hit_enter_to_continue);
108 
109 	lines = 0;
110 	l = maxline(errmsg, &lines);
111 	l1 = maxline(cont, &lines);
112 	if (l < l1)
113 		l = l1;
114 
115 	msg_fmt_prompt_win("%s.\n%s", -1, 18, l + 5, 2+lines,
116 			NULL, NULL, 1, "%s%s", errmsg, cont);
117 	return 0;
118 }
119 
120 static int
121 set_part_type(menudesc *m, void *arg)
122 {
123 	struct part_edit_info *info = arg;
124 	const struct part_type_desc *desc;
125 	char buf[STRSIZE];
126 	const char *err;
127 
128 	if (m->cursel == 0)
129 		return 1;	/* no change */
130 
131 	desc = info->parts->pscheme->get_part_type(m->cursel-1);
132 	if (desc == NULL) {
133 		/* Create custom type */
134 		if (info->cur.nat_type != NULL)
135 			strlcpy(buf, info->cur.nat_type->short_desc,
136 			    sizeof(buf));
137 		else
138 			buf[0] = 0;
139 		for (;;) {
140 			msg_prompt_win(info->parts->pscheme->new_type_prompt,
141 			     -1, 18, 0, 0,
142 			    buf, buf, sizeof(buf));
143 			if (buf[0] == 0)
144 				break;
145 			desc = info->parts->pscheme->create_custom_part_type(
146 			    buf, &err);
147 			if (desc != NULL)
148 				break;
149 			err_msg_win(err);
150 		}
151 	}
152 
153 	info->cur.nat_type = desc;
154 	return 1;
155 }
156 
157 static void
158 set_type_label(menudesc *m, int opt, void *arg)
159 {
160 	struct part_edit_info *info = arg;
161 	const struct part_type_desc *desc;
162 
163 	if (opt == 0) {
164 		wprintw(m->mw, "%s", msg_string(MSG_Dont_change));
165 		return;
166 	}
167 
168 	desc = info->parts->pscheme->get_part_type(opt-1);
169 	if (desc == NULL) {
170 		wprintw(m->mw, "%s", msg_string(MSG_Other_kind));
171 		return;
172 	}
173 	wprintw(m->mw, "%s", desc->description);
174 }
175 
176 static int
177 edit_part_type(menudesc *m, void *arg)
178 {
179 	struct part_edit_info *info = arg;
180 	menu_ent *type_opts;
181 	int type_menu = -1;
182 	size_t popt_cnt, i;
183 
184 	/*
185 	 * We add one line at the start of the menu, and one at the
186 	 * bottom, see "set_type_label" above.
187 	 */
188 	popt_cnt =  info->parts->pscheme->get_part_types_count() + 2;
189 	type_opts = calloc(popt_cnt, sizeof(*type_opts));
190 	for (i = 0; i < popt_cnt; i++) {
191 		type_opts[i].opt_action = set_part_type;
192 	}
193 	type_menu = new_menu(NULL, type_opts, popt_cnt,
194 		13, 12, 0, 30,
195 		MC_SUBMENU | MC_SCROLL | MC_NOEXITOPT | MC_NOCLEAR,
196 		NULL, set_type_label, NULL,
197 		NULL, NULL);
198 
199 	if (type_menu != -1) {
200 		process_menu(type_menu, arg);
201 		info->num_changed = true;	/* force reload of menu */
202 	}
203 
204 	free_menu(type_menu);
205 	free(type_opts);
206 
207 	return -1;
208 }
209 
210 static int
211 edit_part_start(menudesc *m, void *arg)
212 {
213 	struct part_edit_info *marg = arg;
214 	daddr_t max_size;
215 
216 	marg->cur.start = getpartoff(marg->parts, marg->cur.start);
217 	max_size = marg->parts->pscheme->max_free_space_at(marg->parts,
218 	    marg->cur.start);
219 	if (marg->cur.size > max_size)
220 		marg->cur.size = max_size;
221 
222 	return 0;
223 }
224 
225 static int
226 edit_part_size(menudesc *m, void *arg)
227 {
228 	struct part_edit_info *marg = arg;
229 
230 	marg->cur.size = getpartsize(marg->parts, marg->cur.start,
231 	    marg->cur.size);
232 
233 	return 0;
234 }
235 
236 static int
237 edit_part_install(menudesc *m, void *arg)
238 {
239 	struct part_edit_info *marg = arg;
240 
241 	if (pm->ptstart == marg->cur.start) {
242 		pm->ptstart = 0;
243 		pm->ptsize = 0;
244 	} else {
245 		pm->ptstart = marg->cur.start;
246 		pm->ptsize = marg->cur.size;
247 	}
248 	return 0;
249 }
250 
251 static void
252 menu_opts_reload(menudesc *m, const struct disk_partitions *parts)
253 {
254 	size_t new_num;
255 
256 	free(part_menu_opts);
257 	part_menu_opts = outer_fill_part_menu_opts(parts, &new_num);
258 	m->opts = part_menu_opts;
259 	m->numopts = new_num;
260 }
261 
262 static int
263 delete_part(menudesc *m, void *arg)
264 {
265 	struct part_edit_info *marg = arg;
266 	const char *err_msg = NULL;
267 
268 	if (marg->cur_id == NO_PART)
269 		return 0;
270 
271 	if (!marg->parts->pscheme->delete_partition(marg->parts, marg->cur_id,
272 	    &err_msg))
273 		err_msg_win(err_msg);
274 
275 	marg->num_changed = true;	/* reload list of partitions */
276 	marg->cancelled = true;		/* do not write back cur data */
277 
278 	return 0;
279 }
280 
281 static void draw_outer_ptn_line(menudesc *m, int line, void *arg);
282 static void draw_outer_ptn_header(menudesc *m, void *arg);
283 
284 static int
285 part_rollback(menudesc *m, void *arg)
286 {
287 	struct part_edit_info *marg = arg;
288 
289 	marg->cancelled = true;
290 	return 0;
291 }
292 
293 static menu_ent common_ptn_edit_opts[] = {
294 #define PTN_OPT_TYPE		0
295 	{ .opt_action=edit_part_type },
296 #define PTN_OPT_START		1
297 	{ .opt_action=edit_part_start },
298 #define PTN_OPT_SIZE		2
299 	{ .opt_action=edit_part_size },
300 #define PTN_OPT_END		3
301 	{ .opt_flags=OPT_IGNORE }, /* read only "end" */
302 
303 	/*
304 	 * Only the part upto here will be used when adding a new partition
305 	 */
306 
307 #define PTN_OPT_INSTALL		4
308 	{ .opt_action=edit_part_install },
309 
310 #define	PTN_OPTS_COMMON		PTN_OPT_INSTALL	/* cut off from here for add */
311 };
312 
313 static int
314 edit_custom_opt(menudesc *m, void *arg)
315 {
316 	struct part_edit_info *marg = arg;
317 	size_t attr_no = m->cursel - marg->first_custom_opt;
318 	char line[STRSIZE];
319 
320 	switch (marg->parts->pscheme->custom_attributes[attr_no].type) {
321 	case pet_bool:
322 		marg->parts->pscheme->custom_attribute_toggle(
323 		    marg->parts, marg->cur_id, attr_no);
324 		break;
325 	case pet_cardinal:
326 	case pet_str:
327 		marg->parts->pscheme->format_custom_attribute(
328 		    marg->parts, marg->cur_id, attr_no, &marg->cur,
329 		    line, sizeof(line));
330 		msg_prompt_win(
331 		    marg->parts->pscheme->custom_attributes[attr_no].label,
332 		    -1, 18, 0, 0, line, line, sizeof(line));
333 		marg->parts->pscheme->custom_attribute_set_str(
334 		    marg->parts, marg->cur_id, attr_no, line);
335 		break;
336 	}
337 
338 	return 0;
339 }
340 
341 static menu_ent ptn_edit_opts[] = {
342 	{ .opt_name=MSG_askunits, .opt_menu=MENU_sizechoice,
343 	  .opt_flags=OPT_SUB },
344 
345 	{ .opt_name=MSG_Delete_partition,
346 	  .opt_action = delete_part, .opt_flags = OPT_EXIT },
347 
348 	{ .opt_name=MSG_cancel,
349 	  .opt_action = part_rollback, .opt_flags = OPT_EXIT },
350 };
351 
352 static menu_ent ptn_add_opts[] = {
353 	{ .opt_name=MSG_askunits, .opt_menu=MENU_sizechoice,
354 	  .opt_flags=OPT_SUB },
355 
356 	{ .opt_name=MSG_cancel,
357 	  .opt_action = part_rollback, .opt_flags = OPT_EXIT },
358 };
359 
360 /*
361  * Concatenate common_ptn_edit_opts, the partitioning scheme specific
362  * custom options and the given suffix to a single menu options array.
363  */
364 static menu_ent *
365 fill_part_edit_menu_opts(struct disk_partitions *parts,
366     bool with_custom_attrs,
367     const menu_ent *suffix, size_t suffix_count, size_t *res_cnt)
368 {
369 	size_t i;
370 	menu_ent *opts, *p;
371 	size_t count, hdr_cnt;
372 
373 	if (with_custom_attrs) {
374 		hdr_cnt = __arraycount(common_ptn_edit_opts);
375 		count = hdr_cnt + parts->pscheme->custom_attribute_count
376 		    + suffix_count;
377 	} else {
378 		hdr_cnt = PTN_OPTS_COMMON;
379 		count = hdr_cnt + suffix_count;
380 	}
381 
382 	opts = calloc(count, sizeof(*opts));
383 	if (opts == NULL) {
384 		*res_cnt = 0;
385 		return NULL;
386 	}
387 
388 	memcpy(opts, common_ptn_edit_opts,
389 	    sizeof(*opts)*hdr_cnt);
390 	p = opts + hdr_cnt;
391 	if (with_custom_attrs) {
392 		for (i = 0; i < parts->pscheme->custom_attribute_count; i++) {
393 			p->opt_action = edit_custom_opt;
394 			p++;
395 		}
396 	}
397 	memcpy(p, suffix, sizeof(*opts)*suffix_count);
398 
399 	*res_cnt = count;
400 	return opts;
401 }
402 
403 static int
404 edit_part_entry(menudesc *m, void *arg)
405 {
406 	struct outer_parts_data *pdata = arg;
407 	struct part_edit_info data = { .parts = pdata->av.arg,
408 	    .cur_id = m->cursel,
409 	    .first_custom_opt = __arraycount(common_ptn_edit_opts) };
410 	int ptn_menu;
411 	const char *err;
412 	menu_ent *opts;
413 	size_t num_opts;
414 
415 	opts = fill_part_edit_menu_opts(data.parts, true, ptn_edit_opts,
416 	    __arraycount(ptn_edit_opts), &num_opts);
417 	if (opts == NULL)
418 		return 1;
419 
420 	if (data.cur_id < data.parts->num_part)
421 		data.parts->pscheme->get_part_info(data.parts, data.cur_id,
422 		    &data.cur);
423 
424 	ptn_menu = new_menu(NULL, opts, num_opts,
425 		15, 2, 0, 54,
426 		MC_SUBMENU | MC_SCROLL | MC_NOCLEAR,
427 		draw_outer_ptn_header, draw_outer_ptn_line, NULL,
428 		NULL, MSG_Partition_OK);
429 	if (ptn_menu == -1) {
430 		free(opts);
431 		return 1;
432 	}
433 
434 	process_menu(ptn_menu, &data);
435 	free_menu(ptn_menu);
436 	free(opts);
437 
438 	if (!data.cancelled && data.cur_id < data.parts->num_part)
439 		if (!data.parts->pscheme->set_part_info(data.parts,
440 		    data.cur_id, &data.cur, &err))
441 			err_msg_win(err);
442 
443 	if (data.num_changed) {
444 		menu_opts_reload(m, data.parts);
445 		m->cursel = data.parts->num_part > 0 ? 0 : 2;
446 		return -1;
447 	}
448 
449 	return 0;
450 }
451 
452 #ifndef NO_CLONES
453 static int
454 add_part_clone(menudesc *menu, void *arg)
455 {
456 	struct outer_parts_data *pdata = arg;
457 	struct disk_partitions *parts = pdata->av.arg;
458 	struct clone_target_menu_data data;
459 	menu_ent *men;
460 	int num_men, i;
461 	struct disk_part_info sinfo, cinfo;
462 	struct disk_partitions *csrc;
463 	struct disk_part_free_space space;
464 	daddr_t offset, align;
465 	size_t s;
466 	part_id cid;
467 	struct selected_partitions selected;
468 	struct single_clone_data *new_clones;
469 
470 	if (!select_partitions(&selected, parts))
471 		return 0;
472 
473 	new_clones = realloc(pdata->clones,
474 	    sizeof(*pdata->clones)*(pdata->num_clone_entries+1));
475 	if (new_clones == NULL)
476 		return 0;
477 	pdata->num_clone_entries++;
478 	pdata->clones = new_clones;
479 	new_clones += (pdata->num_clone_entries-1);
480 	memset(new_clones, 0, sizeof *new_clones);
481 	new_clones->clone_src = selected;
482 
483 	memset(&data, 0, sizeof data);
484 	data.usage.parts = parts;
485 
486 	/* if we already have partitions, ask for the target position */
487 	if (parts->num_part > 0) {
488 		data.res = -1;
489 		num_men = parts->num_part+1;
490 		men = calloc(num_men, sizeof *men);
491 		if (men == NULL)
492 			return 0;
493 		for (i = 0; i < num_men; i++)
494 			men[i].opt_action = clone_target_select;
495 		men[num_men-1].opt_name = MSG_clone_target_end;
496 
497 		data.usage.menu = new_menu(MSG_clone_target_hdr,
498 		    men, num_men, 3, 2, 0, 65, MC_SCROLL,
499 		    NULL, draw_outer_part_line, NULL, NULL, MSG_cancel);
500 		process_menu(data.usage.menu, &data);
501 		free_menu(data.usage.menu);
502 		free(men);
503 
504 		if (data.res < 0)
505 			goto err;
506 	} else {
507 		data.res = 0;
508 	}
509 
510 	/* find selected offset from data.res and insert clones there */
511 	align = parts->pscheme->get_part_alignment(parts);
512 	offset = -1;
513 	if (data.res > 0) {
514 		for (cid = 0; cid < (size_t)data.res; cid++) {
515 			if (!parts->pscheme->get_part_info(parts, cid, &sinfo))
516 			continue;
517 			offset = sinfo.start + sinfo.size;
518 		}
519 	} else {
520 		offset = 0;
521 	}
522 
523 	new_clones->clone_ids = calloc(selected.num_sel,
524 	    sizeof(*new_clones->clone_ids));
525 	if (new_clones->clone_ids == NULL)
526 		goto err;
527 	for (s = 0; s < selected.num_sel; s++) {
528 		csrc = selected.selection[s].parts;
529 		cid = selected.selection[s].id;
530 		csrc->pscheme->get_part_info(csrc, cid, &sinfo);
531 		if (!parts->pscheme->adapt_foreign_part_info(
532 		    parts, &cinfo, csrc->pscheme, &sinfo))
533 			continue;
534 		size_t cnt = parts->pscheme->get_free_spaces(
535 		    parts, &space, 1, cinfo.size-align, align,
536 		    offset, -1);
537 		if (cnt == 0)
538 			continue;
539 		cinfo.start = space.start;
540 		cid = parts->pscheme->add_partition(
541 		    parts, &cinfo, NULL);
542 		new_clones->clone_ids[s] = cid;
543 		if (cid == NO_PART)
544 			continue;
545 		parts->pscheme->get_part_info(parts, cid, &cinfo);
546 		offset = rounddown(cinfo.start+cinfo.size+align, align);
547 	}
548 
549 	/* reload menu and start again */
550 	menu_opts_reload(menu, parts);
551 	menu->cursel = parts->num_part+1;
552 	if (parts->num_part == 0)
553 		menu->cursel++;
554 	return -1;
555 
556 err:
557 	free_selected_partitions(&selected);
558 	return -1;
559 }
560 #endif
561 
562 static int
563 add_part_entry(menudesc *m, void *arg)
564 {
565 	struct outer_parts_data *pdata = arg;
566 	struct part_edit_info data = { .parts = pdata->av.arg,
567 	    .first_custom_opt = PTN_OPTS_COMMON };
568 	int ptn_menu;
569 	daddr_t ptn_alignment;
570 	menu_ent *opts;
571 	size_t num_opts;
572 	struct disk_part_free_space space;
573 	const char *err;
574 
575 	opts = fill_part_edit_menu_opts(data.parts, false, ptn_add_opts,
576 	    __arraycount(ptn_add_opts), &num_opts);
577 	if (opts == NULL)
578 		return 1;
579 
580 	ptn_alignment = data.parts->pscheme->get_part_alignment(data.parts);
581 	data.cur_id = NO_PART;
582 	memset(&data.cur, 0, sizeof(data.cur));
583 	data.cur.nat_type = data.parts->pscheme->
584 	    get_generic_part_type(PT_root);
585 	if (data.parts->pscheme->get_free_spaces(data.parts, &space, 1,
586 	    max(sizemult, ptn_alignment), ptn_alignment, -1, -1) > 0) {
587 		data.cur.start = space.start;
588 		data.cur.size = space.size;
589 	} else {
590 		return 0;
591 	}
592 
593 	ptn_menu = new_menu(NULL, opts, num_opts,
594 		15, -1, 0, 54,
595 		MC_SUBMENU | MC_SCROLL | MC_NOCLEAR,
596 		draw_outer_ptn_header, draw_outer_ptn_line, NULL,
597 		NULL, MSG_Partition_OK);
598 	if (ptn_menu == -1) {
599 		free(opts);
600 		return 1;
601 	}
602 
603 	process_menu(ptn_menu, &data);
604 	free_menu(ptn_menu);
605 	free(opts);
606 
607 	if (!data.cancelled &&
608 	    data.parts->pscheme->add_partition(data.parts, &data.cur, &err)
609 	    == NO_PART)
610 		err_msg_win(err);
611 
612 	menu_opts_reload(m, data.parts);
613 	m->cursel = data.parts->num_part+1;
614 	if (data.parts->num_part == 0)
615 		m->cursel++;
616 	return -1;
617 }
618 
619 static void
620 draw_outer_ptn_line(menudesc *m, int line, void *arg)
621 {
622 	struct part_edit_info *marg = arg;
623 	char value[STRSIZE];
624 	static int col_width;
625 	static const char *yes, *no, *ptn_type, *ptn_start, *ptn_size,
626 	    *ptn_end, *ptn_install;
627 
628 	if (yes == NULL) {
629 		int i;
630 
631 #define CHECK(str)	i = strlen(str); if (i > col_width) col_width = i;
632 
633 		col_width = 0;
634 		yes = msg_string(MSG_Yes); CHECK(yes);
635 		no = msg_string(MSG_No); CHECK(no);
636 		ptn_type = msg_string(MSG_ptn_type); CHECK(ptn_type);
637 		ptn_start = msg_string(MSG_ptn_start); CHECK(ptn_start);
638 		ptn_size = msg_string(MSG_ptn_size); CHECK(ptn_size);
639 		ptn_end = msg_string(MSG_ptn_end); CHECK(ptn_end);
640 		ptn_install = msg_string(MSG_ptn_install); CHECK(ptn_install);
641 
642 #undef CHECK
643 
644 		for (size_t n = 0;
645 		    n < marg->parts->pscheme->custom_attribute_count; n++) {
646 			i = strlen(msg_string(
647 			    marg->parts->pscheme->custom_attributes[n].label));
648 			if (i > col_width)
649 				col_width = i;
650 		}
651 		col_width += 3;
652 	}
653 
654 	if (line >= marg->first_custom_opt) {
655 		size_t attr_no = line-marg->first_custom_opt;
656 		marg->parts->pscheme->format_custom_attribute(
657 		    marg->parts, marg->cur_id, attr_no, &marg->cur,
658 		    value, sizeof(value));
659 		wprintw(m->mw, "%*s : %s", col_width,
660 		    msg_string(
661 		    marg->parts->pscheme->custom_attributes[attr_no].label),
662 		    value);
663 		return;
664 	}
665 
666 	switch (line) {
667 	case PTN_OPT_TYPE:
668 		wprintw(m->mw, "%*s : %s", col_width, ptn_type,
669 		    marg->cur.nat_type != NULL
670 		    ? marg->cur.nat_type->description
671 		    : "-");
672 		break;
673 	case PTN_OPT_START:
674 		wprintw(m->mw, "%*s : %" PRIu64 " %s", col_width, ptn_start,
675 		    marg->cur.start / (daddr_t)sizemult, multname);
676 		break;
677 	case PTN_OPT_SIZE:
678 		wprintw(m->mw, "%*s : %" PRIu64 " %s", col_width, ptn_size,
679 		    marg->cur.size / (daddr_t)sizemult, multname);
680 		break;
681 	case PTN_OPT_END:
682 		wprintw(m->mw, "%*s : %" PRIu64 " %s", col_width, ptn_end,
683 		    (marg->cur.start + marg->cur.size - 1) / (daddr_t)sizemult,
684 		    multname);
685 		break;
686 	case PTN_OPT_INSTALL:
687 		wprintw(m->mw, "%*s : %s", col_width, ptn_install,
688 		    (marg->cur.nat_type->generic_ptype == PT_root &&
689 		    marg->cur.start == pm->ptstart) ? yes : no);
690 		break;
691 	}
692 
693 }
694 
695 static void
696 draw_outer_ptn_header(menudesc *m, void *arg)
697 {
698 	struct part_edit_info *marg = arg;
699 	size_t attr_no;
700 	bool may_change_type;
701 
702 #define DISABLE(opt,cond) \
703 	if (cond) \
704 		m->opts[opt].opt_flags |= OPT_IGNORE; \
705 	else \
706 		m->opts[opt].opt_flags &= ~OPT_IGNORE;
707 
708 	/* e.g. MBR extended partitions can only change if empty */
709 	may_change_type = marg->cur_id == NO_PART
710 	     || marg->parts->pscheme->part_type_can_change == NULL
711 	     || marg->parts->pscheme->part_type_can_change(
712 		    marg->parts, marg->cur_id);
713 
714 	DISABLE(PTN_OPT_TYPE, !may_change_type);
715 	if (!may_change_type && m->cursel == PTN_OPT_TYPE)
716 		m->cursel++;
717 	if (marg->cur_id != NO_PART) {
718 		for (int i = 0; i < m->numopts; i++) {
719 			if (m->opts[i].opt_action == delete_part) {
720 				DISABLE(i, !may_change_type);
721 			}
722 		}
723 	}
724 
725 	/* Can only install into NetBSD partition */
726 	if (marg->cur_id != NO_PART) {
727 		DISABLE(PTN_OPT_INSTALL, marg->cur.nat_type == NULL
728 		    || marg->cur.nat_type->generic_ptype != PT_root);
729 	}
730 
731 	if (marg->cur_id == NO_PART)
732 		return;
733 
734 	for (attr_no = 0; attr_no <
735 	    marg->parts->pscheme->custom_attribute_count; attr_no++) {
736 		bool writable =
737 		    marg->parts->pscheme->custom_attribute_writable(
738 			marg->parts, marg->cur_id, attr_no);
739 		DISABLE(attr_no+marg->first_custom_opt, !writable);
740 	}
741 }
742 
743 static void
744 draw_outer_part_line(menudesc *m, int opt, void *arg)
745 {
746 	struct outer_parts_data *pdata = arg;
747 	struct disk_partitions *parts = pdata->av.arg;
748 	int len;
749 	part_id pno = opt;
750 	struct disk_part_info info;
751 	char buf[SSTRSIZE], *astr, colval[STRSIZE], line[STRSIZE];
752 	size_t astr_avail, x;
753 	static char install_flag = 0;
754 
755 #define PART_ROW_USED_FMT	"%13" PRIu64 " %13" PRIu64 " %-4s"
756 
757 	len = snprintf(0, 0, PART_ROW_USED_FMT, (daddr_t)0, (daddr_t)0, "");
758 
759 	if (pno >= parts->num_part ||
760 	    !parts->pscheme->get_part_info(parts, pno, &info)) {
761 		wprintw(m->mw, "%*s", len, "");
762 		// XXX
763 		return;
764 	}
765 
766 	if (info.start == pm->ptstart &&
767 	    info.nat_type->generic_ptype == PT_root) {
768 		if (install_flag == 0)
769 			install_flag = msg_string(MSG_install_flag)[0];
770 		astr_avail = sizeof(buf)-1;
771 		buf[0] = install_flag;
772 		buf[1] = 0;
773 		astr = buf+1;
774 	} else {
775 		buf[0] = 0;
776 		astr = buf;
777 		astr_avail = sizeof(buf);
778 	}
779 	if (parts->pscheme->get_part_attr_str != NULL)
780 		parts->pscheme->get_part_attr_str(parts, pno, astr,
781 		    astr_avail);
782 
783 	daddr_t start = info.start / sizemult;
784 	daddr_t size = info.size / sizemult;
785 	wprintw(m->mw, PART_ROW_USED_FMT,
786 	    start, size, buf);
787 
788 	line[0] = 0; x = 0;
789 	for (size_t col = 0; col < parts->pscheme->edit_columns_count; col++) {
790 		if (parts->pscheme->format_partition_table_str(parts, pno,
791 		    col, colval, sizeof(colval)) && colval[0] != 0
792 		    && x < sizeof(line)-2) {
793 			for (size_t i = strlen(line); i < x; i++)
794 				line[i] = ' ';
795 			line[x] = ' ';
796 			strlcpy(line+x+1, colval, sizeof(line)-x-1);
797 		}
798 		x += parts->pscheme->edit_columns[col].width + 1;
799 	}
800 	wprintw(m->mw, "%s", line);
801 }
802 
803 static int
804 part_edit_abort(menudesc *m, void *arg)
805 {
806 	struct outer_parts_data *pdata = arg;
807 
808 	pdata->av.rv = -1;
809 	return 0;
810 }
811 
812 static menu_ent *
813 outer_fill_part_menu_opts(const struct disk_partitions *parts, size_t *cnt)
814 {
815 	menu_ent *opts, *op;
816 	size_t num_opts;
817 	size_t i;
818 	bool may_add;
819 
820 	may_add = parts->pscheme->can_add_partition(parts);
821 	num_opts = 3 + parts->num_part;
822 #ifndef NO_CLONES
823 	num_opts++;
824 #endif
825 	if (parts->num_part == 0)
826 		num_opts++;
827 	if (may_add)
828 		num_opts++;
829 	opts = calloc(num_opts, sizeof *opts);
830 	if (opts == NULL) {
831 		*cnt = 0;
832 		return NULL;
833 	}
834 
835 	/* add all exisiting partitions */
836 	for (op = opts, i = 0; i < parts->num_part && i < (num_opts-2);
837 	    op++, i++) {
838 		op->opt_flags = OPT_SUB;
839 		op->opt_action = edit_part_entry;
840 	}
841 
842 	/* if empty, hint that partitions are missing */
843 	if (parts->num_part == 0) {
844 		op->opt_name = MSG_nopart;
845 		op->opt_flags = OPT_IGNORE|OPT_NOSHORT;
846 		op++;
847 	}
848 
849 	/* separator line between partitions and actions */
850 	op->opt_name = outer_part_sep_line;
851 	op->opt_flags = OPT_IGNORE|OPT_NOSHORT;
852 	op++;
853 
854 	/* followed by new partition adder */
855 	if (may_add) {
856 		op->opt_name = MSG_addpart;
857 		op->opt_flags = OPT_SUB;
858 		op->opt_action = add_part_entry;
859 		op++;
860 	}
861 
862 #ifndef NO_CLONES
863 	/* and a partition cloner */
864 	op->opt_name = MSG_clone_from_elsewhere;
865 	op->opt_action = add_part_clone;
866 	op++;
867 #endif
868 
869 	/* and unit changer */
870 	op->opt_name = MSG_askunits;
871 	op->opt_menu = MENU_sizechoice;
872 	op->opt_flags = OPT_SUB;
873 	op->opt_action = NULL;
874 	op++;
875 
876 	/* and abort option */
877 	op->opt_name = MSG_cancel;
878 	op->opt_flags = OPT_EXIT;
879 	op->opt_action = part_edit_abort;
880 	op++;
881 
882 	/* counts are consistent? */
883 	assert((op - opts) >= 0 && (size_t)(op - opts) == num_opts);
884 
885 	*cnt = num_opts;
886 	return opts;
887 }
888 
889 static void
890 draw_outer_part_header(menudesc *m, void *arg)
891 {
892 	struct outer_parts_data *pdata = arg;
893 	struct disk_partitions *parts = pdata->av.arg;
894 	char start[SSTRSIZE], size[SSTRSIZE], col[SSTRSIZE],
895 	    *disk_info, total[SSTRSIZE], avail[SSTRSIZE];
896 	size_t sep;
897 	const char *args[3];
898 
899 	msg_display_subst(MSG_editparttable, 4,
900 	    parts->disk,
901 	    msg_string(parts->pscheme->name),
902 	    msg_string(parts->pscheme->short_name),
903 	    parts->pscheme->part_flag_desc ?
904 		msg_string(parts->pscheme->part_flag_desc)
905 		: "");
906 
907 	snprintf(total, sizeof(total), "%" PRIu64 " %s",
908 	    parts->disk_size / sizemult, multname);
909 	snprintf(avail, sizeof(total), "%" PRIu64 " %s",
910 	    parts->free_space / sizemult, multname);
911 	args[0] = parts->disk;
912 	args[1] = total;
913 	args[2] = avail;
914 	disk_info = str_arg_subst(msg_string(MSG_part_header), 3, args);
915 	msg_table_add(disk_info);
916 	free(disk_info);
917 
918 
919 	strcpy(outer_part_sep_line, "------------- ------------- ----");
920 	sep = strlen(outer_part_sep_line);
921 	snprintf(start, sizeof(start), "%s(%s)",
922 	    msg_string(MSG_part_header_col_start), multname);
923 	snprintf(size, sizeof(size), "%s(%s)",
924 	    msg_string(MSG_part_header_col_size), multname);
925 	snprintf(outer_part_title, sizeof(outer_part_title),
926 	    "   %13s %13s %-4s", start, size,
927 	    msg_string(MSG_part_header_col_flag));
928 
929 	for (size_t i = 0; i < parts->pscheme->edit_columns_count; i++) {
930 		char *np = outer_part_sep_line+sep;
931 		unsigned int w = parts->pscheme->edit_columns[i].width;
932 		snprintf(col, sizeof(col), " %*s", -w,
933 		    msg_string(parts->pscheme->edit_columns[i].title));
934 		strlcat(outer_part_title, col, sizeof(outer_part_title));
935 		if (sep < sizeof(outer_part_sep_line)-1) {
936 			*np++ = ' ';
937 			sep++;
938 		}
939 		for (unsigned int p = 0; p < w &&
940 		    sep < sizeof(outer_part_sep_line)-1; p++)
941 			*np++ = '-', sep++;
942 		*np = 0;
943 	}
944 
945 	strlcat(outer_part_title, "\n   ", sizeof(outer_part_title));
946 	strlcat(outer_part_title, outer_part_sep_line,
947 	    sizeof(outer_part_title));
948 
949 	msg_table_add("\n\n");
950 }
951 
952 /*
953  * Use the whole disk for NetBSD, but (if any) create required helper
954  * partitions (usually for booting and stuff).
955  */
956 bool
957 parts_use_wholedisk(struct disk_partitions *parts,
958     size_t add_ext_parts, const struct disk_part_info *ext_parts)
959 {
960 	part_id nbsd;
961 	struct disk_part_info info;
962 	struct disk_part_free_space space;
963 	daddr_t align;
964 	size_t i;
965 
966 	parts->pscheme->delete_all_partitions(parts);
967 	align = parts->pscheme->get_part_alignment(parts);
968 
969 	if (ext_parts != NULL) {
970 		for (i = 0; i < add_ext_parts; i++) {
971 			info = ext_parts[i];
972 			if (parts->pscheme->get_free_spaces(parts, &space,
973 			    1, info.size, align, -1, -1) != 1)
974 				return false;
975 			info.start = space.start;
976 			if (parts->pscheme->add_partition(parts, &info, NULL)
977 			    == NO_PART)
978 				return false;
979 		}
980 	}
981 
982 	if (parts->pscheme->get_free_spaces(parts, &space, 1, 3*align,
983 	    align, -1, -1) != 1)
984 		return false;
985 
986 	memset(&info, 0, sizeof(info));
987 	info.start = space.start;
988 	info.size = space.size;
989 	info.nat_type = parts->pscheme->get_generic_part_type(PT_root);
990 	nbsd = parts->pscheme->add_partition(parts, &info, NULL);
991 
992 	if (nbsd == NO_PART)
993 		return false;
994 
995 	if (!parts->pscheme->get_part_info(parts, nbsd, &info))
996 		return false;
997 
998 	if (parts->pscheme->secondary_scheme != NULL) {
999 		/* force empty secondary partitions */
1000 		parts->pscheme->secondary_partitions(parts, info.start, true);
1001 	}
1002 
1003 	pm->ptstart = info.start;
1004 	pm->ptsize = info.size;
1005 	return true;
1006 }
1007 
1008 static int
1009 set_keep_existing(menudesc *m, void *arg)
1010 {
1011 	((arg_rep_int*)arg)->rv = LY_KEEPEXISTING;
1012 	return 0;
1013 }
1014 
1015 static int
1016 set_use_only_part(menudesc *m, void *arg)
1017 {
1018 	((arg_rep_int*)arg)->rv = LY_SETSIZES;
1019 	return 0;
1020 }
1021 
1022 static int
1023 set_use_entire_disk(menudesc *m, void *arg)
1024 {
1025 	((arg_rep_int*)arg)->rv = LY_USEFULL;
1026 	return 0;
1027 }
1028 
1029 static enum layout_type
1030 ask_fullpart(struct disk_partitions *parts)
1031 {
1032 	arg_rep_int ai;
1033 	const char *args[2];
1034 	int menu;
1035 	size_t num_opts;
1036 	menu_ent options[3], *opt;
1037 	daddr_t start, size;
1038 
1039 	args[0] = msg_string(pm->parts->pscheme->name);
1040 	args[1] = msg_string(pm->parts->pscheme->short_name);
1041 	ai.args.argv = args;
1042 	ai.args.argc = 2;
1043 	ai.rv = LY_SETSIZES;
1044 
1045 	memset(options, 0, sizeof(options));
1046 	num_opts = 0;
1047 	opt = &options[0];
1048 	if (parts->pscheme->guess_install_target != NULL &&
1049 	    parts->pscheme->guess_install_target(parts, &start, &size)) {
1050 		opt->opt_name = MSG_Keep_existing_partitions;
1051 		opt->opt_flags = OPT_EXIT;
1052 		opt->opt_action = set_keep_existing;
1053 		opt++;
1054 		num_opts++;
1055 	}
1056 	opt->opt_name = MSG_Use_only_part_of_the_disk;
1057 	opt->opt_flags = OPT_EXIT;
1058 	opt->opt_action = set_use_only_part;
1059 	opt++;
1060 	num_opts++;
1061 
1062 	opt->opt_name = MSG_Use_the_entire_disk;
1063 	opt->opt_flags = OPT_EXIT;
1064 	opt->opt_action = set_use_entire_disk;
1065 	opt++;
1066 	num_opts++;
1067 
1068 	menu = new_menu(MSG_Select_your_choice, options, num_opts,
1069 	    -1, -10, 0, 0, MC_NOEXITOPT, NULL, NULL, NULL, NULL, NULL);
1070 	if (menu != -1) {
1071 		get_menudesc(menu)->expand_act = expand_all_option_texts;
1072 		process_menu(menu, &ai);
1073 		free_menu(menu);
1074 	}
1075 
1076 	return ai.rv;
1077 }
1078 
1079 /*
1080  * return (see post_edit_verify):
1081  *  0 -> abort
1082  *  1 -> re-edit
1083  *  2 -> continue installation
1084  */
1085 static int
1086 verify_outer_parts(struct disk_partitions *parts, bool quiet)
1087 {
1088 	part_id i;
1089 	int num_bsdparts;
1090 	daddr_t first_bsdstart, first_bsdsize, inst_start, inst_size;
1091 
1092 	first_bsdstart = first_bsdsize = 0;
1093 	inst_start = inst_size = 0;
1094 	num_bsdparts = 0;
1095 	for (i = 0; i < parts->num_part; i++) {
1096 		struct disk_part_info info;
1097 		if (!parts->pscheme->get_part_info(parts, i, &info))
1098 			continue;
1099 		if (!(info.flags & PTI_SEC_CONTAINER))
1100 			continue;
1101 		if (info.nat_type->generic_ptype != PT_root)
1102 			continue;
1103 		num_bsdparts++;
1104 
1105 		if (first_bsdstart == 0) {
1106 			first_bsdstart = info.start;
1107 			first_bsdsize = info.size;
1108 		}
1109 		if (inst_start == 0 && info.start == pm->ptstart) {
1110 			inst_start = info.start;
1111 			inst_size = info.size;
1112 		}
1113 	}
1114 
1115 	if (num_bsdparts == 0 ||
1116 	    (num_bsdparts > 1 && inst_start == 0)) {
1117 		if (quiet && num_bsdparts == 0)
1118 			return 0;
1119 		if (quiet && parts->pscheme->guess_install_target &&
1120 		    parts->pscheme->guess_install_target(parts,
1121 		    &inst_start, &inst_size)) {
1122 			pm->ptstart = inst_start;
1123 			pm->ptsize = inst_size;
1124 		} else {
1125 			if (num_bsdparts == 0)
1126 				msg_display_subst(MSG_nobsdpart, 2,
1127 				    msg_string(parts->pscheme->name),
1128 				    msg_string(parts->pscheme->short_name));
1129 			else
1130 				msg_display_subst(MSG_multbsdpart, 2,
1131 				    msg_string(parts->pscheme->name),
1132 				    msg_string(parts->pscheme->short_name));
1133 
1134 			return ask_reedit(parts);
1135 		}
1136 	}
1137 
1138 	if (pm->ptstart == 0) {
1139 		if (inst_start > 0) {
1140 			pm->ptstart = inst_start;
1141 			pm->ptsize = inst_size;
1142 		} else if (first_bsdstart > 0) {
1143 			pm->ptstart = first_bsdstart;
1144 			pm->ptsize = first_bsdsize;
1145 		} else if (parts->pscheme->guess_install_target &&
1146 			   parts->pscheme->guess_install_target(
1147 			   parts, &inst_start, &inst_size)) {
1148 			pm->ptstart = inst_start;
1149 			pm->ptsize = inst_size;
1150 		}
1151 	}
1152 
1153 	/*
1154 	 * post_edit_verify returns:
1155 	 *  0 -> abort
1156 	 *  1 -> re-edit
1157 	 *  2 -> continue installation
1158 	 */
1159 	if (parts->pscheme->post_edit_verify)
1160 		return parts->pscheme->post_edit_verify(parts, quiet);
1161 
1162 	return 2;
1163 }
1164 
1165 static bool
1166 ask_outer_partsizes(struct disk_partitions *parts)
1167 {
1168 	int j;
1169 	int part_menu;
1170 	size_t num_opts;
1171 #ifndef NO_CLONES
1172 	size_t i, ci;
1173 #endif
1174 	struct outer_parts_data data;
1175 
1176 	part_menu_opts = outer_fill_part_menu_opts(parts, &num_opts);
1177 	part_menu = new_menu(outer_part_title, part_menu_opts, num_opts,
1178 			0, -1, 15, 70,
1179 			MC_NOBOX|MC_ALWAYS_SCROLL|MC_NOCLEAR|MC_CONTINUOUS,
1180 			draw_outer_part_header, draw_outer_part_line, NULL,
1181 			NULL, MSG_Partition_table_ok);
1182 	if (part_menu == -1) {
1183 		free(part_menu_opts);
1184 		return false;
1185 	}
1186 
1187 	/* Default to MB, and use bios geometry for cylinder size */
1188 	set_default_sizemult(MEG/512);
1189 	if (pm->current_cylsize == 0)
1190 		pm->current_cylsize = 16065;	/* noone cares nowadays */
1191 	pm->ptstart = 0;
1192 	pm->ptsize = 0;
1193 	memset(&data, 0, sizeof data);
1194 	data.av.arg = parts;
1195 
1196 	for (;;) {
1197 		data.av.rv = 0;
1198 		process_menu(part_menu, &data);
1199 		if (data.av.rv < 0)
1200 			break;
1201 
1202 		j = verify_outer_parts(parts, false);
1203 		if (j == 0) {
1204 			data.av.rv = -1;
1205 			return false;
1206 		} else if (j == 1) {
1207 			continue;
1208 		}
1209 		break;
1210 	}
1211 
1212 #ifndef NO_CLONES
1213 	/* handle cloned partitions content copies now */
1214 	for (i = 0; i < data.num_clone_entries; i++) {
1215 		for (ci = 0; ci < data.clones[i].clone_src.num_sel; ci++) {
1216 			if (data.clones[i].clone_src.with_data)
1217 				clone_partition_data(parts,
1218 				    data.clones[i].clone_ids[ci],
1219 				    data.clones[i].clone_src.selection[ci].
1220 				    parts,
1221 				    data.clones[i].clone_src.selection[ci].id);
1222 		}
1223 	}
1224 
1225 	/* free clone data */
1226 	if (data.clones) {
1227 		for (i = 0; i < data.num_clone_entries; i++)
1228 			free_selected_partitions(&data.clones[i].clone_src);
1229 		free(data.clones);
1230 	}
1231 #endif
1232 
1233 	free_menu(part_menu);
1234 	free(part_menu_opts);
1235 
1236 	return data.av.rv == 0;
1237 }
1238 
1239 bool
1240 edit_outer_parts(struct disk_partitions *parts)
1241 {
1242 	part_id i;
1243 	enum layout_type layout;
1244 	int num_foreign_parts;
1245 
1246 	/* If targeting a wedge, do not ask for further partitioning */
1247 	if (pm && (pm->no_part || pm->no_mbr))
1248 		return true;
1249 
1250 	/* Make sure parts has been properly initialized */
1251 	assert(parts && parts->pscheme);
1252 
1253 	if (parts->pscheme->secondary_scheme == NULL)
1254 		return true;	/* no outer parts */
1255 
1256 	if (partman_go) {
1257 		layout = LY_SETSIZES;
1258 	} else {
1259 		/* Ask full/part */
1260 		const struct disk_partitioning_scheme *sec =
1261 		    parts->pscheme->secondary_scheme;
1262 
1263 		uint64_t m_size =
1264 		    DEFROOTSIZE + DEFSWAPSIZE + DEFUSRSIZE + XNEEDMB;
1265 		char min_size[5], build_size[5];
1266 		const char
1267 		    *prim_name = msg_string(parts->pscheme->name),
1268 		    *prim_short = msg_string(parts->pscheme->short_name),
1269 		    *sec_name = msg_string(sec->name),
1270 		    *sec_short = msg_string(sec->short_name);
1271 
1272 		humanize_number(min_size, sizeof(min_size),
1273 		    m_size * MEG,
1274 		    "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
1275 		humanize_number(build_size, sizeof(build_size),
1276 		     SYSTEM_BUILD_SIZE * MEG, "", HN_AUTOSCALE,
1277 		     HN_B | HN_NOSPACE | HN_DECIMAL);
1278 
1279 		msg_display_subst(MSG_fullpart, 7,
1280 		    pm->diskdev,
1281 		    prim_name, sec_name,
1282 		    prim_short, sec_short,
1283 		    min_size, build_size);
1284 		msg_display_add("\n\n");
1285 
1286 		layout = ask_fullpart(parts);
1287 	}
1288 
1289 	if (layout == LY_USEFULL) {
1290 		struct disk_part_info info;
1291 
1292 		/* Count nonempty, non-BSD partitions. */
1293 		num_foreign_parts = 0;
1294 		for (i = 0; i < parts->num_part; i++) {
1295 			if (!parts->pscheme->get_part_info(parts, i, &info))
1296 				continue;
1297 			if (info.size == 0)
1298 				continue;
1299 			if (info.flags & (PTI_PSCHEME_INTERNAL|PTI_RAW_PART))
1300 				continue;
1301 			if (info.nat_type != NULL
1302 			    && info.nat_type->generic_ptype != PT_root
1303 			    && info.nat_type->generic_ptype != PT_swap)
1304 				num_foreign_parts++;
1305 		}
1306 
1307 		/* Ask if we really want to blow away non-NetBSD stuff */
1308 		if (num_foreign_parts > 0) {
1309 			msg_display(MSG_ovrwrite);
1310 			if (!ask_noyes(NULL)) {
1311 				if (logfp)
1312 					(void)fprintf(logfp,
1313 					    "User answered no to destroy "
1314 					    "other data, aborting.\n");
1315 				return false;
1316 			}
1317 		}
1318 		if (!md_parts_use_wholedisk(parts)) {
1319 			hit_enter_to_continue(MSG_No_free_space, NULL);
1320 			return false;
1321 		}
1322 		if (parts->pscheme->post_edit_verify) {
1323 			return
1324 			    parts->pscheme->post_edit_verify(parts, true) == 2;
1325 		}
1326 		return true;
1327 	} else if (layout == LY_SETSIZES) {
1328 		return ask_outer_partsizes(parts);
1329 	} else {
1330 		return verify_outer_parts(parts, true) == 2;
1331 	}
1332 }
1333 
1334 static int
1335 set_part_scheme(menudesc *m, void *arg)
1336 {
1337 	size_t *res = arg;
1338 
1339 	*res = (size_t)m->cursel;
1340 	return 1;
1341 }
1342 
1343 const struct disk_partitioning_scheme *
1344 select_part_scheme(
1345 	struct pm_devs *dev,
1346 	const struct disk_partitioning_scheme *skip,
1347 	bool bootable,
1348 	const char *hdr)
1349 {
1350 	int ps_menu = -1;
1351 	menu_ent *opt;
1352 	char **str, *ms = NULL;
1353 	const struct disk_partitioning_scheme **options, *res;
1354 	const char *title;
1355 	size_t ndx, selected = ~0U, used;
1356 	const struct disk_partitioning_scheme *p;
1357 	bool showing_limit = false;
1358 
1359 	if (hdr == NULL)
1360 		hdr = MSG_select_part_scheme;
1361 
1362 	opt = calloc(num_available_part_schemes, sizeof *opt);
1363 	if (!opt)
1364 		return NULL;
1365 	str = calloc(num_available_part_schemes, sizeof *str);
1366 	if (!str) {
1367 		free(opt);
1368 		return NULL;
1369 	}
1370 	options = calloc(num_available_part_schemes, sizeof *options);
1371 	if (!options) {
1372 		free(str);
1373 		free(opt);
1374 		return NULL;
1375 	}
1376 
1377 	for (used = 0, ndx = 0; ndx < num_available_part_schemes; ndx++) {
1378 		p = available_part_schemes[ndx];
1379 		if (skip != NULL && p == skip)
1380 			continue;
1381 		if (bootable && p->have_boot_support != NULL &&
1382 		    !p->have_boot_support(dev->diskdev))
1383 			continue;
1384 #ifdef HAVE_MBR
1385 		if (dev->no_mbr && p->name == MSG_parttype_mbr)
1386 			continue;
1387 #endif
1388 		if (p->size_limit && dev->dlsize > p->size_limit) {
1389 			char buf[255], hum_lim[5];
1390 
1391 			humanize_number(hum_lim, sizeof(hum_lim),
1392 			    (uint64_t)p->size_limit*512UL,
1393 			    "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
1394 			sprintf(buf, "%s [%s %s]", msg_string(p->name),
1395 			    msg_string(MSG_size_limit), hum_lim);
1396 			str[used] = strdup(buf);
1397 			showing_limit = true;
1398 		} else {
1399 			str[used] = strdup(msg_string(p->name));
1400 		}
1401 		if (!str[used])
1402 			goto out;
1403 
1404 		opt[used].opt_name = str[used];
1405 		opt[used].opt_action = set_part_scheme;
1406 		options[used] = p;
1407 		used++;
1408 	}
1409 
1410 	/* do not bother to ask if there are no options */
1411 	if (used <= 1) {
1412 		selected = (used == 1) ? 0 : ~0U;
1413 		goto out;
1414 	}
1415 
1416 	if (showing_limit) {
1417 		char hum_lim[5], *tmp;
1418 		size_t total;
1419 
1420 		const char *p1 = msg_string(hdr);
1421 		const char *p2 = msg_string(MSG_select_part_limit);
1422 
1423 		humanize_number(hum_lim, sizeof(hum_lim),
1424 		    (uint64_t)dev->dlsize*512, "",
1425 		    HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
1426 
1427 		const char *args[] = { dev->diskdev, hum_lim };
1428 		char *p3 = str_arg_subst(msg_string(MSG_part_limit_disksize),
1429 		    __arraycount(args), args);
1430 
1431 		total = strlen(p1) + strlen(p2) + strlen(p3)
1432 		    + sizeof(hum_lim) + 5;
1433 		ms = tmp = malloc(total);
1434 		title = tmp;
1435 		strcpy(tmp, p1); tmp += strlen(p1);
1436 		*tmp++ = '\n'; *tmp++ = '\n';
1437 		strcpy(tmp, p2); tmp += strlen(p2);
1438 		*tmp++ = '\n'; *tmp++ = '\n';
1439 		strcpy(tmp, p3);
1440 		free(p3);
1441 		assert(strlen(ms) < total);
1442 	} else {
1443 		title = msg_string(hdr);
1444 	}
1445 	ps_menu = new_menu(title, opt, used,
1446 	    5, 5, 0, 0, 0, NULL, NULL, NULL, NULL, MSG_exit_menu_generic);
1447 	if (ps_menu != -1)
1448 		process_menu(ps_menu, &selected);
1449 out:
1450 	res = selected >= used ? NULL : options[selected];
1451 	for (ndx = 0; ndx < used; ndx++)
1452 		free(str[ndx]);
1453 	if (showing_limit && ms)
1454 		free(ms);
1455 	free(str);
1456 	free(opt);
1457 	free(options);
1458 	if (ps_menu != -1)
1459 		free_menu(ps_menu);
1460 
1461 	return res;
1462 }
1463