subr_disk_mbr.c revision 1.23.2.2 1 /* $NetBSD: subr_disk_mbr.c,v 1.23.2.2 2007/03/12 05:58:40 rmind Exp $ */
2
3 /*
4 * Copyright (c) 1982, 1986, 1988 Regents of the University of California.
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 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 *
31 * @(#)ufs_disksubr.c 7.16 (Berkeley) 5/4/91
32 */
33
34 /*
35 * Code to find a NetBSD label on a disk that contains an i386 style MBR.
36 * The first NetBSD label found in the 2nd sector of a NetBSD partition
37 * is used.
38 * If we don't find a label searching the MBR, we look at the start of the
39 * disk, if that fails then a label is faked up from the MBR.
40 *
41 * If there isn't a disklabel or anything in the MBR then partition a
42 * is set to cover the whole disk.
43 * Useful for files that contain single filesystems (etc).
44 *
45 * This code will read host endian netbsd labels from little endian MBR.
46 *
47 * Based on the i386 disksubr.c
48 *
49 * Since the mbr only has 32bit fields for sector addresses, we do the same.
50 *
51 * XXX There are potential problems writing labels to disks where there
52 * is only space for 8 netbsd partitions but this code has been compiled
53 * with MAXPARTITIONS=16.
54 */
55
56 #include <sys/cdefs.h>
57 __KERNEL_RCSID(0, "$NetBSD: subr_disk_mbr.c,v 1.23.2.2 2007/03/12 05:58:40 rmind Exp $");
58
59 #include <sys/param.h>
60 #include <sys/systm.h>
61 #include <sys/buf.h>
62 #include <sys/disklabel.h>
63 #include <sys/disk.h>
64 #include <sys/syslog.h>
65
66 #include "opt_mbr.h"
67
68 typedef struct mbr_partition mbr_partition_t;
69
70 /*
71 * We allocate a buffer 2 sectors large, and look in both....
72 * That means we find labels written by other ports with different offsets.
73 * LABELSECTOR and LABELOFFSET are only used if the disk doesn't have a label.
74 */
75 #if LABELSECTOR > 1 || LABELOFFSET > 512
76 #error Invalid LABELSECTOR or LABELOFFSET
77 #endif
78
79 #define MBR_LABELSECTOR 1
80
81 #define SCAN_CONTINUE 0
82 #define SCAN_FOUND 1
83 #define SCAN_ERROR 2
84
85 typedef struct mbr_args {
86 struct disklabel *lp;
87 void (*strat)(struct buf *);
88 struct buf *bp;
89 const char *msg;
90 int error;
91 int written; /* number of times we wrote label */
92 int found_mbr; /* set if disk has a valid mbr */
93 uint label_sector; /* where we found the label */
94 int action;
95 #define READ_LABEL 1
96 #define UPDATE_LABEL 2
97 #define WRITE_LABEL 3
98 } mbr_args_t;
99
100 static int validate_label(mbr_args_t *, uint);
101 static int look_netbsd_part(mbr_args_t *, mbr_partition_t *, int, uint);
102 static int write_netbsd_label(mbr_args_t *, mbr_partition_t *, int, uint);
103
104 static int
105 read_sector(mbr_args_t *a, uint sector, int count)
106 {
107 struct buf *bp = a->bp;
108 int error;
109
110 bp->b_blkno = sector;
111 bp->b_bcount = count * a->lp->d_secsize;
112 bp->b_flags = (bp->b_flags & ~(B_WRITE | B_DONE)) | B_READ;
113 bp->b_cylinder = sector / a->lp->d_secpercyl;
114 (*a->strat)(bp);
115 error = biowait(bp);
116 if (error != 0)
117 a->error = error;
118 return error;
119 }
120
121 /*
122 * Scan MBR for partitions, call 'action' routine for each.
123 */
124
125 static int
126 scan_mbr(mbr_args_t *a, int (*actn)(mbr_args_t *, mbr_partition_t *, int, uint))
127 {
128 mbr_partition_t ptns[MBR_PART_COUNT];
129 mbr_partition_t *dp;
130 struct mbr_sector *mbr;
131 uint ext_base, this_ext, next_ext;
132 int rval;
133 int i;
134 int j;
135 #ifdef COMPAT_386BSD_MBRPART
136 int dp_386bsd = -1;
137 int ap_386bsd = -1;
138 #endif
139
140 ext_base = 0;
141 this_ext = 0;
142 for (;;) {
143 if (read_sector(a, this_ext, 1)) {
144 a->msg = "dos partition I/O error";
145 return SCAN_ERROR;
146 }
147
148 /* Note: Magic number is little-endian. */
149 mbr = (void *)a->bp->b_data;
150 if (mbr->mbr_magic != htole16(MBR_MAGIC))
151 return SCAN_CONTINUE;
152
153 /* Copy data out of buffer so action can use bp */
154 memcpy(ptns, &mbr->mbr_parts, sizeof ptns);
155
156 /* Look for drivers and skip them */
157 if (ext_base == 0 && ptns[0].mbrp_type == MBR_PTYPE_DM6_DDO) {
158 /* We've found a DM6 DDO partition type (used by
159 * the Ontrack Disk Manager drivers).
160 *
161 * Ensure that there are no other partitions in the
162 * MBR and jump to the real partition table (stored
163 * in the first sector of the second track). */
164 bool ok = true;
165
166 for (i = 1; i < MBR_PART_COUNT; i++)
167 if (ptns[i].mbrp_type != MBR_PTYPE_UNUSED)
168 ok = false;
169
170 if (ok) {
171 this_ext = le32toh(a->lp->d_secpercyl /
172 a->lp->d_ntracks);
173 continue;
174 }
175 }
176
177 /* look for NetBSD partition */
178 next_ext = 0;
179 dp = ptns;
180 j = 0;
181 for (i = 0; i < MBR_PART_COUNT; i++, dp++) {
182 if (dp->mbrp_type == MBR_PTYPE_UNUSED)
183 continue;
184 /* Check end of partition is inside disk limits */
185 if ((uint64_t)ext_base + le32toh(dp->mbrp_start) +
186 le32toh(dp->mbrp_size) > a->lp->d_secperunit) {
187 /* This mbr doesn't look good.... */
188 a->msg = "mbr partition exceeds disk size";
189 /* ...but don't report this as an error (yet) */
190 return SCAN_CONTINUE;
191 }
192 a->found_mbr = 1;
193 if (MBR_IS_EXTENDED(dp->mbrp_type)) {
194 next_ext = le32toh(dp->mbrp_start);
195 continue;
196 }
197 #ifdef COMPAT_386BSD_MBRPART
198 if (dp->mbrp_type == MBR_PTYPE_386BSD) {
199 /*
200 * If more than one matches, take last,
201 * as NetBSD install tool does.
202 */
203 if (this_ext == 0) {
204 dp_386bsd = i;
205 ap_386bsd = j;
206 }
207 continue;
208 }
209 #endif
210 rval = (*actn)(a, dp, j, this_ext);
211 if (rval != SCAN_CONTINUE)
212 return rval;
213 j++;
214 }
215 if (next_ext == 0)
216 break;
217 if (ext_base == 0) {
218 ext_base = next_ext;
219 next_ext = 0;
220 }
221 next_ext += ext_base;
222 if (next_ext <= this_ext)
223 break;
224 this_ext = next_ext;
225 }
226 #ifdef COMPAT_386BSD_MBRPART
227 if (this_ext == 0 && dp_386bsd != -1)
228 return (*actn)(a, &ptns[dp_386bsd], ap_386bsd, 0);
229 #endif
230 return SCAN_CONTINUE;
231 }
232
233 /*
234 * Attempt to read a disk label from a device
235 * using the indicated strategy routine.
236 * The label must be partly set up before this:
237 * secpercyl, secsize and anything required for a block i/o read
238 * operation in the driver's strategy/start routines
239 * must be filled in before calling us.
240 *
241 * If dos partition table requested, attempt to load it and
242 * find disklabel inside a DOS partition. Also, if bad block
243 * table needed, attempt to extract it as well. Return buffer
244 * for use in signalling errors if requested.
245 *
246 * Returns null on success and an error string on failure.
247 */
248 const char *
249 readdisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
250 struct cpu_disklabel *osdep)
251 {
252 struct dkbad *bdp;
253 int rval;
254 int i;
255 mbr_args_t a;
256
257 memset(&a, 0, sizeof a);
258 a.lp = lp;
259 a.strat = strat;
260 a.action = READ_LABEL;
261
262 /* minimal requirements for architypal disk label */
263 if (lp->d_secsize == 0)
264 lp->d_secsize = DEV_BSIZE;
265 if (lp->d_secperunit == 0)
266 lp->d_secperunit = 0x1fffffff;
267 lp->d_npartitions = RAW_PART + 1;
268 for (i = 0; i < RAW_PART; i++) {
269 lp->d_partitions[i].p_size = 0;
270 lp->d_partitions[i].p_offset = 0;
271 }
272 if (lp->d_partitions[RAW_PART].p_size == 0)
273 lp->d_partitions[RAW_PART].p_size = lp->d_secperunit;
274 lp->d_partitions[RAW_PART].p_offset = 0;
275
276 /*
277 * Set partition 'a' to be the whole disk.
278 * Cleared if we find an mbr or a netbsd label.
279 */
280 lp->d_partitions[0].p_size = lp->d_partitions[RAW_PART].p_size;
281 lp->d_partitions[0].p_fstype = FS_BSDFFS;
282
283 /* get a buffer and initialize it */
284 a.bp = geteblk(2 * (int)lp->d_secsize);
285 a.bp->b_dev = dev;
286
287 if (osdep)
288 /*
289 * Scan mbr searching for netbsd partition and saving
290 * bios partition information to use if the netbsd one
291 * is absent.
292 */
293 rval = scan_mbr(&a, look_netbsd_part);
294 else
295 rval = SCAN_CONTINUE;
296
297 if (rval == SCAN_CONTINUE) {
298 /* Look at start of disk */
299 rval = validate_label(&a, 0);
300 }
301
302 #if 0
303 /*
304 * Save sector where we found the label for the 'don't overwrite
305 * the label' check in bounds_check_with_label.
306 */
307 if (rval == SCAN_FOUND)
308 xxx->label_sector = a.label_sector;
309 #endif
310
311 /* Obtain bad sector table if requested and present */
312 if (rval == SCAN_FOUND && osdep && (lp->d_flags & D_BADSECT)) {
313 struct dkbad *db;
314 int blkno;
315
316 bdp = &osdep->bad;
317 i = 0;
318 rval = SCAN_ERROR;
319 do {
320 /* read a bad sector table */
321 blkno = lp->d_secperunit - lp->d_nsectors + i;
322 if (lp->d_secsize > DEV_BSIZE)
323 blkno *= lp->d_secsize / DEV_BSIZE;
324 else
325 blkno /= DEV_BSIZE / lp->d_secsize;
326 /* if successful, validate, otherwise try another */
327 if (read_sector(&a, blkno, 1)) {
328 a.msg = "bad sector table I/O error";
329 continue;
330 }
331 db = (struct dkbad *)(a.bp->b_data);
332 #define DKBAD_MAGIC 0x4321
333 if (db->bt_mbz != 0 || db->bt_flag != DKBAD_MAGIC) {
334 a.msg = "bad sector table corrupted";
335 continue;
336 }
337 rval = SCAN_FOUND;
338 *bdp = *db;
339 break;
340 } while ((a.bp->b_flags & B_ERROR) && (i += 2) < 10 &&
341 i < lp->d_nsectors);
342 }
343
344 brelse(a.bp);
345 if (rval == SCAN_ERROR || rval == SCAN_CONTINUE)
346 return a.msg;
347 return NULL;
348 }
349
350 static int
351 look_netbsd_part(mbr_args_t *a, mbr_partition_t *dp, int slot, uint ext_base)
352 {
353 struct partition *pp;
354 int ptn_base = ext_base + le32toh(dp->mbrp_start);
355 int rval;
356
357 if (
358 #ifdef COMPAT_386BSD_MBRPART
359 dp->mbrp_type == MBR_PTYPE_386BSD ||
360 #endif
361 dp->mbrp_type == MBR_PTYPE_NETBSD) {
362 rval = validate_label(a, ptn_base);
363
364 #if RAW_PART == 3
365 /* Put actual location where we found the label into ptn 2 */
366 if (rval == SCAN_FOUND || a->lp->d_partitions[2].p_size == 0) {
367 a->lp->d_partitions[2].p_size = le32toh(dp->mbrp_size);
368 a->lp->d_partitions[2].p_offset = ptn_base;
369 }
370 #endif
371
372 /* If we got a netbsd label look no further */
373 if (rval == SCAN_FOUND)
374 return rval;
375 }
376
377 /* Install main partitions into e..h and extended into i+ */
378 if (ext_base == 0)
379 slot += 4;
380 else {
381 slot = 4 + MBR_PART_COUNT;
382 pp = &a->lp->d_partitions[slot];
383 for (; slot < MAXPARTITIONS; pp++, slot++) {
384 /* This gets called twice - avoid duplicates */
385 if (pp->p_offset == ptn_base &&
386 pp->p_size == le32toh(dp->mbrp_size))
387 break;
388 if (pp->p_size == 0)
389 break;
390 }
391 }
392
393 if (slot < MAXPARTITIONS) {
394 /* Stop 'a' being the entire disk */
395 a->lp->d_partitions[0].p_size = 0;
396 a->lp->d_partitions[0].p_fstype = 0;
397
398 /* save partition info */
399 pp = &a->lp->d_partitions[slot];
400 pp->p_offset = ptn_base;
401 pp->p_size = le32toh(dp->mbrp_size);
402 pp->p_fstype = xlat_mbr_fstype(dp->mbrp_type);
403
404 if (slot >= a->lp->d_npartitions)
405 a->lp->d_npartitions = slot + 1;
406 }
407
408 return SCAN_CONTINUE;
409 }
410
411
412 static int
413 validate_label(mbr_args_t *a, uint label_sector)
414 {
415 struct disklabel *dlp;
416 char *dlp_lim, *dlp_byte;
417 int error;
418
419 /* Next, dig out disk label */
420 if (read_sector(a, label_sector, 2)) {
421 a->msg = "disk label read failed";
422 return SCAN_ERROR;
423 }
424
425 /* Locate disk label within block and validate */
426 /*
427 * XXX (dsl) This search may be a waste of time, a lot of other i386
428 * code assumes the label is at offset LABELOFFSET (=0) in the sector.
429 *
430 * If we want to support disks from other netbsd ports, then the
431 * code should also allow for a shorter label nearer the end of
432 * the disk sector, and (IIRC) labels within 8k of the disk start.
433 */
434 dlp = (void *)a->bp->b_data;
435 dlp_lim = (char *)a->bp->b_data + a->bp->b_bcount - sizeof *dlp;
436 for (;; dlp = (void *)((char *)dlp + sizeof(long))) {
437 if ((char *)dlp > dlp_lim) {
438 if (a->action != WRITE_LABEL)
439 return SCAN_CONTINUE;
440 /* Write at arch. dependant default location */
441 dlp_byte = (char *)a->bp->b_data + LABELOFFSET;
442 if (label_sector)
443 dlp_byte += MBR_LABELSECTOR * a->lp->d_secsize;
444 else
445 dlp_byte += LABELSECTOR * a->lp->d_secsize;
446 dlp = (void *)dlp_byte;
447 break;
448 }
449 if (dlp->d_magic != DISKMAGIC || dlp->d_magic2 != DISKMAGIC)
450 continue;
451 if (dlp->d_npartitions > MAXPARTITIONS || dkcksum(dlp) != 0) {
452 a->msg = "disk label corrupted";
453 continue;
454 }
455 break;
456 }
457
458 switch (a->action) {
459 case READ_LABEL:
460 *a->lp = *dlp;
461 a->label_sector = label_sector;
462 return SCAN_FOUND;
463 case UPDATE_LABEL:
464 case WRITE_LABEL:
465 *dlp = *a->lp;
466 a->bp->b_flags &= ~(B_READ|B_DONE);
467 a->bp->b_flags |= B_WRITE;
468 (*a->strat)(a->bp);
469 error = biowait(a->bp);
470 if (error != 0) {
471 a->error = error;
472 a->msg = "disk label write failed";
473 return SCAN_ERROR;
474 }
475 a->written++;
476 /* Write label to all mbr partitions */
477 return SCAN_CONTINUE;
478 default:
479 return SCAN_ERROR;
480 }
481 }
482
483 /*
484 * Check new disk label for sensibility
485 * before setting it.
486 */
487 int
488 setdisklabel(struct disklabel *olp, struct disklabel *nlp, u_long openmask,
489 struct cpu_disklabel *osdep)
490 {
491 int i;
492 struct partition *opp, *npp;
493
494 /* sanity clause */
495 if (nlp->d_secpercyl == 0 || nlp->d_secsize == 0
496 || (nlp->d_secsize % DEV_BSIZE) != 0)
497 return (EINVAL);
498
499 /* special case to allow disklabel to be invalidated */
500 if (nlp->d_magic == 0xffffffff) {
501 *olp = *nlp;
502 return (0);
503 }
504
505 if (nlp->d_magic != DISKMAGIC || nlp->d_magic2 != DISKMAGIC ||
506 dkcksum(nlp) != 0)
507 return (EINVAL);
508
509 /* XXX missing check if other dos partitions will be overwritten */
510
511 while (openmask != 0) {
512 i = ffs(openmask) - 1;
513 openmask &= ~(1 << i);
514 if (i > nlp->d_npartitions)
515 return (EBUSY);
516 opp = &olp->d_partitions[i];
517 npp = &nlp->d_partitions[i];
518 /*
519 * Copy internally-set partition information
520 * if new label doesn't include it. XXX
521 */
522 if (npp->p_fstype == FS_UNUSED && opp->p_fstype != FS_UNUSED) {
523 *npp = *opp;
524 continue;
525 }
526 if (npp->p_offset != opp->p_offset || npp->p_size < opp->p_size)
527 return (EBUSY);
528 }
529 nlp->d_checksum = 0;
530 nlp->d_checksum = dkcksum(nlp);
531 *olp = *nlp;
532 return (0);
533 }
534
535
536 /*
537 * Write disk label back to device after modification.
538 */
539 int
540 writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
541 struct cpu_disklabel *osdep)
542 {
543 mbr_args_t a;
544
545 memset(&a, 0, sizeof a);
546 a.lp = lp;
547 a.strat = strat;
548
549 /* get a buffer and initialize it */
550 a.bp = geteblk(2 * (int)lp->d_secsize);
551 a.bp->b_dev = dev;
552
553 /* osdep => we expect an mbr with label in netbsd ptn */
554 a.action = osdep != NULL ? WRITE_LABEL : UPDATE_LABEL;
555
556 /* Write/update the label to every netbsd mbr partition */
557 scan_mbr(&a, write_netbsd_label);
558
559 /* Old write the label at the start of the volume on disks that
560 * don't have a valid mbr (always update an existing one) */
561 a.action = a.found_mbr ? UPDATE_LABEL : WRITE_LABEL;
562 validate_label(&a, 0);
563
564 if (a.written == 0 && a.error == 0)
565 a.error = ESRCH;
566
567 brelse(a.bp);
568 return a.error;
569 }
570
571 static int
572 write_netbsd_label(mbr_args_t *a, mbr_partition_t *dp, int slot, uint ext_base)
573 {
574 int ptn_base = ext_base + le32toh(dp->mbrp_start);
575
576 if (dp->mbrp_type != MBR_PTYPE_NETBSD)
577 return SCAN_CONTINUE;
578
579 return validate_label(a, ptn_base);
580 }
581