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