/* $NetBSD: bsddisklabel.c,v 1.72 2023/01/06 18:19:27 martin Exp $ */
/*
* Copyright 1997 Piermont Information Systems Inc.
* All rights reserved.
*
* Based on code written by Philip A. Nelson for Piermont Information
* Systems Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of Piermont Information Systems Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
/* bsddisklabel.c -- generate standard BSD disklabel */
/* Included by appropriate arch/XXXX/md.c */
for (;;) {
msg_prompt_win(partman_go?MSG_askfsmountadv:MSG_askfsmount,
-1, 18, 0, 0, NULL, new_mp, sizeof(new_mp));
if (new_mp[0] == 0)
return 0;
if (new_mp[0] != '/') {
/* we need absolute mount paths */
memmove(new_mp+1, new_mp, sizeof(new_mp)-1);
new_mp[0] = '/';
}
/* duplicates? */
bool duplicate = false;
for (size_t i = 0; i < pset->num; i++) {
if (strcmp(pset->infos[i].mount,
new_mp) == 0) {
args = new_mp;
err = str_arg_subst(
msg_string(MSG_mp_already_exists),
1, &args);
err_msg_win(err);
free(err);
duplicate = true;
break;
}
}
if (!duplicate)
break;
}
m = realloc(pset->menu_opts, (pset->num+5)*sizeof(*pset->menu_opts));
if (m == NULL)
return 0;
p = realloc(pset->infos, (pset->num+1)*sizeof(*pset->infos));
if (p == NULL)
return 0;
if (pset->cur_free_space == 0 && p->size == 0 &&
!(p->flags & PUIFLG_JUST_MOUNTPOINT))
/* Don't allow 'free_parts' to go negative */
return 0;
if (p->cur_part_id != NO_PART) {
rv = 0;
process_menu(MENU_ptnsize_replace_existing_partition, &rv);
if (rv == 0)
return 0;
if (!pset->parts->pscheme->delete_partition(pset->parts,
p->cur_part_id, &err_msg)) {
if (err_msg)
err_msg_win(err_msg);
return 0;
}
p->cur_part_id = NO_PART;
/*
* All other part ids are invalid now too - update them!
*/
for (i = 0; i < pset->num; i++) {
if (pset->infos[i].cur_part_id == NO_PART)
continue;
pset->infos[i].cur_part_id =
find_part_at(pset->parts, pset->infos[i].cur_start);
}
}
/* Some special cases when /usr is first given a size */
if (old_size == 0 && non_zero &&
strcmp(p->mount, "/usr") == 0) {
for (i = 0; i < pset->num; i++) {
if (strcmp(pset->infos[i].mount, "/") == 0) {
root = i;
break;
}
}
/* Remove space for /usr from / */
if (root < pset->num &&
pset->infos[root].cur_part_id == NO_PART &&
pset->infos[root].size ==
pset->infos[root].def_size) {
/*
* root partition does not yet exist and
* has default size
*/
pset->infos[root].size -= p->def_size;
pset->cur_free_space += p->def_size;
}
/*
* hack to add free space to /usr if
* previously / got it
*/
if (pset->infos[root].flags & PUIFLAG_EXTEND)
extend = true;
}
if (new_size_val < 0)
continue;
size = new_size_val;
break;
}
daddr_t align = pset->parts->pscheme->get_part_alignment(pset->parts);
if (!is_ram_size) {
size = NUMSEC(size, mult, align);
}
if (p->flags & PUIFLAG_EXTEND)
p->flags &= ~PUIFLAG_EXTEND;
if (extend && (p->limit == 0 || p->limit > p->size)) {
for (size_t k = 0; k < pset->num; k++)
pset->infos[k].flags &= ~PUIFLAG_EXTEND;
p->flags |= PUIFLAG_EXTEND;
if (size == 0)
size = align;
}
if (p->limit != 0 && size > p->limit)
size = p->limit;
if ((p->flags & (PUIFLG_IS_OUTER|PUIFLG_JUST_MOUNTPOINT)) == 0)
pset->cur_free_space += p->size - size;
p->size = is_percent ? -size : size;
set_pset_exit_str(pset);
return 0;
}
/*
* User interface to edit a "wanted" partition layout "pset" as first
* abstract phase (not concrete partitions).
* Make sure to have everything (at least theoretically) fit the
* available space.
* During editing we keep the part_usage_info and the menu_opts
* in pset in sync, that is: we always allocate just enough entries
* in pset->infos as we have usage infos in the list (pset->num),
* and two additional menu entries ("add a partition" and "select units").
* The menu exit string changes depending on content, and implies
* abort while the partition set is not valid (does not fit).
* Return true when the user wants to continue (by editing the concrete
* partitions), return false to abort.
*/
bool
get_ptn_sizes(struct partition_usage_set *pset)
{
size_t num;
wclear(stdscr);
wrefresh(stdscr);
if (pset->menu_opts == NULL)
pset->menu_opts = calloc(pset->num+4, sizeof(*pset->menu_opts));
/*
* Check if there is a reasonable pre-existing partition for
* NetBSD.
*/
static bool
check_existing_netbsd(struct disk_partitions *parts)
{
size_t nbsd_parts;
struct disk_part_info info;
nbsd_parts = 0;
for (part_id p = 0; p < parts->num_part; p++) {
if (!parts->pscheme->get_part_info(parts, p, &info))
continue;
if (info.flags & (PTI_PSCHEME_INTERNAL|PTI_RAW_PART))
continue;
if (info.nat_type && info.nat_type->generic_ptype == PT_root)
nbsd_parts++;
}
return nbsd_parts > 0;
}
/*
* Query a partition layout type (with available options depending on
* pre-existing partitions).
*/
static enum layout_type
ask_layout(struct disk_partitions *parts, bool have_existing)
{
arg_rep_int ai;
const char *args[2];
int menu;
size_t num_opts;
menu_ent options[5], *opt;
for (i = 0; i < wanted->num; i++) {
wanted->infos[i].parts = parts;
wanted->infos[i].cur_part_id = NO_PART;
if (wanted->infos[i].type == PT_undef &&
wanted->infos[i].fs_type != FS_UNUSED) {
const struct part_type_desc *pt =
parts->pscheme->get_fs_part_type(PT_undef,
wanted->infos[i].fs_type,
wanted->infos[i].fs_version);
if (pt != NULL)
wanted->infos[i].type = pt->generic_ptype;
}
if (wanted->parts->parent != NULL &&
(wanted->infos[i].fs_type == FS_MSDOS ||
wanted->infos[i].fs_type == FS_EX2FS))
wanted->infos[i].flags |=
PUIFLG_ADD_INNER|PUIFLAG_ADD_OUTER;
#if DEFSWAPSIZE == -1
if (wanted->infos[i].type == PT_swap) {
#ifdef MD_MAY_SWAP_TO
if (MD_MAY_SWAP_TO(wanted->parts->disk))
#endif
wanted->infos[i].size =
get_ramsize() * (MEG / 512);
}
#endif
if (wanted->infos[i].type == PT_swap && swap > wanted->num)
swap = i;
#if defined(DEFAULT_UFS2) && !defined(HAVE_UFS2_BOOT)
if (wanted->infos[i].instflags & PUIINST_BOOT)
boot = i;
#endif
if (wanted->infos[i].type == PT_root) {
if (strcmp(wanted->infos[i].mount, "/") == 0) {
root = i;
} else if (
strcmp(wanted->infos[i].mount, "/usr") == 0) {
if (wanted->infos[i].size > 0)
usr = i;
else
def_usr = i;
}
if (wanted->infos[i].fs_type == FS_UNUSED)
wanted->infos[i].fs_type = FS_BSDFFS;
if (wanted->infos[i].fs_type == FS_BSDFFS) {
#ifdef DEFAULT_UFS2
#ifndef HAVE_UFS2_BOOT
if (boot < wanted->num || i != root)
#endif
wanted->infos[i].fs_version = 3;
#endif
}
}
}
/*
* Now we have the defaults as if we were installing to an
* empty disk. Merge the partitions in target range that are already
* there (match with wanted) or are there additionally.
* The only thing outside of target range that we care for
* are FAT partitions, EXT2FS partitions, and a potential
* swap partition - we assume one is enough.
*/
size_t num = wanted->num;
if (parts->parent) {
for (part_id pno = 0; pno < parts->parent->num_part; pno++) {
struct disk_part_info info;
/*
* Preliminary calc additional space to allocate and how much
* we likely will have left over. Use that to do further
* adjustments, so we don't present the user inherently
* impossible defaults.
*/
free_space = parts->free_space - wanted->reserved_space;
required = 0;
if (root < wanted->num)
required += wanted->infos[root].size;
if (usr < wanted->num)
required += wanted->infos[usr].size;
else if (def_usr < wanted->num)
required += wanted->infos[def_usr].def_size;
free_space -= required;
for (i = 0; i < wanted->num; i++) {
if (i == root || i == usr)
continue; /* already accounted above */
if (wanted->infos[i].cur_part_id != NO_PART)
continue;
if (wanted->infos[i].size == 0)
continue;
if (wanted->infos[i].flags
& (PUIFLG_IS_OUTER|PUIFLG_JUST_MOUNTPOINT))
continue;
free_space -= wanted->infos[i].size;
}
if (free_space < 0 && swap < wanted->num &&
get_ramsize() > TINY_RAM_SIZE) {
/* steel from swap partition */
daddr_t d = wanted->infos[swap].size;
daddr_t inc = roundup(-free_space, align);
if (inc > d)
inc = d;
free_space += inc;
wanted->infos[swap].size -= inc;
}
if (root < wanted->num) {
/* Add space for 2 system dumps to / (traditional) */
dump_space = get_ramsize() * (MEG/512);
dump_space = roundup(dump_space, align);
if (free_space > dump_space*2)
dump_space *= 2;
if (free_space > dump_space) {
wanted->infos[root].size += dump_space;
free_space -= dump_space;
}
}
if (wanted->infos[root].limit > 0 &&
(wanted->infos[root].cur_start + wanted->infos[root].size >
wanted->infos[root].limit ||
(wanted->infos[root].flags & PUIFLAG_EXTEND &&
(wanted->infos[root].cur_start + wanted->infos[root].size
+ free_space > wanted->infos[root].limit)))) {
if (usr >= wanted->num && def_usr < wanted->num) {
usr = def_usr;
wanted->infos[usr].size = wanted->infos[root].size
- wanted->infos[root].limit;
if (wanted->infos[usr].size <= 0)
wanted->infos[usr].size = max(1,
wanted->infos[usr].def_size);
wanted->infos[root].size =
wanted->infos[root].limit;
if (wanted->infos[root].flags & PUIFLAG_EXTEND) {
wanted->infos[root].flags &= ~PUIFLAG_EXTEND;
wanted->infos[usr].flags |= PUIFLAG_EXTEND;
}
} else if (usr < wanted->num) {
/* move space from root to usr */
daddr_t spill = wanted->infos[root].size -
wanted->infos[root].limit;
spill = roundup(spill, align);
wanted->infos[root].size =
wanted->infos[root].limit;
wanted->infos[usr].size = spill;
} else {
wanted->infos[root].size =
wanted->infos[root].limit;
}
}
wanted->infos[root].def_size = wanted->infos[root].size;
}
/*
* We sort pset->infos to sync with pset->parts and
* the cur_part_id, to allow using the same index into both
* "array" in later phases. This may include inserting
* dummy entries (when we do not actually want the
* partition, but it is forced upon us, like RAW_PART in
* disklabel).
*/
static void
sort_and_sync_parts(struct partition_usage_set *pset)
{
struct part_usage_info *infos;
size_t i, j, no;
part_id pno;
/* count non-empty entries that are not in pset->parts */
no = pset->parts->num_part;
for (i = 0; i < pset->num; i++) {
if (pset->infos[i].size == 0)
continue;
if (pset->infos[i].cur_part_id != NO_PART)
continue;
no++;
}
/* allocate new infos */
infos = calloc(no, sizeof *infos);
if (infos == NULL)
return;
/* pre-initialize the first entries as dummy entries */
for (i = 0; i < pset->parts->num_part; i++) {
infos[i].cur_part_id = NO_PART;
infos[i].cur_flags = PTI_PSCHEME_INTERNAL;
}
/*
* Now copy over everything from our old entries that points to
* a real partition.
*/
for (i = 0; i < pset->num; i++) {
pno = pset->infos[i].cur_part_id;
if (pno == NO_PART)
continue;
if (pset->parts != pset->infos[i].parts)
continue;
if (pset->infos[i].flags & PUIFLG_JUST_MOUNTPOINT)
continue;
if ((pset->infos[i].flags & (PUIFLG_IS_OUTER|PUIFLG_ADD_INNER))
== PUIFLG_IS_OUTER)
continue;
if (pno >= pset->parts->num_part)
continue;
memcpy(infos+pno, pset->infos+i, sizeof(*infos));
}
/* Fill in the infos for real partitions where we had no data */
for (pno = 0; pno < pset->parts->num_part; pno++) {
struct disk_part_info info;
if (infos[pno].cur_part_id != NO_PART)
continue;
if (!pset->parts->pscheme->get_part_info(pset->parts, pno,
&info))
continue;
infos[pno].parts = pset->parts;
infos[pno].cur_part_id = pno;
infos[pno].cur_flags = info.flags;
infos[pno].size = info.size;
infos[pno].type = info.nat_type->generic_ptype;
infos[pno].cur_start = info.start;
infos[pno].fs_type = info.fs_type;
infos[pno].fs_version = info.fs_sub_type;
}
/* Add the non-partition entries after that */
j = pset->parts->num_part;
for (i = 0; i < pset->num; i++) {
if (j >= no)
break;
if (pset->infos[i].size == 0)
continue;
if (pset->infos[i].cur_part_id != NO_PART)
continue;
memcpy(infos+j, pset->infos+i, sizeof(*infos));
j++;
}
#ifndef NO_CLONES
/*
* Convert clone entries with more than one source into
* several entries with a single source each.
*/
static void
normalize_clones(struct part_usage_info **infos, size_t *num)
{
size_t i, j, add_clones;
struct part_usage_info *ui, *src, *target;
struct disk_part_info info;
struct selected_partition *clone;
for (add_clones = 0, i = 0; i < *num; i++) {
if ((*infos)[i].clone_src != NULL &&
(*infos)[i].flags & PUIFLG_CLONE_PARTS &&
(*infos)[i].cur_part_id == NO_PART)
add_clones += (*infos)[i].clone_src->num_sel-1;
}
if (add_clones == 0)
return;
ui = calloc(*num+add_clones, sizeof(**infos));
if (ui == NULL)
return; /* can not handle this well here, drop some clones */
/* walk the list and dedup clones */
for (src = *infos, target = ui, i = 0; i < *num; i++) {
if (src != target)
*target = *src;
if (target->clone_src != NULL &&
(target->flags & PUIFLG_CLONE_PARTS) &&
target->cur_part_id == NO_PART) {
for (j = 0; j < src->clone_src->num_sel; j++) {
if (j > 0) {
target++;
*target = *src;
}
target->clone_ndx = j;
clone = &target->clone_src->selection[j];
clone->parts->pscheme->get_part_info(
clone->parts, clone->id, &info);
target->size = info.size;
}
}
target++;
src++;
}
*num += add_clones;
assert((target-ui) >= 0 && (size_t)(target-ui) == *num);
free(*infos);
*infos = ui;
}
#endif
/*
* Now it gets tricky: we want the wanted partitions in order
* as defined, but any already existing partitions should not
* be moved. We allow them to change size though.
* To keep it simple, we just assign in order and skip blocked
* spaces. This may shuffle the order of the resulting partitions
* compared to the wanted list.
*/
/* Adjust sizes of existing partitions */
for (i = 0; i < wanted->num; i++) {
ps = wanted->infos[i].flags & PUIFLG_IS_OUTER ?
parts->parent : parts;
const struct part_usage_info *want = &wanted->infos[i];
if (want->cur_part_id == NO_PART)
continue;
if (i == exp_ndx) /* the exp. part. can not exist yet */
continue;
daddr_t free_size = ps->pscheme->max_free_space_at(ps,
infos[i].start);
if (free_size < wanted->infos[i].size)
continue;
if (infos[i].size != wanted->infos[i].size) {
infos[i].size = wanted->infos[i].size;
ps->pscheme->set_part_info(ps, want->cur_part_id,
&infos[i], NULL);
}
}
from = start > 0 ? start : -1;
/*
* First add all outer partitions - we need to align those exactly
* with the inner counterpart later.
*/
if (parts->parent) {
ps = parts->parent;
daddr_t outer_align = ps->pscheme->get_part_alignment(ps);
for (i = 0; i < wanted->num; i++) {
struct part_usage_info *want = &wanted->infos[i];
if (want->cur_part_id != NO_PART)
continue;
if (!(want->flags & PUIFLAG_ADD_OUTER))
continue;
if (want->size <= 0)
continue;
if (new_part_id == NO_PART)
continue; /* failed to add, skip */
wanted->parts->pscheme->get_part_info(
wanted->parts, new_part_id, &infos[i]);
from = roundup(infos[i].start+infos[i].size, align);
}
/*
* If there are any outer partitions that we need as inner ones
* too, add them to the inner partitioning scheme.
*/
for (i = 0; i < wanted->num; i++) {
struct part_usage_info *want = &wanted->infos[i];
if (want->cur_part_id == NO_PART)
continue;
if (want->flags & PUIFLG_JUST_MOUNTPOINT)
continue;
if (want->size <= 0)
continue;
if ((want->flags & (PUIFLG_ADD_INNER|PUIFLG_IS_OUTER)) !=
(PUIFLG_ADD_INNER|PUIFLG_IS_OUTER))
continue;
/*
* Note: all part_ids are invalid now, as we have added things!
*/
for (i = 0; i < wanted->num; i++)
wanted->infos[i].cur_part_id = NO_PART;
for (pno = 0; pno < parts->num_part; pno++) {
struct disk_part_info t;
if (!parts->pscheme->get_part_info(parts, pno, &t))
continue;
if (t.flags & PTI_SPECIAL_PARTS)
continue;
for (i = 0; i < wanted->num; i++) {
if (wanted->infos[i].cur_part_id != NO_PART)
continue;
if (wanted->infos[i].size <= 0)
continue;
if (t.start == infos[i].start) {
wanted->infos[i].cur_part_id = pno;
wanted->infos[i].cur_start = infos[i].start;
wanted->infos[i].cur_flags = infos[i].flags;
break;
}
}
}
free(infos);
/* sort, and sync part ids and wanted->infos[] indices */
sort_and_sync_parts(wanted);
}
fill_defaults(wanted, parts, start, size);
ok = get_ptn_sizes(wanted);
if (ok)
apply_settings_to_partitions(parts, wanted, start, size);
return ok;
}
/*
* md back-end code for menu-driven BSD disklabel editor.
* returns 0 on failure, 1 on success, -1 for restart.
* fills the install target with a list for newfs/fstab.
*/
int
make_bsd_partitions(struct install_partition_desc *install)
{
struct disk_partitions *parts = pm->parts;
const struct disk_partitioning_scheme *pscheme;
struct partition_usage_set wanted;
daddr_t p_start, p_size;
enum layout_type layoutkind = LY_SETSIZES;
bool have_existing;
if (pm && pm->no_part && parts == NULL)
return 1;
if (parts == NULL) {
pscheme = select_part_scheme(pm, NULL, !pm->no_mbr, NULL);
if (pscheme == NULL)
return 0;
parts = pscheme->create_new_for_disk(pm->diskdev,
0, pm->dlsize, true, NULL);
if (parts == NULL)
return 0;
pm->parts = parts;
} else {
pscheme = parts->pscheme;
}
if (pscheme->secondary_partitions) {
struct disk_partitions *p;
p = pscheme->secondary_partitions(parts, pm->ptstart, false);
if (p) {
parts = p;
pscheme = parts->pscheme;
}
}
have_existing = check_existing_netbsd(parts);
/*
* Make sure the cylinder size multiplier/divisor and disk size are
* valid
*/
if (pm->current_cylsize == 0)
pm->current_cylsize = pm->dlcylsize;
if (pm->ptsize == 0)
pm->ptsize = pm->dlsize;
/* Ask for layout type -- standard or special */
if (partman_go == 0) {
char bsd_size[6], min_size[6], x_size[6];
/*
* OK, we have a partition table. Give the user the chance to
* edit it and verify it's OK, or abort altogether.
*/
for (;;) {
int rv = edit_and_check_label(pm, &wanted, true);
if (rv == 0) {
msg_display(MSG_abort_part);
free_usage_set(&wanted);
return 0;
}
/* update install infos */
install->num = wanted.num;
install->infos = wanted.infos;
install->write_back = wanted.write_back;
install->num_write_back = wanted.num_write_back;
/* and check them */
if (check_partitions(install))
break;
}
/* we moved infos from wanted to install target */
wanted.infos = NULL;
wanted.write_back = NULL;
free_usage_set(&wanted);
/*
* check that there is at least a / somewhere.
*/
bool
check_partitions(struct install_partition_desc *install)
{
#ifdef HAVE_BOOTXX_xFS
int rv = 1;
char *bootxx;
#endif
#ifndef HAVE_UFS2_BOOT
size_t i;
#endif
#ifdef HAVE_BOOTXX_xFS
if (MD_NEED_BOOTBLOCK(install)) {
/* check if we have boot code for the root partition type */
bootxx = bootxx_name(install);
if (bootxx != NULL) {
rv = access(bootxx, R_OK);
free(bootxx);
} else
rv = -1;
if (rv != 0) {
hit_enter_to_continue(NULL, MSG_No_Bootcode);
return false;
}
}
#endif
#ifndef HAVE_UFS2_BOOT
if (MD_NEED_BOOTBLOCK(install)) {
for (i = 0; i < install->num; i++) {
if (install->infos[i].type != PT_root)
continue;
if (strcmp(install->infos[i].mount, "/") != 0)
continue;
if (install->infos[i].fs_type != FS_BSDFFS)
continue;
if (install->infos[i].fs_version < 2)
continue;
hit_enter_to_continue(NULL, MSG_cannot_ufs2_root);
return false;
}
}
#endif