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