/* $NetBSD: mbr.c,v 1.56 2003/11/27 21:43:22 dsl Exp $ */ /* * Copyright 1997 Piermont Information Systems Inc. * All rights reserved. * * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed for the NetBSD Project by * Piermont Information Systems Inc. * 4. 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. * */ /* * Following applies to the geometry guessing code */ /* * Mach Operating System * Copyright (c) 1992 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* mbr.c -- DOS Master Boot Record editing code */ #include #include #include #include #include #include #include "defs.h" #include "mbr.h" #include "md.h" #include "msg_defs.h" #include "menu_defs.h" #include "endian.h" #define NO_BOOTMENU (-0x100) #define MAXCYL 1023 /* Possibly 1024 */ #define MAXHEAD 255 /* Possibly 256 */ #define MAXSECTOR 63 struct part_id { int id; const char *name; } part_ids[] = { {0, "unused"}, {MBR_PTYPE_NETBSD, "NetBSD"}, {MBR_PTYPE_EXT_LBA, "Extended partition, LBA"}, {MBR_PTYPE_386BSD, "FreeBSD/386BSD"}, {MBR_PTYPE_OPENBSD, "OpenBSD"}, {MBR_PTYPE_LNXEXT2, "Linux native"}, {MBR_PTYPE_LNXSWAP, "Linux swap"}, {MBR_PTYPE_FAT12, "DOS FAT12"}, {MBR_PTYPE_FAT16S, "DOS FAT16, <32M"}, {MBR_PTYPE_FAT16B, "DOS FAT16, >32M"}, {MBR_PTYPE_FAT16L, "Windows FAT16, LBA"}, {MBR_PTYPE_FAT32, "Windows FAT32"}, {MBR_PTYPE_FAT32L, "Windows FAT32, LBA"}, {MBR_PTYPE_NTFSVOL, "NTFS volume set"}, {MBR_PTYPE_NTFS, "NTFS"}, {-1, "Unknown"}, }; static int get_mapping(struct mbr_partition *, int, int *, int *, int *, unsigned long *); static void convert_mbr_chs(int, int, int, u_int8_t *, u_int8_t *, u_int8_t *, u_int32_t); /* * get C/H/S geometry from user via menu interface and * store in globals. */ void set_bios_geom(int cyl, int head, int sec) { char res[80]; msg_display_add(MSG_setbiosgeom); do { snprintf(res, 80, "%d", sec); msg_prompt_add(MSG_sectors, res, res, 80); bsec = atoi(res); } while (bsec <= 0 || bsec > 63); do { snprintf(res, 80, "%d", head); msg_prompt_add(MSG_heads, res, res, 80); bhead = atoi(res); } while (bhead <= 0 || bhead > 256); bcyl = dlsize / bsec / bhead; if (dlsize != bcyl * bsec * bhead) bcyl++; } #ifdef notdef void disp_cur_geom(void) { msg_display_add(MSG_realgeom, dlcyl, dlhead, dlsec); msg_display_add(MSG_biosgeom, bcyl, bhead, bsec); } #endif /* * Then, the partition stuff... */ /* * If we change the mbr partitioning, the we must remove any references * in the netbsd disklabel to the part we changed. */ static void remove_old_partitions(uint start, int size) { partinfo *p; uint end; /* Allow for size being -ve, get it right for very large partitions */ end = start + size; if (end < start) { end = start; start = end + size; } if (end == 0) return; for (p = oldlabel; p < oldlabel + nelem(oldlabel); p++) { if (p->pi_offset >= end || p->pi_offset + p->pi_size <= start) continue; memset(p, 0, sizeof *p); } } static int find_mbr_space(struct mbr_sector *mbrs, uint *start, uint *size, int from, int ignore) { int sz; int i; uint s, e; check_again: sz = dlsize - from; for (i = 0; i < MBR_PART_COUNT; i++) { if (i == ignore) continue; s = mbrs->mbr_parts[i].mbrp_start; e = s + mbrs->mbr_parts[i].mbrp_size; if (s <= from && e > from) { from = e; goto check_again; } if (s > from && s - from < sz) sz = s - from; } if (sz == 0) return -1; if (start != NULL) *start = from; if (size != NULL) *size = sz; return 0; } static struct mbr_partition * get_mbrp(mbr_info_t **mbrip, int opt) { mbr_info_t *mbri = *mbrip; if (opt >= MBR_PART_COUNT) for (opt -= MBR_PART_COUNT - 1; opt; opt--) mbri = mbri->extended; *mbrip = mbri; return &mbri->mbr.mbr_parts[opt]; } static int err_msg_win(const char *errmsg) { const char *cont; int l, l1; errmsg = msg_string(errmsg); cont = msg_string(MSG_Hit_enter_to_continue); l = strlen(errmsg); l1 = strlen(cont); if (l < l1) l = l1; msg_prompt_win("%s.\n%s", -1, 18, l + 5, 4, NULL, NULL, 1, errmsg, cont); return 0; } static int set_mbr_type(menudesc *m, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ombri = arg; mbr_info_t *ext; struct mbr_partition *mbrp; char *cp; int opt = mbri->opt; int type; int start, sz; int i; char numbuf[4]; mbrp = get_mbrp(&mbri, opt); if (opt >= MBR_PART_COUNT) opt = 0; type = m->cursel; if (type == 0) return 1; type = part_ids[type - 1].id; while (type == -1) { snprintf(numbuf, sizeof numbuf, "%u", mbrp->mbrp_type); msg_prompt_win(MSG_get_ptn_id, -1, 18, 0, 0, numbuf, numbuf, sizeof numbuf); type = strtoul(numbuf, &cp, 0); if (*cp != 0) type = -1; } if (type == mbrp->mbrp_type) /* type not changed... */ return 1; mbri->last_mounted[opt < MBR_PART_COUNT ? opt : 0] = NULL; if (MBR_IS_EXTENDED(mbrp->mbrp_type)) { /* deleting extended partition.... */ if (mbri->sector || mbri->extended->extended) /* We should have stopped this happening... */ return err_msg_win("can't delete extended"); free(mbri->extended); mbri->extended = NULL; } if (type == 0) { /* Deleting partition */ mbrp->mbrp_type = 0; /* Remove references to this space from the NetBSD label */ remove_old_partitions(mbri->sector + mbrp->mbrp_start, mbrp->mbrp_size); #ifdef BOOTSEL if (ombri->bootsec == mbri->sector + mbrp->mbrp_start) ombri->bootsec = 0; memset(mbri->nametab[opt], 0, sizeof mbri->nametab[opt]); #endif if (mbri->sector == 0) { /* A main partition */ memset(mbrp, 0, sizeof *mbrp); return 1; } /* Merge with previous and next free areas */ ext = mbri->prev_ext; if (ext != NULL && ext->mbr.mbr_parts[0].mbrp_type == 0) { /* previous was free - back up one entry */ mbri = ext; ombri->opt--; } while ((ext = mbri->extended)) { if (ext->mbr.mbr_parts[0].mbrp_type != 0) break; sz = ext->mbr.mbr_parts[0].mbrp_start + ext->mbr.mbr_parts[0].mbrp_size; /* Increase size of our (empty) partition */ mbri->mbr.mbr_parts[0].mbrp_size += sz; /* Make us describe the next partition */ mbri->mbr.mbr_parts[1] = ext->mbr.mbr_parts[1]; /* fix list of extended partitions */ mbri->extended = ext->extended; if (ext->extended != NULL) ext->extended->prev_ext = mbri; free(ext); /* Make previous size cover all our ptn */ ext = mbri->prev_ext; if (ext != NULL) ext->mbr.mbr_parts[1].mbrp_size += sz; } return 1; } if (mbrp->mbrp_start == 0) { /* Find first chunk of space... */ /* Must be in the main partition */ if (mbri->sector != 0) /* shouldn't be possible to have null start... */ return err_msg_win("main-extended mixup"); if (find_mbr_space(&mbri->mbr, &start, &sz, bsec, -1) != 0) /* no space */ return err_msg_win(MSG_No_free_space); mbrp->mbrp_start = start; mbrp->mbrp_size = sz; /* If there isn't an active partition mark this one active */ if (!MBR_IS_EXTENDED(type)) { for (i = 0; i < MBR_PART_COUNT; i++) if (mbri->mbr.mbr_parts[i].mbrp_flag != 0) break; if (i == MBR_PART_COUNT) mbrp->mbrp_flag = MBR_PFLAG_ACTIVE; } } if (MBR_IS_EXTENDED(type)) { if (mbri->sector != 0) /* Can't set extended partition in an extended one */ return err_msg_win(MSG_Only_one_extended_ptn); if (mbri->extended) /* Can't have two extended partitions */ return err_msg_win(MSG_Only_one_extended_ptn); /* Create new extended partition */ ext = calloc(1, sizeof *mbri->extended); if (!ext) return 0; mbri->extended = ext; ext->sector = mbrp->mbrp_start; ext->mbr.mbr_parts[0].mbrp_start = bsec; ext->mbr.mbr_parts[0].mbrp_size = mbrp->mbrp_size - bsec; } mbrp->mbrp_type = type; return 1; } static void set_type_label(menudesc *m, int opt, void *arg) { if (opt == 0) { wprintw(m->mw, msg_string(MSG_Dont_change)); return; } if (opt == 1) { wprintw(m->mw, msg_string(MSG_Delete_partition)); return; } if (part_ids[opt - 1].id == -1) { wprintw(m->mw, msg_string(MSG_Other_kind)); return; } wprintw(m->mw, part_ids[opt - 1].name); } static int edit_mbr_type(menudesc *m, void *arg) { static menu_ent type_opts[1 + nelem(part_ids)]; static int type_menu = -1; int i; if (type_menu == -1) { for (i = 0; i < nelem(type_opts); i++) { type_opts[i].opt_menu = OPT_NOMENU; type_opts[i].opt_action = set_mbr_type; } type_menu = new_menu(NULL, type_opts, nelem(type_opts), 15, 12, 0, 30, MC_SCROLL | MC_NOEXITOPT | MC_NOCLEAR, NULL, set_type_label, NULL, NULL, NULL); } if (type_menu != -1) process_menu(type_menu, arg); return 0; } static int edit_mbr_start(menudesc *m, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ext; struct mbr_partition *mbrp; int opt = mbri->opt; uint start, sz; uint new_r, new, limit, dflt_r; int delta; const char *errmsg; char *cp; struct { uint start; uint start_r; uint limit; } freespace[MBR_PART_COUNT]; int spaces; int i; char prompt[MBR_PART_COUNT * 60]; int len; char numbuf[12]; if (opt >= MBR_PART_COUNT) /* should not be able to get here... */ return 1; mbrp = mbri->mbr.mbr_parts + opt; /* locate the start of all free areas */ spaces = 0; for (start = bsec, i = 0; i < MBR_PART_COUNT; start += sz, i++) { if (find_mbr_space(&mbri->mbr, &start, &sz, start, opt)) break; if (MBR_IS_EXTENDED(mbrp->mbrp_type)) { /* Only want the area that contains this partition */ if (mbrp->mbrp_start < start || mbrp->mbrp_start >= start + sz) continue; i = MBR_PART_COUNT - 1; } freespace[spaces].start = start; freespace[spaces].start_r = start / sizemult; freespace[spaces].limit = start + sz; if (++spaces >= sizeof freespace) /* shouldn't happen... */ break; } len = 0; for (i = 0; i < spaces; i++) { len += snprintf(prompt + len, sizeof prompt - len, msg_string(MSG_ptn_starts), freespace[i].start_r, freespace[i].limit / sizemult, multname, freespace[i].limit / sizemult - freespace[i].start_r, multname); if (len >= sizeof prompt) break; } dflt_r = mbrp->mbrp_start / sizemult; errmsg = ""; for (;;) { snprintf(numbuf, sizeof numbuf, "%d", dflt_r); msg_prompt_win(MSG_get_ptn_start, -1, 18, 60, spaces + 3, numbuf, numbuf, sizeof numbuf, prompt, msg_string(errmsg), multname); new_r = strtoul(numbuf, &cp, 0); if (*cp != 0) { errmsg = MSG_Invalid_numeric; continue; } if (new_r == dflt_r) /* Unchanged */ return 0; new = new_r * sizemult; for (i = 0; i < spaces; i++) { if (new_r == freespace[i].start_r) { new = freespace[i].start; break; } if (new >= freespace[i].start && new < freespace[i].limit) break; } if (i >= spaces) { errmsg = MSG_Space_allocated; continue; } limit = freespace[i].limit; if (new > mbrp->mbrp_start && MBR_IS_EXTENDED(mbrp->mbrp_type) && (mbri->extended->mbr.mbr_parts[0].mbrp_type != 0 || mbri->extended->mbr.mbr_parts[0].mbrp_size < new - mbrp->mbrp_start)) { errmsg = MSG_Space_allocated; continue; } break; } if (new < mbrp->mbrp_start + mbrp->mbrp_size && limit > mbrp->mbrp_start) /* Keep end of partition in the same place */ limit = mbrp->mbrp_start + mbrp->mbrp_size; delta = new - mbrp->mbrp_start; if (MBR_IS_EXTENDED(mbrp->mbrp_type)) { ext = mbri->extended; if (ext->mbr.mbr_parts[0].mbrp_type != 0) { /* allocate an extended ptn for the free item */ ext = calloc(1, sizeof *ext); if (!ext) return 0; ext->sector = mbrp->mbrp_start; ext->extended = mbri->extended; mbri->extended->prev_ext = ext; mbri->extended = ext; ext->mbr.mbr_parts[0].mbrp_start = bsec; ext->mbr.mbr_parts[0].mbrp_size = -bsec; ext->mbr.mbr_parts[1].mbrp_type = MBR_PTYPE_EXT; ext->mbr.mbr_parts[1].mbrp_start = 0; ext->mbr.mbr_parts[1].mbrp_size = ext->extended->mbr.mbr_parts[0].mbrp_start + ext->extended->mbr.mbr_parts[0].mbrp_size; } /* adjust size of first free item */ ext->mbr.mbr_parts[0].mbrp_size -= delta; ext->sector += delta; /* and the link of all extended partitions */ do if (ext->extended) ext->mbr.mbr_parts[1].mbrp_start -= delta; while ((ext = ext->extended)); } remove_old_partitions(mbri->sector + mbrp->mbrp_start, delta); /* finally set partition base and size */ mbrp->mbrp_start = new; mbrp->mbrp_size = limit - new; mbri->last_mounted[opt] = NULL; return 0; } static int edit_mbr_size(menudesc *m, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ombri = arg; mbr_info_t *ext; struct mbr_partition *mbrp; int opt = mbri->opt; uint start, max, max_r, dflt, dflt_r, new; uint freespace; int delta; char numbuf[12]; char *cp; const char *errmsg; mbrp = get_mbrp(&mbri, opt); dflt = mbrp->mbrp_size; if (opt < MBR_PART_COUNT) { max = 0; find_mbr_space(&mbri->mbr, &start, &max, mbrp->mbrp_start, opt); if (start != mbrp->mbrp_start) return 0; if (dflt == 0) dflt = max; } else { ext = mbri->extended; max = dflt; if (ext != NULL && ext->mbr.mbr_parts[0].mbrp_type == 0) { /* Easier to merge now and split later... */ if (ext->extended) ext->extended->prev_ext = mbri; mbri->extended = ext->extended; if (mbri->prev_ext) mbri->prev_ext->mbr.mbr_parts[1].mbrp_size += mbri->mbr.mbr_parts[1].mbrp_size; mbrp->mbrp_size += mbri->mbr.mbr_parts[1].mbrp_size; max += mbri->mbr.mbr_parts[1].mbrp_size; mbri->mbr.mbr_parts[1] = ext->mbr.mbr_parts[1]; free(ext); } } start = mbri->sector + mbrp->mbrp_start; dflt_r = (start + dflt) / sizemult - start / sizemult; if (max == dflt) max_r = dflt_r; else max_r = max / sizemult; for (errmsg = "";;) { snprintf(numbuf, sizeof numbuf, "%d", dflt_r); msg_prompt_win(MSG_get_ptn_size, -1, 18, 0, 0, numbuf, numbuf, sizeof numbuf, msg_string(errmsg), max_r, multname); new = strtoul(numbuf, &cp, 0); if (*cp != 0) { errmsg = MSG_Invalid_numeric; continue; } if (new == 0 || new == max_r) new = max; else { if (new == dflt_r) new = dflt; else { /* Round end to cylinder boundary */ if (sizemult != 1) { new *= sizemult; new += ROUNDDOWN(start, bcylsize); new = ROUNDUP(new, bcylsize); new -= start; while (new <= 0) new += bcylsize; } } } if (new > max) { errmsg = MSG_Too_large; continue; } if (!MBR_IS_EXTENDED(mbrp->mbrp_type) || new == dflt) break; /* Must keep extended list aligned */ for (ext = mbri->extended; ext->extended; ext = ext->extended) continue; if ((new < dflt && (ext->mbr.mbr_parts[0].mbrp_type != 0 || (mbrp->mbrp_start + new < ext->sector + bsec && mbrp->mbrp_start + new != ext->sector))) || (new > dflt && ext->mbr.mbr_parts[0].mbrp_type != 0 && new < dflt + bsec)) { errmsg = MSG_Space_allocated; continue; } delta = new - dflt; if (ext->mbr.mbr_parts[0].mbrp_type == 0) { /* adjust size of last item (free space) */ if (mbrp->mbrp_start + new == ext->sector) { /* kill last extended ptn */ ext = ext->prev_ext; free(ext->extended); ext->extended = NULL; memset(&ext->mbr.mbr_parts[1], 0, sizeof ext->mbr.mbr_parts[1]); break; } ext->mbr.mbr_parts[0].mbrp_size += delta; ext = ext->prev_ext; if (ext != NULL) ext->mbr.mbr_parts[1].mbrp_size += delta; break; } /* Joy of joys, we must allocate another extended ptn */ mbri = ext; ext = calloc(1, sizeof *ext); if (!ext) return 0; mbri->extended = ext; ext->prev_ext = mbri; ext->mbr.mbr_parts[0].mbrp_start = bsec; ext->mbr.mbr_parts[0].mbrp_size = delta - bsec; ext->sector = mbri->sector + mbri->mbr.mbr_parts[0].mbrp_start + mbri->mbr.mbr_parts[0].mbrp_size; mbri->mbr.mbr_parts[1].mbrp_start = ext->sector - ombri->sector; mbri->mbr.mbr_parts[1].mbrp_type = MBR_PTYPE_EXT; mbri->mbr.mbr_parts[1].mbrp_size = delta; break; } if (opt >= MBR_PART_COUNT && max - new <= bsec) /* round up if not enough space for a header */ new = max; if (new != mbrp->mbrp_size) { mbri->last_mounted[opt < MBR_PART_COUNT ? opt : 0] = NULL; remove_old_partitions(mbri->sector + mbrp->mbrp_start + mbrp->mbrp_size, new - mbrp->mbrp_size); } mbrp->mbrp_size = new; if (opt < MBR_PART_COUNT || new == max) return 0; /* Need to allocate an extra item for the free space */ ext = calloc(1, sizeof *ext); if (!ext) { mbrp->mbrp_size = max; return 0; } /* Link into our extended chain */ ext->extended = mbri->extended; mbri->extended = ext; ext->prev_ext = mbri; if (ext->extended != NULL) ext->extended->prev_ext = ext; ext->mbr.mbr_parts[1] = mbri->mbr.mbr_parts[1]; freespace = max - new; if (mbri->prev_ext != NULL) mbri->prev_ext->mbr.mbr_parts[1].mbrp_size -= freespace; ext->mbr.mbr_parts[0].mbrp_start = bsec; ext->mbr.mbr_parts[0].mbrp_size = freespace - bsec; ext->sector = mbri->sector + mbri->mbr.mbr_parts[0].mbrp_start + mbri->mbr.mbr_parts[0].mbrp_size; mbri->mbr.mbr_parts[1].mbrp_start = ext->sector - ombri->sector; mbri->mbr.mbr_parts[1].mbrp_type = MBR_PTYPE_EXT; mbri->mbr.mbr_parts[1].mbrp_size = freespace; return 0; } static int edit_mbr_active(menudesc *m, void *arg) { mbr_info_t *mbri = arg; int i; uint8_t *fl; if (mbri->opt >= MBR_PART_COUNT) /* sanity */ return 0; /* Invert active flag */ fl = &mbri->mbr.mbr_parts[mbri->opt].mbrp_flag; if (*fl == MBR_PFLAG_ACTIVE) { *fl = 0; return 0; } /* Ensure there is at most one active partition */ for (i = 0; i < MBR_PART_COUNT; i++) mbri->mbr.mbr_parts[i].mbrp_flag = 0; *fl = MBR_PFLAG_ACTIVE; return 0; } static int edit_mbr_install(menudesc *m, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ombri = arg; struct mbr_partition *mbrp; int opt = mbri->opt; uint start; mbrp = get_mbrp(&mbri, opt); if (opt >= MBR_PART_COUNT) opt = 0; start = mbri->sector + mbrp->mbrp_start; if (start == ombri->install) ombri->install = 0; else ombri->install = start; return 0; } #ifdef BOOTSEL static int edit_mbr_bootmenu(menudesc *m, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ombri = arg; struct mbr_partition *mbrp; int opt = mbri->opt; mbrp = get_mbrp(&mbri, opt); if (opt >= MBR_PART_COUNT) opt = 0; msg_prompt_win(/* XXX */ "bootmenu", -1, 18, 0, 0, mbri->nametab[opt], mbri->nametab[opt], sizeof mbri->nametab[opt]); if (mbri->nametab[opt][0] == ' ') mbri->nametab[opt][0] = 0; if (mbri->nametab[opt][0] == 0) { if (ombri->bootsec == mbri->sector + mbrp->mbrp_start) ombri->bootsec = 0; return 0; } return 0; } static int edit_mbr_bootdefault(menudesc *m, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ombri = arg; struct mbr_partition *mbrp; mbrp = get_mbrp(&mbri, mbri->opt); ombri->bootsec = mbri->sector + mbrp->mbrp_start; return 0; } #endif static void set_ptn_label(menudesc *m, int line, void *arg); static void set_ptn_header(menudesc *m, void *arg); static int edit_mbr_entry(menudesc *m, void *arg) { mbr_info_t *mbri = arg; static int ptn_menu = -1; static menu_ent ptn_opts[] = { #define PTN_OPT_TYPE 0 {NULL, OPT_NOMENU, 0, edit_mbr_type}, #define PTN_OPT_START 1 {NULL, OPT_NOMENU, 0, edit_mbr_start}, #define PTN_OPT_SIZE 2 {NULL, OPT_NOMENU, 0, edit_mbr_size}, #define PTN_OPT_END 3 {NULL, OPT_NOMENU, OPT_IGNORE, NULL}, /* display end */ #define PTN_OPT_ACTIVE 4 {NULL, OPT_NOMENU, 0, edit_mbr_active}, #define PTN_OPT_INSTALL 5 {NULL, OPT_NOMENU, 0, edit_mbr_install}, #ifdef BOOTSEL #define PTN_OPT_BOOTMENU 6 {NULL, OPT_NOMENU, 0, edit_mbr_bootmenu}, #define PTN_OPT_BOOTDEFAULT 7 {NULL, OPT_NOMENU, 0, edit_mbr_bootdefault}, #endif {MSG_askunits, MENU_sizechoice, OPT_SUB, NULL}, }; if (ptn_menu == -1) ptn_menu = new_menu(NULL, ptn_opts, nelem(ptn_opts), 15, 6, 0, 50, MC_SCROLL | MC_NOCLEAR, set_ptn_header, set_ptn_label, NULL, NULL, MSG_Partition_OK); if (ptn_menu == -1) return 1; mbri->opt = m->cursel; process_menu(ptn_menu, mbri); return 0; } static void set_ptn_label(menudesc *m, int line, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ombri = arg; struct mbr_partition *mbrp; int opt; static const char *yes, *no; if (yes == NULL) { yes = msg_string(MSG_Yes); no = msg_string(MSG_No); } opt = mbri->opt; mbrp = get_mbrp(&mbri, opt); if (opt >= MBR_PART_COUNT) opt = 0; switch (line) { case PTN_OPT_TYPE: wprintw(m->mw, msg_string(MSG_ptn_type), get_partname(mbrp->mbrp_type)); break; case PTN_OPT_START: wprintw(m->mw, msg_string(MSG_ptn_start), (mbri->sector + mbrp->mbrp_start) / sizemult, multname); break; case PTN_OPT_SIZE: wprintw(m->mw, msg_string(MSG_ptn_size), (mbri->sector + mbrp->mbrp_start + mbrp->mbrp_size) / sizemult - (mbri->sector + mbrp->mbrp_start) / sizemult, multname); break; case PTN_OPT_END: wprintw(m->mw, msg_string(MSG_ptn_end), (mbri->sector + mbrp->mbrp_start + mbrp->mbrp_size) / sizemult, multname); break; case PTN_OPT_ACTIVE: wprintw(m->mw, msg_string(MSG_ptn_active), mbrp->mbrp_flag == MBR_PFLAG_ACTIVE ? yes : no); break; case PTN_OPT_INSTALL: wprintw(m->mw, msg_string(MSG_ptn_install), mbri->sector + mbrp->mbrp_start == ombri->install && mbrp->mbrp_type == MBR_PTYPE_NETBSD ? yes : no); break; #ifdef BOOTSEL case PTN_OPT_BOOTMENU: wprintw(m->mw, msg_string(MSG_bootmenu), mbri->nametab[opt]); break; case PTN_OPT_BOOTDEFAULT: wprintw(m->mw, msg_string(MSG_boot_dflt), ombri->bootsec == mbri->sector + mbrp->mbrp_start ? yes : no); break; #endif } } static void set_ptn_header(menudesc *m, void *arg) { mbr_info_t *mbri = arg; struct mbr_partition *mbrp; int opt = mbri->opt; int typ; mbrp = get_mbrp(&mbri, opt); if (opt >= MBR_PART_COUNT) opt = 0; typ = mbrp->mbrp_type; #define DISABLE(opt,cond) \ if (cond) \ m->opts[opt].opt_flags |= OPT_IGNORE; \ else \ m->opts[opt].opt_flags &= ~OPT_IGNORE; /* Can't change type of the extended partition unless it is empty */ DISABLE(PTN_OPT_TYPE, MBR_IS_EXTENDED(typ) && (mbri->extended->mbr.mbr_parts[0].mbrp_type != 0 || mbri->extended->extended != NULL)); /* It is unnecessary to be able to change the base of an extended ptn */ DISABLE(PTN_OPT_START, mbri->sector || typ == 0); /* or the size of a free area */ DISABLE(PTN_OPT_SIZE, typ == 0); /* Only 'normal' partitions can be 'Active' */ DISABLE(PTN_OPT_ACTIVE, mbri->sector != 0 || MBR_IS_EXTENDED(typ) || typ == 0); /* Can only install into NetBSD partition */ DISABLE(PTN_OPT_INSTALL, typ != MBR_PTYPE_NETBSD); #ifdef BOOTSEL /* The extended partition isn't bootable */ DISABLE(PTN_OPT_BOOTMENU, MBR_IS_EXTENDED(typ) || typ == 0); if (typ == 0) mbri->nametab[opt][0] = 0; /* Only partitions with bootmenu names can be made the default */ DISABLE(PTN_OPT_BOOTDEFAULT, mbri->nametab[opt][0] == 0); #endif #undef DISABLE } static void set_mbr_label(menudesc *m, int opt, void *arg) { mbr_info_t *mbri = arg; mbr_info_t *ombri = arg; struct mbr_partition *mbrp; uint rstart, rend; const char *name, *cp, *mounted; int len; mbrp = get_mbrp(&mbri, opt); if (opt >= MBR_PART_COUNT) opt = 0; if (mbrp->mbrp_type == 0 && mbri->sector == 0) { len = snprintf(0, 0, msg_string(MSG_part_row_used), 0, 0, 0); wprintw(m->mw, "%*s", len, ""); } else { rstart = mbri->sector + mbrp->mbrp_start; rend = (rstart + mbrp->mbrp_size) / sizemult; rstart = rstart / sizemult; wprintw(m->mw, msg_string(MSG_part_row_used), rstart, rend - rstart, mbrp->mbrp_flag == MBR_PFLAG_ACTIVE ? 'a' : ' ', #ifdef BOOTSEL ombri->bootsec == mbri->sector + mbrp->mbrp_start ? 'd' : #endif ' ', mbri->sector + mbrp->mbrp_start == ombri->install && mbrp->mbrp_type == MBR_PTYPE_NETBSD ? 'I' : ' '); } name = get_partname(mbrp->mbrp_type); mounted = mbri->last_mounted[opt]; len = strlen(name); cp = strchr(name, ','); if (cp != NULL) len = cp - name; if (mounted && *mounted != 0) { wprintw(m->mw, " %*s (%s)", len, name, mounted); } else wprintw(m->mw, " %.*s", len, name); #ifdef BOOTSEL if (mbri->nametab[opt][0] != 0) { int x, y; if (opt >= MBR_PART_COUNT) opt = 0; getyx(m->mw, y, x); if (x > 52) { x = 52; wmove(m->mw, y, x); } wprintw(m->mw, "%*s %s", 53 - x, "", mbri->nametab[opt]); } #endif } static void set_mbr_header(menudesc *m, void *arg) { mbr_info_t *mbri = arg; static menu_ent *opts; static int num_opts; mbr_info_t *ext; menu_ent *op; int i; int left; msg_display(MSG_editparttable); msg_table_add(MSG_part_header, dlsize/sizemult, multname, multname, multname, multname); if (num_opts == 0) { num_opts = 6; opts = malloc(6 * sizeof *opts); if (opts == NULL) { m->numopts = 0; return; } } /* First four items are the main partitions */ for (op = opts, i = 0; i < MBR_PART_COUNT; op++, i++) { op->opt_name = NULL; op->opt_menu = OPT_NOMENU; op->opt_flags = OPT_SUB; op->opt_action = edit_mbr_entry; } left = num_opts - MBR_PART_COUNT; /* Followed by the extended partitions */ for (ext = mbri->extended; ext; left--, op++, ext = ext->extended) { if (left <= 1) { menu_ent *new = realloc(opts, (num_opts + 4) * sizeof *opts); if (new == NULL) break; num_opts += 4; left += 4; op = new + (op - opts); opts = new; } op->opt_name = NULL; op->opt_menu = OPT_NOMENU; op->opt_flags = 0; op->opt_action = edit_mbr_entry; } /* and unit changer */ op->opt_name = MSG_askunits; op->opt_menu = MENU_sizechoice; op->opt_flags = OPT_SUB; op->opt_action = NULL; op++; m->opts = opts; m->numopts = op - opts; } /* * Let user change incore Master Boot Record partitions via menu. */ int edit_mbr(mbr_info_t *mbri) { struct mbr_sector *mbrs = &mbri->mbr; mbr_info_t *ext; struct mbr_partition *part; int i, j; int usefull; int mbr_menu; int activepart; int numbsd; uint bsdstart, bsdsize; uint start; /* Ask full/part */ part = &mbrs->mbr_parts[0]; msg_display(MSG_fullpart, diskdev); process_menu(MENU_fullpart, &usefull); /* DOS fdisk label checking and value setting. */ if (usefull) { /* Count nonempty, non-BSD partitions. */ numbsd = 0; for (i = 0; i < MBR_PART_COUNT; i++) { j = part[i].mbrp_type; if (j == 0) continue; numbsd++; if (j != MBR_PTYPE_NETBSD) numbsd++; } /* Ask if we really want to blow away non-NetBSD stuff */ if (numbsd > 1) { msg_display(MSG_ovrwrite); process_menu(MENU_noyes, NULL); if (!yesno) { if (logging) (void)fprintf(logfp, "User answered no to destroy other data, aborting.\n"); return 0; } } /* Set the partition information for full disk usage. */ while ((ext = mbri->extended)) { mbri->extended = ext->extended; free(ext); } memset(part, 0, MBR_PART_COUNT * sizeof *part); #ifdef BOOTSEL memset(&mbri->nametab, 0, sizeof mbri->nametab); #endif part[0].mbrp_type = MBR_PTYPE_NETBSD; part[0].mbrp_size = dlsize - bsec; part[0].mbrp_start = bsec; part[0].mbrp_flag = MBR_PFLAG_ACTIVE; ptstart = bsec; ptsize = dlsize - bsec; return 1; } mbr_menu = new_menu(NULL, NULL, 16, 0, -1, 15, 70, MC_NOBOX | MC_ALWAYS_SCROLL | MC_NOCLEAR, set_mbr_header, set_mbr_label, NULL, NULL, MSG_Partition_table_ok); if (mbr_menu == -1) return 0; /* Ask for sizes, which partitions, ... */ sizemult = MEG / sectorsize; ask_sizemult(bcylsize); for (;;) { ptstart = 0; ptsize = 0; process_menu(mbr_menu, mbri); activepart = 0; bsdstart = 0; bsdsize = 0; for (ext = mbri; ext; ext = ext->extended) { part = ext->mbr.mbr_parts; for (i = 0; i < MBR_PART_COUNT; part++, i++) { if (part->mbrp_flag != 0) activepart = 1; if (part->mbrp_type != MBR_PTYPE_NETBSD) continue; start = ext->sector + part->mbrp_start; if (start == mbri->install) { ptstart = mbri->install; ptsize = part->mbrp_size; } if (bsdstart != 0) bsdstart = ~0; else { bsdstart = start; bsdsize = part->mbrp_size; } } } /* Install in only netbsd partition if none tagged */ if (ptstart == 0 && bsdstart != ~0) { ptstart = bsdstart; ptsize = bsdsize; } if (ptstart == 0) { if (bsdstart == 0) msg_display(MSG_nobsdpart); else msg_display(MSG_multbsdpart, 0); msg_display_add(MSG_reeditpart, 0); process_menu(MENU_yesno, NULL); if (!yesno) return 0; continue; } if (activepart == 0) { msg_display(MSG_noactivepart); process_menu(MENU_yesno, NULL); if (yesno) continue; } break; } free_menu(mbr_menu); return 1; } const char * get_partname(int typ) { int j; static char unknown[32]; for (j = 0; part_ids[j].id != -1; j++) if (part_ids[j].id == typ) return part_ids[j].name; snprintf(unknown, sizeof unknown, "Unknown (%d)", typ); return unknown; } int read_mbr(const char *disk, mbr_info_t *mbri) { struct mbr_partition *mbrp; struct mbr_sector *mbrs = &mbri->mbr; mbr_info_t *ext = NULL; char diskpath[MAXPATHLEN]; int fd, i; uint32_t ext_base = 0, next_ext = 0, ext_size = 0; int rval = -1; #ifdef BOOTSEL mbr_info_t *ombri = mbri; int bootkey; #endif memset(mbri, 0, sizeof *mbri); /* Open the disk. */ fd = opendisk(disk, O_RDONLY, diskpath, sizeof(diskpath), 0); if (fd < 0) goto bad_mbr; for (;;) { if (pread(fd, mbrs, sizeof *mbrs, (ext_base + next_ext) * (off_t)MBR_SECSIZE) < sizeof *mbrs) break; if (!valid_mbr(mbrs)) break; mbrp = &mbrs->mbr_parts[0]; if (ext_base != 0) { /* sanity check extended chain */ if (MBR_IS_EXTENDED(mbrp[0].mbrp_type)) break; if (mbrp[1].mbrp_type != 0 && !MBR_IS_EXTENDED(mbrp[1].mbrp_type)) break; if (mbrp[2].mbrp_type != 0 || mbrp[3].mbrp_type != 0) break; /* Looks ok, link into extended chain */ mbri->extended = ext; ext->prev_ext = next_ext != 0 ? mbri : NULL; ext->extended = NULL; mbri = ext; ext = NULL; } #if BOOTSEL else { if (mbrs->mbr_bootsel.mbrbs_magic == htole16(MBR_MAGIC)) bootkey = mbrs->mbr_bootsel.mbrbs_defkey; else bootkey = 0; bootkey -= SCAN_1; } if (mbrs->mbr_bootsel.mbrbs_magic == htole16(MBR_MAGIC)) memcpy(mbri->nametab, mbrs->mbr_bootsel.mbrbs_nametab, sizeof mbri->nametab); #endif mbri->sector = next_ext + ext_base; next_ext = 0; rval = 0; for (i = 0; i < MBR_PART_COUNT; mbrp++, i++) { if (mbrp->mbrp_type == 0) { /* type is unused, discard scum */ memset(mbrp, 0, sizeof *mbrp); continue; } mbrp->mbrp_start = le32toh(mbrp->mbrp_start); mbrp->mbrp_size = le32toh(mbrp->mbrp_size); if (MBR_IS_EXTENDED(mbrp->mbrp_type)) { next_ext = mbrp->mbrp_start; if (ext_base == 0) ext_size = mbrp->mbrp_size; } else { mbri->last_mounted[i] = strdup(get_last_mounted( fd, mbri->sector + mbrp->mbrp_start)); #if BOOTSEL if (ombri->install == 0 && strcmp(mbri->last_mounted[i], "/") == 0) ombri->install = mbri->sector + mbrp->mbrp_start; #endif } #if BOOTSEL if (mbri->nametab[i][0] != 0 && bootkey-- == 0) ombri->bootsec = mbri->sector + mbrp->mbrp_start; #endif } if (ext_base != 0) { /* Is there a gap before the next partition? */ unsigned int limit = next_ext; unsigned int base; if (limit == 0) limit = ext_size; mbrp -= MBR_PART_COUNT; base =mbri->sector + mbrp->mbrp_start + mbrp->mbrp_size; if (mbrp->mbrp_type != 0 && ext_base + limit != base) { /* Mock up an extry for the space */ ext = calloc(1, sizeof *ext); if (!ext) break; ext->sector = base; ext->mbr.mbr_magic = htole16(MBR_MAGIC); ext->mbr.mbr_parts[1] = mbrp[1]; mbrp[1].mbrp_type = MBR_PTYPE_EXT; mbrp[1].mbrp_start = base - ext_base; mbrp[1].mbrp_size = limit - mbrp[1].mbrp_start; mbri->extended = ext; ext->prev_ext = mbri; ext->extended = NULL; mbri = ext; ext = NULL; } } if (next_ext == 0 || ext_base + next_ext <= mbri->sector) break; if (ext_base == 0) { ext_base = next_ext; next_ext = 0; } ext = malloc(sizeof *ext); if (!ext) break; mbrs = &ext->mbr; } bad_mbr: free(ext); if (fd >= 0) close(fd); if (rval == -1) { memset(&mbrs->mbr_parts, 0, sizeof mbrs->mbr_parts); mbrs->mbr_magic = htole16(MBR_MAGIC); } return rval; } int write_mbr(const char *disk, mbr_info_t *mbri, int convert) { char diskpath[MAXPATHLEN]; int fd, i, ret = 0; struct mbr_partition *mbrp; u_int32_t pstart, psize; struct mbr_sector *mbrs; mbr_info_t *ext; #ifdef BOOTSEL int netbsd_bootcode; int8_t key = SCAN_1; if (mbri->mbr.mbr_bootsel.mbrbs_magic == htole16(MBR_MAGIC)) { netbsd_bootcode = 1; mbri->mbr.mbr_bootsel.mbrbs_defkey = SCAN_ENTER; } else netbsd_bootcode = 0; #endif /* Open the disk. */ fd = opendisk(disk, O_WRONLY, diskpath, sizeof(diskpath), 0); if (fd < 0) return -1; for (ext = mbri; ext != NULL; ext = ext->extended) { mbrs = &ext->mbr; #ifdef BOOTSEL if (netbsd_bootcode) { mbrs->mbr_bootsel.mbrbs_magic = htole16(MBR_MAGIC); memcpy(&mbrs->mbr_bootsel.mbrbs_nametab, &ext->nametab, sizeof mbrs->mbr_bootsel.mbrbs_nametab); } #endif mbrp = &mbrs->mbr_parts[0]; for (i = 0; i < MBR_PART_COUNT; i++) { if (mbrp[i].mbrp_start == 0 && mbrp[i].mbrp_size == 0) { mbrp[i].mbrp_scyl = 0; mbrp[i].mbrp_shd = 0; mbrp[i].mbrp_ssect = 0; mbrp[i].mbrp_ecyl = 0; mbrp[i].mbrp_ehd = 0; mbrp[i].mbrp_esect = 0; continue; } pstart = mbrp[i].mbrp_start; psize = mbrp[i].mbrp_size; mbrp[i].mbrp_start = htole32(pstart); mbrp[i].mbrp_size = htole32(psize); if (convert) { convert_mbr_chs(bcyl, bhead, bsec, &mbrp[i].mbrp_scyl, &mbrp[i].mbrp_shd, &mbrp[i].mbrp_ssect, pstart); convert_mbr_chs(bcyl, bhead, bsec, &mbrp[i].mbrp_ecyl, &mbrp[i].mbrp_ehd, &mbrp[i].mbrp_esect, pstart + psize - 1); } #ifdef BOOTSEL if (netbsd_bootcode && ext->nametab[i][0] != 0) { if (ext->sector + pstart == mbri->bootsec) mbri->mbr.mbr_bootsel.mbrbs_defkey = key; key++; } #endif } mbrs->mbr_magic = htole16(MBR_MAGIC); /* * Sector zero is written outside the loop after we have * set mbrbs_defkey. */ if (ext->sector != 0 && pwrite(fd, mbrs, sizeof *mbrs, ext->sector * (off_t)MBR_SECSIZE) < 0) ret = -1; } if (pwrite(fd, &mbri->mbr, sizeof mbri->mbr, 0) < 0) ret = -1; (void)close(fd); return ret; } int valid_mbr(struct mbr_sector *mbrs) { return (le16toh(mbrs->mbr_magic) == MBR_MAGIC); } static void convert_mbr_chs(int cyl, int head, int sec, u_int8_t *cylp, u_int8_t *headp, u_int8_t *secp, u_int32_t relsecs) { unsigned int tcyl, temp, thead, tsec; temp = head * sec; tcyl = relsecs / temp; relsecs -= tcyl * temp; thead = relsecs / sec; tsec = relsecs - thead * sec + 1; if (tcyl > MAXCYL) tcyl = MAXCYL; *cylp = MBR_PUT_LSCYL(tcyl); *headp = thead; *secp = MBR_PUT_MSCYLANDSEC(tcyl, tsec); } /* * This function is ONLY to be used as a last resort to provide a * hint for the user. Ports should provide a more reliable way * of getting the BIOS geometry. The i386 code, for example, * uses the BIOS geometry as passed on from the bootblocks, * and only uses this as a hint to the user when that information * is not present, or a match could not be made with a NetBSD * device. */ int guess_biosgeom_from_mbr(mbr_info_t *mbri, int *cyl, int *head, int *sec) { struct mbr_sector *mbrs = &mbri->mbr; struct mbr_partition *parts = &mbrs->mbr_parts[0]; int xcylinders, xheads, xsectors, i, j; int c1, h1, s1, c2, h2, s2; unsigned long a1, a2; uint64_t num, denom; /* * The physical parameters may be invalid as bios geometry. * If we cannot determine the actual bios geometry, we are * better off picking a likely 'faked' geometry than leaving * the invalid physical one. */ xcylinders = dlcyl; xheads = dlhead; xsectors = dlsec; if (xcylinders > MAXCYL || xheads > MAXHEAD || xsectors > MAXSECTOR) { xsectors = MAXSECTOR; xheads = MAXHEAD; xcylinders = dlsize / (MAXSECTOR * MAXHEAD); if (xcylinders > MAXCYL) xcylinders = MAXCYL; } *cyl = xcylinders; *head = xheads; *sec = xsectors; xheads = -1; /* Try to deduce the number of heads from two different mappings. */ for (i = 0; i < MBR_PART_COUNT * 2 - 1; i++) { if (get_mapping(parts, i, &c1, &h1, &s1, &a1) < 0) continue; for (j = i + 1; j < MBR_PART_COUNT * 2; j++) { if (get_mapping(parts, j, &c2, &h2, &s2, &a2) < 0) continue; a1 -= s1; a2 -= s2; num = (uint64_t)h1 * a2 - (quad_t)h2 * a1; denom = (uint64_t)c2 * a1 - (quad_t)c1 * a2; if (denom != 0 && num % denom == 0) { xheads = (int)(num / denom); xsectors = a1 / (c1 * xheads + h1); break; } } if (xheads != -1) break; } if (xheads == -1) return -1; /* * Estimate the number of cylinders. * XXX relies on get_disks having been called. */ xcylinders = dlsize / xheads / xsectors; if (dlsize != xcylinders * xheads * xsectors) xcylinders++; /* * Now verify consistency with each of the partition table entries. * Be willing to shove cylinders up a little bit to make things work, * but translation mismatches are fatal. */ for (i = 0; i < MBR_PART_COUNT * 2; i++) { if (get_mapping(parts, i, &c1, &h1, &s1, &a1) < 0) continue; if (c1 >= MAXCYL - 1) /* Ignore anything that is near the CHS limit */ continue; if (xsectors * (c1 * xheads + h1) + s1 != a1) return -1; } /* * Everything checks out. Reset the geometry to use for further * calculations. */ *cyl = MIN(xcylinders, MAXCYL); *head = xheads; *sec = xsectors; return 0; } static int get_mapping(struct mbr_partition *parts, int i, int *cylinder, int *head, int *sector, unsigned long *absolute) { struct mbr_partition *apart = &parts[i / 2]; if (apart->mbrp_type == 0) return -1; if (i % 2 == 0) { *cylinder = MBR_PCYL(apart->mbrp_scyl, apart->mbrp_ssect); *head = apart->mbrp_shd; *sector = MBR_PSECT(apart->mbrp_ssect) - 1; *absolute = le32toh(apart->mbrp_start); } else { *cylinder = MBR_PCYL(apart->mbrp_ecyl, apart->mbrp_esect); *head = apart->mbrp_ehd; *sector = MBR_PSECT(apart->mbrp_esect) - 1; *absolute = le32toh(apart->mbrp_start) + le32toh(apart->mbrp_size) - 1; } /* Sanity check the data against max values */ if ((((*cylinder * MAXHEAD) + *head) * MAXSECTOR + *sector) < *absolute) /* cannot be a CHS mapping */ return -1; return 0; }