| 1 | /* |
| 2 | * Copyright (c) 1999 Robert Nordier |
| 3 | * All rights reserved. |
| 4 | * |
| 5 | * Redistribution and use in source and binary forms, with or without |
| 6 | * modification, are permitted provided that the following conditions |
| 7 | * are met: |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in the |
| 12 | * documentation and/or other materials provided with the distribution. |
| 13 | * |
| 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND |
| 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS |
| 18 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, |
| 19 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT |
| 20 | * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| 21 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| 22 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| 23 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
| 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 25 | * |
| 26 | * $FreeBSD: src/usr.sbin/boot0cfg/boot0cfg.c,v 1.7.2.4 2002/03/16 01:06:51 mikeh Exp $ |
| 27 | */ |
| 28 | |
| 29 | #include <sys/param.h> |
| 30 | #include <sys/diskmbr.h> |
| 31 | #include <sys/stat.h> |
| 32 | |
| 33 | #include <err.h> |
| 34 | #include <errno.h> |
| 35 | #include <fcntl.h> |
| 36 | #include <paths.h> |
| 37 | #include <stdio.h> |
| 38 | #include <stdlib.h> |
| 39 | #include <string.h> |
| 40 | #include <unistd.h> |
| 41 | |
| 42 | #define MBRSIZE 512 /* master boot record size */ |
| 43 | |
| 44 | #define OFF_VERSION 0x1b0 /* offset: version number */ |
| 45 | #define OFF_OPT 0x1b9 /* offset: default boot option */ |
| 46 | #define OFF_DRIVE 0x1ba /* offset: setdrv drive */ |
| 47 | #define OFF_FLAGS 0x1bb /* offset: option flags */ |
| 48 | #define OFF_TICKS 0x1bc /* offset: clock ticks */ |
| 49 | #define OFF_PTBL 0x1be /* offset: partition table */ |
| 50 | #define OFF_MAGIC 0x1fe /* offset: magic number */ |
| 51 | |
| 52 | #define cv2(p) ((p)[0] | (p)[1] << 010) |
| 53 | |
| 54 | #define mk2(p, x) \ |
| 55 | (p)[0] = (u_int8_t)(x), \ |
| 56 | (p)[1] = (u_int8_t)((x) >> 010) |
| 57 | |
| 58 | static const struct { |
| 59 | const char *tok; |
| 60 | int def; |
| 61 | } opttbl[] = { |
| 62 | {"packet", 0}, |
| 63 | {"update", 1}, |
| 64 | {"setdrv", 0} |
| 65 | }; |
| 66 | static const int nopt = sizeof(opttbl) / sizeof(opttbl[0]); |
| 67 | |
| 68 | static const char fmt0[] = "# flag start chs type" |
| 69 | " end chs offset size\n"; |
| 70 | |
| 71 | static const char fmt1[] = "%d 0x%02x %4u:%3u:%2u 0x%02x" |
| 72 | " %4u:%3u:%2u %10u %10u\n"; |
| 73 | |
| 74 | static int read_mbr(const char *, u_int8_t **, int); |
| 75 | static void write_mbr(const char *, int, u_int8_t *, int); |
| 76 | static void display_mbr(u_int8_t *); |
| 77 | static int boot0version(const u_int8_t *); |
| 78 | static int boot0bs(const u_int8_t *); |
| 79 | static void stropt(const char *, int *, int *); |
| 80 | static char *mkrdev(const char *); |
| 81 | static int argtoi(const char *, int, int, int); |
| 82 | static void usage(void); |
| 83 | |
| 84 | /* |
| 85 | * Boot manager installation/configuration utility. |
| 86 | */ |
| 87 | int |
| 88 | main(int argc, char *argv[]) |
| 89 | { |
| 90 | u_int8_t *mbr, *boot0; |
| 91 | int boot0_size, mbr_size; |
| 92 | const char *bpath, *fpath; |
| 93 | char *disk; |
| 94 | int B_flag, v_flag, o_flag; |
| 95 | int d_arg, m_arg, s_arg, t_arg; |
| 96 | int o_and, o_or; |
| 97 | int up, c; |
| 98 | |
| 99 | bpath = "/boot/boot0"; |
| 100 | fpath = NULL; |
| 101 | B_flag = v_flag = o_flag = 0; |
| 102 | d_arg = m_arg = s_arg = t_arg = -1; |
| 103 | o_and = 0xff; |
| 104 | o_or = 0; |
| 105 | while ((c = getopt(argc, argv, "Bvb:d:f:m:o:s:t:")) != -1) |
| 106 | switch (c) { |
| 107 | case 'B': |
| 108 | B_flag = 1; |
| 109 | break; |
| 110 | case 'v': |
| 111 | v_flag = 1; |
| 112 | break; |
| 113 | case 'b': |
| 114 | bpath = optarg; |
| 115 | break; |
| 116 | case 'd': |
| 117 | d_arg = argtoi(optarg, 0, 0xff, 'd'); |
| 118 | break; |
| 119 | case 'f': |
| 120 | fpath = optarg; |
| 121 | break; |
| 122 | case 'm': |
| 123 | m_arg = argtoi(optarg, 0, 0xf, 'm'); |
| 124 | break; |
| 125 | case 'o': |
| 126 | stropt(optarg, &o_and, &o_or); |
| 127 | o_flag = 1; |
| 128 | break; |
| 129 | case 's': |
| 130 | s_arg = argtoi(optarg, 1, 5, 's'); |
| 131 | break; |
| 132 | case 't': |
| 133 | t_arg = argtoi(optarg, 1, 0xffff, 't'); |
| 134 | break; |
| 135 | default: |
| 136 | usage(); |
| 137 | } |
| 138 | argc -= optind; |
| 139 | argv += optind; |
| 140 | if (argc != 1) |
| 141 | usage(); |
| 142 | disk = mkrdev(*argv); |
| 143 | up = B_flag || d_arg != -1 || m_arg != -1 || o_flag || s_arg != -1 |
| 144 | || t_arg != -1; |
| 145 | |
| 146 | /* open the disk and read in the existing mbr */ |
| 147 | mbr_size = read_mbr(disk, &mbr, !B_flag); |
| 148 | |
| 149 | /* save the existing MBR if we are asked to do so */ |
| 150 | if (fpath) |
| 151 | write_mbr(fpath, O_CREAT | O_TRUNC, mbr, mbr_size); |
| 152 | |
| 153 | /* |
| 154 | * If we are installing the boot loader, read it from disk and copy the |
| 155 | * slice table over from the existing MBR. If not, then point boot0 |
| 156 | * back at the MBR we just read in. After this, boot0 is the data to |
| 157 | * write back to disk if we are going to do a write. |
| 158 | */ |
| 159 | if (B_flag) { |
| 160 | boot0_size = read_mbr(bpath, &boot0, 1); |
| 161 | memcpy(boot0 + OFF_PTBL, mbr + OFF_PTBL, |
| 162 | sizeof(struct dos_partition) * NDOSPART); |
| 163 | } else { |
| 164 | boot0 = mbr; |
| 165 | boot0_size = mbr_size; |
| 166 | } |
| 167 | |
| 168 | /* set the drive */ |
| 169 | if (d_arg != -1) |
| 170 | boot0[OFF_DRIVE] = d_arg; |
| 171 | |
| 172 | /* set various flags */ |
| 173 | if (m_arg != -1) { |
| 174 | boot0[OFF_FLAGS] &= 0xf0; |
| 175 | boot0[OFF_FLAGS] |= m_arg; |
| 176 | } |
| 177 | if (o_flag) { |
| 178 | boot0[OFF_FLAGS] &= o_and; |
| 179 | boot0[OFF_FLAGS] |= o_or; |
| 180 | } |
| 181 | |
| 182 | /* set the default boot selection */ |
| 183 | if (s_arg != -1) |
| 184 | boot0[OFF_OPT] = s_arg - 1; |
| 185 | |
| 186 | /* set the timeout */ |
| 187 | if (t_arg != -1) |
| 188 | mk2(boot0 + OFF_TICKS, t_arg); |
| 189 | |
| 190 | /* write the MBR back to disk */ |
| 191 | if (up) |
| 192 | write_mbr(disk, 0, boot0, boot0_size); |
| 193 | |
| 194 | /* display the MBR */ |
| 195 | if (v_flag) |
| 196 | display_mbr(boot0); |
| 197 | |
| 198 | /* clean up */ |
| 199 | if (mbr != boot0) |
| 200 | free(boot0); |
| 201 | free(mbr); |
| 202 | free(disk); |
| 203 | |
| 204 | return 0; |
| 205 | } |
| 206 | |
| 207 | /* |
| 208 | * Read in the MBR of the disk. If it is boot0, then use the version to |
| 209 | * read in all of it if necessary. Use pointers to return a malloc'd |
| 210 | * buffer containing the MBR and then return its size. |
| 211 | */ |
| 212 | static int |
| 213 | read_mbr(const char *disk, u_int8_t **mbr, int check_version) |
| 214 | { |
| 215 | u_int8_t buf[MBRSIZE]; |
| 216 | int mbr_size, fd; |
| 217 | ssize_t n; |
| 218 | |
| 219 | if ((fd = open(disk, O_RDONLY)) == -1) |
| 220 | err(1, "open %s", disk); |
| 221 | if ((n = read(fd, buf, MBRSIZE)) == -1) |
| 222 | err(1, "read %s", disk); |
| 223 | if (n != MBRSIZE) |
| 224 | errx(1, "%s: short read", disk); |
| 225 | if (cv2(buf + OFF_MAGIC) != 0xaa55) |
| 226 | errx(1, "%s: bad magic", disk); |
| 227 | |
| 228 | if (!boot0bs(buf)) { |
| 229 | if (check_version) |
| 230 | errx(1, "%s: unknown or incompatible boot code", disk); |
| 231 | } else if (boot0version(buf) == 0x101) { |
| 232 | mbr_size = 1024; |
| 233 | if ((*mbr = malloc(mbr_size)) == NULL) |
| 234 | errx(1, "%s: unable to allocate read buffer", disk); |
| 235 | if (lseek(fd, 0, SEEK_SET) == -1 || |
| 236 | (n = read(fd, *mbr, mbr_size)) == -1) |
| 237 | err(1, "%s", disk); |
| 238 | if (n != mbr_size) |
| 239 | errx(1, "%s: short read", disk); |
| 240 | close(fd); |
| 241 | return (mbr_size); |
| 242 | } |
| 243 | |
| 244 | if ((*mbr = malloc(sizeof(buf))) == NULL) |
| 245 | errx(1, "%s: unable to allocate mbr buffer", disk); |
| 246 | memcpy(*mbr, buf, sizeof(buf)); |
| 247 | close(fd); |
| 248 | return sizeof(buf); |
| 249 | } |
| 250 | |
| 251 | /* |
| 252 | * Write out the mbr to the specified file. |
| 253 | */ |
| 254 | static void |
| 255 | write_mbr(const char *fname, int flags, u_int8_t *mbr, int mbr_size) |
| 256 | { |
| 257 | int fd; |
| 258 | ssize_t n; |
| 259 | |
| 260 | fd = open(fname, O_WRONLY | flags, 0666); |
| 261 | if (fd != -1) { |
| 262 | n = write(fd, mbr, mbr_size); |
| 263 | close(fd); |
| 264 | if (n != mbr_size) |
| 265 | errx(1, "%s: short write", fname); |
| 266 | return; |
| 267 | } |
| 268 | if (flags != 0) |
| 269 | err(1, "%s", fname); |
| 270 | err(1, "write_mbr: %s", fname); |
| 271 | } |
| 272 | |
| 273 | /* |
| 274 | * Outputs an informative dump of the data in the MBR to stdout. |
| 275 | */ |
| 276 | static void |
| 277 | display_mbr(u_int8_t *mbr) |
| 278 | { |
| 279 | struct dos_partition *part; |
| 280 | int i, version; |
| 281 | |
| 282 | part = (struct dos_partition *)(mbr + DOSPARTOFF); |
| 283 | printf(fmt0); |
| 284 | for (i = 0; i < NDOSPART; i++) |
| 285 | if (part[i].dp_typ) |
| 286 | printf(fmt1, 1 + i, part[i].dp_flag, |
| 287 | part[i].dp_scyl + ((part[i].dp_ssect & 0xc0) << 2), |
| 288 | part[i].dp_shd, part[i].dp_ssect & 0x3f, part[i].dp_typ, |
| 289 | part[i].dp_ecyl + ((part[i].dp_esect & 0xc0) << 2), |
| 290 | part[i].dp_ehd, part[i].dp_esect & 0x3f, part[i].dp_start, |
| 291 | part[i].dp_size); |
| 292 | printf("\n"); |
| 293 | version = boot0version(mbr); |
| 294 | printf("version=%d.%d drive=0x%x mask=0x%x ticks=%u\noptions=", |
| 295 | version >> 8, version & 0xff, mbr[OFF_DRIVE], |
| 296 | mbr[OFF_FLAGS] & 0xf, cv2(mbr + OFF_TICKS)); |
| 297 | for (i = 0; i < nopt; i++) { |
| 298 | if (i) |
| 299 | printf(","); |
| 300 | if (!(mbr[OFF_FLAGS] & 1 << (7 - i)) ^ opttbl[i].def) |
| 301 | printf("no"); |
| 302 | printf("%s", opttbl[i].tok); |
| 303 | } |
| 304 | printf("\n"); |
| 305 | printf("default_selection=F%d (", mbr[OFF_OPT] + 1); |
| 306 | if (mbr[OFF_OPT] < 4) |
| 307 | printf("Slice %d", mbr[OFF_OPT] + 1); |
| 308 | else |
| 309 | printf("Drive 1"); |
| 310 | printf(")\n"); |
| 311 | } |
| 312 | |
| 313 | /* |
| 314 | * Return the boot0 version with the minor revision in the low byte, and |
| 315 | * the major revision in the next higher byte. |
| 316 | */ |
| 317 | static int |
| 318 | boot0version(const u_int8_t *bs) |
| 319 | { |
| 320 | static u_int8_t idold[] = {0xfe, 0x45, 0xf2, 0xe9, 0x00, 0x8a}; |
| 321 | |
| 322 | /* Check for old version, and return 0x100 if found. */ |
| 323 | if (memcmp(bs + 0x1c, idold, sizeof(idold)) == 0) |
| 324 | return 0x100; |
| 325 | |
| 326 | /* We have a newer boot0, so extract the version number and return it. */ |
| 327 | return *(const int *)(bs + OFF_VERSION) & 0xffff; |
| 328 | } |
| 329 | |
| 330 | /* |
| 331 | * Decide if we have valid boot0 boot code by looking for |
| 332 | * characteristic byte sequences at fixed offsets. |
| 333 | */ |
| 334 | static int |
| 335 | boot0bs(const u_int8_t *bs) |
| 336 | { |
| 337 | static u_int8_t id0[] = {0xfc, 0x31, 0xc0, 0x8e, 0xc0, 0x8e, 0xd8, |
| 338 | 0x8e, 0xd0, 0xbc, 0x00, 0x7c }; |
| 339 | static u_int8_t id1[] = {0xfc, 0xb8, 0x00, 0x09, 0x8e, 0xc0, 0x8e, |
| 340 | 0xd0, 0xbc, 0x00, 0x10 ,0x31 }; |
| 341 | static u_int8_t id2[] = {'D', 'r', 'i', 'v', 'e', ' '}; |
| 342 | static struct { |
| 343 | unsigned off; |
| 344 | unsigned len; |
| 345 | u_int8_t *key; |
| 346 | } ident[] = { |
| 347 | {0x0, sizeof(id0), id0}, |
| 348 | {0x0, sizeof(id1), id1}, |
| 349 | {0x1b2, sizeof(id2), id2} |
| 350 | }; |
| 351 | unsigned int i; |
| 352 | int count = 0; |
| 353 | |
| 354 | for (i = 0; i < sizeof(ident) / sizeof(ident[0]); i++) { |
| 355 | if (memcmp(bs + ident[i].off, ident[i].key, ident[i].len) == 0) |
| 356 | ++count; |
| 357 | } |
| 358 | if (count >= 2) |
| 359 | return 1; |
| 360 | return 0; |
| 361 | } |
| 362 | |
| 363 | /* |
| 364 | * Adjust "and" and "or" masks for a -o option argument. |
| 365 | */ |
| 366 | static void |
| 367 | stropt(const char *arg, int *xa, int *xo) |
| 368 | { |
| 369 | const char *q; |
| 370 | char *s, *s1; |
| 371 | int inv, i, x; |
| 372 | |
| 373 | if (!(s = strdup(arg))) |
| 374 | err(1, NULL); |
| 375 | for (s1 = s; (q = strtok(s1, ",")); s1 = NULL) { |
| 376 | if ((inv = !strncmp(q, "no", 2))) |
| 377 | q += 2; |
| 378 | for (i = 0; i < nopt; i++) |
| 379 | if (!strcmp(q, opttbl[i].tok)) |
| 380 | break; |
| 381 | if (i == nopt) |
| 382 | errx(1, "%s: Unknown -o option", q); |
| 383 | if (opttbl[i].def) |
| 384 | inv ^= 1; |
| 385 | x = 1 << (7 - i); |
| 386 | if (inv) |
| 387 | *xa &= ~x; |
| 388 | else |
| 389 | *xo |= x; |
| 390 | } |
| 391 | free(s); |
| 392 | } |
| 393 | |
| 394 | /* |
| 395 | * Produce a device path for a "canonical" name, where appropriate. |
| 396 | */ |
| 397 | static char * |
| 398 | mkrdev(const char *fname) |
| 399 | { |
| 400 | char buf[MAXPATHLEN]; |
| 401 | char *s; |
| 402 | |
| 403 | if (!strchr(fname, '/')) { |
| 404 | snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, fname); |
| 405 | s = strdup(buf); |
| 406 | } else |
| 407 | s = strdup(fname); |
| 408 | |
| 409 | if (s == NULL) |
| 410 | errx(1, "No more memory"); |
| 411 | return s; |
| 412 | } |
| 413 | |
| 414 | /* |
| 415 | * Convert and check an option argument. |
| 416 | */ |
| 417 | static int |
| 418 | argtoi(const char *arg, int lo, int hi, int opt) |
| 419 | { |
| 420 | char *s; |
| 421 | long x; |
| 422 | |
| 423 | errno = 0; |
| 424 | x = strtol(arg, &s, 0); |
| 425 | if (errno || !*arg || *s || x < lo || x > hi) |
| 426 | errx(1, "%s: Bad argument to -%c option", arg, opt); |
| 427 | return x; |
| 428 | } |
| 429 | |
| 430 | /* |
| 431 | * Display usage information. |
| 432 | */ |
| 433 | static void |
| 434 | usage(void) |
| 435 | { |
| 436 | fprintf(stderr, "%s\n%s\n", |
| 437 | "usage: boot0cfg [-Bv] [-b boot0] [-d drive] [-f file] [-m mask]", |
| 438 | " [-o options] [-s slice] [-t ticks] disk"); |
| 439 | exit(1); |
| 440 | } |