xref: /plan9/sys/src/9/pcboot/parts.c (revision e4575fb150652789294d651c24a881396f70018d)
1 /*
2  * read disk partition tables, intended for early use on systems
3  * that don't use (the new) 9load.  borrowed from old 9load.
4  */
5 
6 #include	"u.h"
7 #include	"../port/lib.h"
8 #include	"mem.h"
9 #include	"dat.h"
10 #include	"fns.h"
11 #include	"io.h"
12 #include	"ureg.h"
13 #include	"pool.h"
14 #include	"../port/error.h"
15 #include	"../port/netif.h"
16 #include	"dosfs.h"
17 #include	"../port/sd.h"
18 #include	"iso9660.h"
19 
20 #define gettokens(l, a, an, del)	getfields(l, a, an, 1, del)
21 
22 enum {
23 	Trace	= 0,
24 	Parttrace = 0,
25 	Debugboot = 0,
26 
27 	Maxsec	= 2048,
28 	Normsec	= 512,			/* mag disks */
29 
30 	/* from devsd.c */
31 	PartLOG		= 8,
32 	NPart		= (1<<PartLOG),
33 };
34 
35 typedef struct PSDunit PSDunit;
36 struct PSDunit {
37 	SDunit;
38 	Chan	*ctlc;
39 	Chan	*data;
40 };
41 
42 static uchar *mbrbuf, *partbuf;
43 static char buf[128], buf2[128];
44 
45 static void
psdaddpart(PSDunit * unit,char * name,uvlong start,uvlong end)46 psdaddpart(PSDunit* unit, char* name, uvlong start, uvlong end)
47 {
48 	int len, nw;
49 
50 	sdaddpart(unit, name, start, end);
51 
52 	/* update devsd's in-memory partition table. */
53 	len = snprint(buf, sizeof buf, "part %s %lld %lld\n", name, start, end);
54 	nw = devtab[unit->ctlc->type]->write(unit->ctlc, buf, len,
55 		unit->ctlc->offset);
56 	if (nw != len)
57 		print("can't update devsd's partition table\n");
58 	if (Debugboot)
59 		print("part %s %lld %lld\n", name, start, end);
60 }
61 
62 static long
psdread(PSDunit * unit,SDpart * pp,void * va,long len,vlong off)63 psdread(PSDunit *unit, SDpart *pp, void* va, long len, vlong off)
64 {
65 	long l, secsize;
66 	uvlong bno, nb;
67 
68 	/*
69 	 * Check the request is within partition bounds.
70 	 */
71 	secsize = unit->secsize;
72 	if (secsize == 0)
73 		panic("psdread: %s: zero sector size", unit->name);
74 	bno = off/secsize + pp->start;
75 	nb = (off+len+secsize-1)/secsize + pp->start - bno;
76 	if(bno+nb > pp->end)
77 		nb = pp->end - bno;
78 	if(bno >= pp->end || nb == 0)
79 		return 0;
80 
81 	unit->data->offset = bno * secsize;
82 	l = myreadn(unit->data, va, len);
83 	if (l < 0)
84 		return 0;
85 	return l;
86 }
87 
88 static int
sdreadblk(PSDunit * unit,SDpart * part,void * a,vlong off,int mbr)89 sdreadblk(PSDunit *unit, SDpart *part, void *a, vlong off, int mbr)
90 {
91 	uchar *b;
92 
93 	assert(a);			/* sdreadblk */
94 	if(psdread(unit, part, a, unit->secsize, off) != unit->secsize){
95 		if(Trace)
96 			print("%s: read %lud at %lld failed\n", unit->name,
97 				unit->secsize, (vlong)part->start*unit->secsize+off);
98 		return -1;
99 	}
100 	b = a;
101 	if(mbr && (b[0x1FE] != 0x55 || b[0x1FF] != 0xAA)){
102 		if(Trace)
103 			print("%s: bad magic %.2ux %.2ux at %lld\n",
104 				unit->name, b[0x1FE], b[0x1FF],
105 				(vlong)part->start*unit->secsize+off);
106 		return -1;
107 	}
108 	return 0;
109 }
110 
111 /*
112  *  read partition table.  The partition table is just ascii strings.
113  */
114 #define MAGIC "plan9 partitions"
115 static void
oldp9part(PSDunit * unit)116 oldp9part(PSDunit *unit)
117 {
118 	SDpart *pp;
119 	char *field[3], *line[NPart+1];
120 	ulong n;
121 	uvlong start, end;
122 	int i;
123 	static SDpart fakepart;
124 
125 	/*
126 	 * We prefer partition tables on the second to last sector,
127 	 * but some old disks use the last sector instead.
128 	 */
129 
130 	pp = &fakepart;
131 	kstrdup(&pp->name, "partition");
132 	pp->start = unit->sectors - 2;
133 	pp->end = unit->sectors - 1;
134 
135 	if(Debugboot)
136 		print("oldp9part %s\n", unit->name);
137 	if(sdreadblk(unit, pp, partbuf, 0, 0) < 0)
138 		return;
139 
140 	if(strncmp((char*)partbuf, MAGIC, sizeof(MAGIC)-1) != 0) {
141 		/* not found on 2nd last sector; look on last sector */
142 		pp->start++;
143 		pp->end++;
144 		if(sdreadblk(unit, pp, partbuf, 0, 0) < 0)
145 			return;
146 		if(strncmp((char*)partbuf, MAGIC, sizeof(MAGIC)-1) != 0)
147 			return;
148 		print("%s: using old plan9 partition table on last sector\n", unit->name);
149 	}else
150 		print("%s: using old plan9 partition table on 2nd-to-last sector\n", unit->name);
151 
152 	/* we found a partition table, so add a partition partition */
153 	psdaddpart(unit, pp->name, pp->start, pp->end);
154 
155 	/*
156 	 * parse partition table
157 	 */
158 	partbuf[unit->secsize-1] = '\0';
159 	n = gettokens((char*)partbuf, line, NPart+1, "\n");
160 	if(n && strncmp(line[0], MAGIC, sizeof(MAGIC)-1) == 0)
161 		for(i = 1; i < n; i++){
162 			if(gettokens(line[i], field, 3, " ") != 3)
163 				break;
164 			start = strtoull(field[1], 0, 0);
165 			end = strtoull(field[2], 0, 0);
166 			if(start >= end || end > unit->sectors)
167 				break;
168 			psdaddpart(unit, field[0], start, end);
169 		}
170 }
171 
172 static SDpart*
sdfindpart(PSDunit * unit,char * name)173 sdfindpart(PSDunit *unit, char *name)
174 {
175 	int i;
176 
177 	if(Parttrace)
178 		print("findpart %d %s %s: ", unit->npart, unit->name, name);
179 	for(i=0; i<unit->npart; i++) {
180 		if(Parttrace)
181 			print("%s...", unit->part[i].name);
182 		if(strcmp(unit->part[i].name, name) == 0){
183 			if(Parttrace)
184 				print("\n");
185 			return &unit->part[i];
186 		}
187 	}
188 	if(Parttrace)
189 		print("not found\n");
190 	return nil;
191 }
192 
193 /*
194  * look for a plan 9 partition table on drive `unit' in the second
195  * sector (sector 1) of partition `name'.
196  * if found, add the partitions defined in the table.
197  */
198 static void
p9part(PSDunit * unit,char * name)199 p9part(PSDunit *unit, char *name)
200 {
201 	SDpart *p;
202 	char *field[4], *line[NPart+1];
203 	uvlong start, end;
204 	int i, n;
205 
206 	if(Debugboot)
207 		print("p9part %s %s\n", unit->name, name);
208 	p = sdfindpart(unit, name);
209 	if(p == nil)
210 		return;
211 
212 	if(sdreadblk(unit, p, partbuf, unit->secsize, 0) < 0)
213 		return;
214 	partbuf[unit->secsize-1] = '\0';
215 
216 	if(strncmp((char*)partbuf, "part ", 5) != 0)
217 		return;
218 
219 	n = gettokens((char*)partbuf, line, NPart+1, "\n");
220 	if(n == 0)
221 		return;
222 	for(i = 0; i < n; i++){
223 		if(strncmp(line[i], "part ", 5) != 0)
224 			break;
225 		if(gettokens(line[i], field, 4, " ") != 4)
226 			break;
227 		start = strtoull(field[2], 0, 0);
228 		end   = strtoull(field[3], 0, 0);
229 		if(start >= end || end > unit->sectors)
230 			break;
231 		psdaddpart(unit, field[1], p->start+start, p->start+end);
232 	}
233 }
234 
235 static int
isdos(int t)236 isdos(int t)
237 {
238 	return t==FAT12 || t==FAT16 || t==FATHUGE || t==FAT32 || t==FAT32X;
239 }
240 
241 static int
isextend(int t)242 isextend(int t)
243 {
244 	return t==EXTEND || t==EXTHUGE || t==LEXTEND;
245 }
246 
247 /*
248  * Fetch the first dos and all plan9 partitions out of the MBR partition table.
249  * We return -1 if we did not find a plan9 partition.
250  */
251 static int
mbrpart(PSDunit * unit)252 mbrpart(PSDunit *unit)
253 {
254 	Dospart *dp;
255 	uvlong taboffset, start, end;
256 	uvlong firstxpart, nxtxpart;
257 	int havedos, i, nplan9;
258 	char name[10];
259 
260 	taboffset = 0;
261 	dp = (Dospart*)&mbrbuf[0x1BE];
262 	{
263 		/* get the MBR (allowing for DMDDO) */
264 		if(sdreadblk(unit, &unit->part[0], mbrbuf,
265 		    (vlong)taboffset * unit->secsize, 1) < 0)
266 			return -1;
267 		for(i=0; i<4; i++)
268 			if(dp[i].type == DMDDO) {
269 				if(Trace)
270 					print("DMDDO partition found\n");
271 				taboffset = 63;
272 				if(sdreadblk(unit, &unit->part[0], mbrbuf,
273 				    (vlong)taboffset * unit->secsize, 1) < 0)
274 					return -1;
275 				i = -1;	/* start over */
276 			}
277 	}
278 
279 	/*
280 	 * Read the partitions, first from the MBR and then
281 	 * from successive extended partition tables.
282 	 */
283 	nplan9 = 0;
284 	havedos = 0;
285 	firstxpart = 0;
286 	for(;;) {
287 		if(sdreadblk(unit, &unit->part[0], mbrbuf,
288 		    (vlong)taboffset * unit->secsize, 1) < 0)
289 			return -1;
290 		if(Trace) {
291 			if(firstxpart)
292 				print("%s ext %llud ", unit->name, taboffset);
293 			else
294 				print("%s mbr ", unit->name);
295 		}
296 		nxtxpart = 0;
297 		for(i=0; i<4; i++) {
298 			if(Trace)
299 				print("dp %d...", dp[i].type);
300 			start = taboffset+GLONG(dp[i].start);
301 			end = start+GLONG(dp[i].len);
302 
303 			if(dp[i].type == PLAN9) {
304 				if(nplan9 == 0)
305 					strncpy(name, "plan9", sizeof name);
306 				else
307 					snprint(name, sizeof name, "plan9.%d",
308 						nplan9);
309 				psdaddpart(unit, name, start, end);
310 				p9part(unit, name);
311 				nplan9++;
312 			}
313 
314 			/*
315 			 * We used to take the active partition (and then the first
316 			 * when none are active).  We have to take the first here,
317 			 * so that the partition we call ``dos'' agrees with the
318 			 * partition disk/fdisk calls ``dos''.
319 			 */
320 			if(havedos==0 && isdos(dp[i].type)){
321 				havedos = 1;
322 				psdaddpart(unit, "dos", start, end);
323 			}
324 
325 			/* nxtxpart is relative to firstxpart (or 0), not taboffset */
326 			if(isextend(dp[i].type)){
327 				nxtxpart = start-taboffset+firstxpart;
328 				if(Trace)
329 					print("link %llud...", nxtxpart);
330 			}
331 		}
332 		if(Trace)
333 			print("\n");
334 
335 		if(!nxtxpart)
336 			break;
337 		if(!firstxpart)
338 			firstxpart = nxtxpart;
339 		taboffset = nxtxpart;
340 	}
341 	return nplan9 ? 0 : -1;
342 }
343 
344 /*
345  * To facilitate booting from CDs, we create a partition for
346  * the FAT filesystem image embedded in a bootable CD.
347  */
348 static int
part9660(PSDunit * unit)349 part9660(PSDunit *unit)
350 {
351 	ulong a, n, i, j;
352 	uchar drecsz;
353 	uchar *p;
354 	uchar buf[Maxsec];
355 	Drec *rootdrec, *drec;
356 	Voldesc *v;
357 	static char stdid[] = "CD001\x01";
358 
359 	if(unit->secsize == 0)
360 		unit->secsize = Cdsec;
361 	if(unit->secsize != Cdsec)
362 		return -1;
363 
364 	if(psdread(unit, &unit->part[0], buf, Cdsec, VOLDESC*Cdsec) < 0)
365 		return -1;
366 	if(buf[0] != PrimaryIso ||
367 	    memcmp((char*)buf+1, stdid, sizeof stdid - 1) != 0)
368 		return -1;
369 
370 	v = (Voldesc *)buf;
371 	rootdrec = (Drec *)v->z.desc.rootdir;
372 	assert(rootdrec);
373 	p = rootdrec->addr;
374 	a = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
375 	p = rootdrec->size;
376 	n = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
377 //	print("part9660: read %uld %uld\n", n, a);	/* debugging */
378 
379 	if(n < Cdsec){
380 		print("warning: bad boot file size %ld in iso directory", n);
381 		n = Cdsec;
382 	}
383 
384 	drec = nil;
385 	for(j = 0; j*Cdsec < n; j++){
386 		if(psdread(unit, &unit->part[0], buf, Cdsec, (a + j)*Cdsec) < 0)
387 			return -1;
388 		for(i = 0; i + j*Cdsec <= n && i < Cdsec; i += drecsz){
389 			drec = (Drec *)&buf[i];
390 			drecsz = drec->reclen;
391 			if(drecsz == 0 || drecsz + i > Cdsec)
392 				break;
393 			if(cistrncmp("bootdisk.img", (char *)drec->name, 12) == 0)
394 				goto Found;
395 		}
396 	}
397 Found:
398 	if(j*Cdsec >= n || drec == nil)
399 		return -1;
400 
401 	p = drec->addr;
402 	a = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
403 	p = drec->size;
404 	n = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
405 
406 	print("found partition %s!9fat; %lud+%lud\n", unit->name, a, n);
407 	n /= Cdsec;
408 	psdaddpart(unit, "9fat", a, a+n);
409 	return 0;
410 }
411 
412 enum {
413 	NEW = 1<<0,
414 	OLD = 1<<1
415 };
416 
417 /*
418  * read unit->data to look for partition tables.
419  * if found, stash partitions in environment and write them to ctl too.
420  */
421 static void
partition(PSDunit * unit)422 partition(PSDunit *unit)
423 {
424 	int type;
425 	char *p;
426 
427 	if(unit->part == 0)
428 		return;
429 
430 	if(part9660(unit) == 0)
431 		return;
432 
433 	p = getconf("partition");
434 	if(p != nil && strncmp(p, "new", 3) == 0)
435 		type = NEW;
436 	else if(p != nil && strncmp(p, "old", 3) == 0)
437 		type = OLD;
438 	else
439 		type = NEW|OLD;
440 
441 	if(mbrbuf == nil) {
442 		mbrbuf = malloc(Maxsec);
443 		partbuf = malloc(Maxsec);
444 		if(mbrbuf==nil || partbuf==nil) {
445 			free(mbrbuf);
446 			free(partbuf);
447 			partbuf = mbrbuf = nil;
448 			return;
449 		}
450 	}
451 
452 	/*
453 	 * there might be no mbr (e.g. on a very large device), so look for
454 	 * a bare plan 9 partition table if mbrpart fails.
455 	 */
456 	if((type & NEW) && mbrpart(unit) >= 0){
457 		/* nothing to do */
458 	}
459 	else if (type & NEW)
460 		p9part(unit, "data");
461 	else if(type & OLD)
462 		oldp9part(unit);
463 }
464 
465 static void
rdgeom(PSDunit * unit)466 rdgeom(PSDunit *unit)
467 {
468 	int n, f, lines;
469 	char *buf, *p;
470 	char *line[64], *fld[5];
471 	char ctl[64];
472 	static char geom[] = "geometry";
473 
474 	buf = smalloc(Maxfile + 1);
475 	strncpy(ctl, unit->name, sizeof ctl);
476 	p = strrchr(ctl, '/');
477 	if (p)
478 		strcpy(p, "/ctl");		/* was "/data" */
479 	n = readfile(ctl, buf, Maxfile);
480 	if (n < 0) {
481 		print("rdgeom: can't read %s\n", ctl);
482 		free(buf);
483 		return;
484 	}
485 	buf[n] = 0;
486 
487 	lines = getfields(buf, line, nelem(line), 0, "\r\n");
488 	for (f = 0; f < lines; f++)
489 		if (tokenize(line[f], fld, nelem(fld)) >= 3 &&
490 		    strcmp(fld[0], geom) == 0)
491 			break;
492 	if(f < lines){
493 		unit->sectors = strtoull(fld[1], nil, 0);
494 		unit->secsize = strtoull(fld[2], nil, 0);
495 	}
496 	if (f >= lines || unit->sectors == 0){
497 		/* no geometry line, so fake it */
498 		unit->secsize = Cdsec;
499 		unit->sectors = ~0ull / unit->secsize;
500 	}
501 	if(unit->secsize == 0)
502 		print("rdgeom: %s: zero sector size read from ctl file\n",
503 			unit->name);
504 	free(buf);
505 	unit->ctlc->offset = 0;
506 }
507 
508 static void
setpartitions(char * name,Chan * ctl,Chan * data)509 setpartitions(char *name, Chan *ctl, Chan *data)
510 {
511 	PSDunit sdunit;
512 	PSDunit *unit;
513 	SDpart *part0;
514 
515 	unit = &sdunit;
516 	memset(unit, 0, sizeof *unit);
517 	unit->ctlc = ctl;
518 	unit->data = data;
519 
520 	unit->secsize = Normsec;	/* default: won't work for CDs */
521 	unit->sectors = ~0ull / unit->secsize;
522 	kstrdup(&unit->name, name);
523 	rdgeom(unit);
524 	unit->part = mallocz(sizeof(SDpart) * SDnpart, 1);
525 	unit->npart = SDnpart;
526 
527 	part0 = &unit->part[0];
528 	part0->end = unit->sectors - 1;
529 	kstrdup(&part0->name, "data");
530 	part0->valid = 1;
531 
532 	mbrbuf = malloc(Maxsec);
533 	partbuf = malloc(Maxsec);
534 	partition(unit);
535 	free(unit->part);
536 	unit->part = nil;
537 }
538 
539 /*
540  * read disk partition tables so that readnvram via factotum
541  * can see them.
542  */
543 int
readparts(char * disk)544 readparts(char *disk)
545 {
546 	Chan *ctl, *data;
547 
548 	snprint(buf, sizeof buf, "%s/ctl", disk);
549 	ctl  = namecopen(buf, ORDWR);
550 	snprint(buf2, sizeof buf2, "%s/data", disk);
551 	data = namecopen(buf2, OREAD);
552 	if (ctl != nil && data != nil)
553 		setpartitions(buf2, ctl, data);
554 	cclose(ctl);
555 	cclose(data);
556 	return 0;
557 }
558 
559 /*
560  * Leave partitions around for devsd in next kernel to pick up.
561  * (Needed by boot process; more extensive
562  * partitioning is done by termrc or cpurc).
563  */
564 void
sdaddconf(SDunit * unit)565 sdaddconf(SDunit *unit)
566 {
567 	int i;
568 	SDpart *pp;
569 
570 	/*
571 	 * If there were no partitions (just data and partition), don't bother.
572 	 */
573 	if(unit->npart <= 1 || (unit->npart == 2 &&
574 	    strcmp(unit->part[1].name, "partition") == 0))
575 		return;
576 
577 	addconf("%spart=", unit->name);
578 	/* skip 0, which is "data" */
579 	for(i = 1, pp = &unit->part[i]; i < unit->npart; i++, pp++)
580 		if (pp->valid)
581 			addconf("%s%s %lld %lld", i==1 ? "" : "/", pp->name,
582 				pp->start, pp->end);
583 	addconf("\n");
584 }
585