xref: /plan9/sys/src/9/pcboot/dosboot.c (revision 25210b069a6ed8c047fa67220cf1dff32812f121)
1 #include	"u.h"
2 #include	"../port/lib.h"
3 #include	"mem.h"
4 #include	"dat.h"
5 #include	"fns.h"
6 #include	"io.h"
7 #include	"ureg.h"
8 #include	"pool.h"
9 #include	"../port/error.h"
10 #include	"../port/netif.h"
11 #include	"dosfs.h"
12 
13 enum {
14 	Dosfilemax = 8,
15 	Dosextmax = 3,
16 };
17 
18 /*
19  *  predeclared
20  */
21 static void	bootdump(Dosboot*);
22 static void	setname(Dosfile*, char*);
23 
24 /*
25  *  debugging
26  */
27 #define chatty	0
28 #define chat	if(chatty)print
29 
30 /*
31  *  block io buffers
32  */
33 enum
34 {
35 	Nbio=	16,
36 };
37 typedef struct	Clustbuf	Clustbuf;
38 struct Clustbuf
39 {
40 	int	age;
41 	long	sector;
42 	uchar	*iobuf;
43 	Dos	*dos;
44 	int	size;
45 };
46 Clustbuf	bio[Nbio];
47 
48 /*
49  *  get an io block from an io buffer
50  */
51 Clustbuf*
getclust(Dos * dos,long sector)52 getclust(Dos *dos, long sector)
53 {
54 	Bootfs *fs;
55 	Clustbuf *p, *oldest;
56 	int size;
57 
58 	chat("getclust @ %ld\n", sector);
59 
60 	/*
61 	 *  if we have it, just return it
62 	 */
63 	for(p = bio; p < &bio[Nbio]; p++){
64 		if(sector == p->sector && dos == p->dos){
65 			p->age = m->ticks;
66 			chat("getclust %ld in cache\n", sector);
67 			return p;
68 		}
69 	}
70 
71 	/*
72 	 *  otherwise, reuse the oldest entry
73 	 */
74 	oldest = bio;
75 	for(p = &bio[1]; p < &bio[Nbio]; p++){
76 		if(p->age <= oldest->age)
77 			oldest = p;
78 	}
79 	p = oldest;
80 
81 	/*
82 	 *  make sure the buffer is big enough
83 	 */
84 	size = dos->clustsize*dos->sectsize;
85 	if(p->iobuf==0 || p->size < size)
86 		p->iobuf = smalloc(size);
87 	p->size = size;
88 
89 	/*
90 	 *  read in the cluster
91 	 */
92 	fs = (Bootfs*)dos;		/* assume dos is embedded at start of an Bootfs */
93 	chat("getclust addr %llud %p %s\n", ((sector+dos->start)*(vlong)dos->sectsize),
94 		fs, fs->disk);
95 	fs->devch->offset = (sector+dos->start) * (vlong)dos->sectsize;
96 	if(myreadn(fs->devch, p->iobuf, size) != size){
97 		chat("can't read block\n");
98 		return 0;
99 	}
100 	USED(fs);
101 	p->age = m->ticks;
102 	p->dos = dos;
103 	p->sector = sector;
104 	chat("getclust %ld read\n", sector);
105 	return p;
106 }
107 
108 /*
109  *  walk the fat one level ( n is a current cluster number ).
110  *  return the new cluster number or -1 if no more.
111  */
112 static long
fatwalk(Dos * dos,int n)113 fatwalk(Dos *dos, int n)
114 {
115 	ulong k, sect;
116 	Clustbuf *p;
117 	int o;
118 
119 	chat("fatwalk %d\n", n);
120 
121 	if(n < 2 || n >= dos->fatclusters)
122 		return -1;
123 
124 	switch(dos->fatbits){
125 	case 12:
126 		k = (3*n)/2; break;
127 	case 16:
128 		k = 2*n; break;
129 	case 32:
130 		k = 4*n; break;
131 	default:
132 		return -1;
133 	}
134 	if(k >= dos->fatsize*dos->sectsize)
135 		panic("getfat");
136 
137 	if (dos->sectsize == 0 || dos->clustsize == 0)
138 		panic("fatwalk: zero sector or cluster size");
139 	sect = (k/(dos->sectsize*dos->clustsize))*dos->clustsize + dos->fataddr;
140 	o = k%(dos->sectsize*dos->clustsize);
141 	p = getclust(dos, sect);
142 	k = p->iobuf[o++];
143 	if(o >= dos->sectsize*dos->clustsize){
144 		p = getclust(dos, sect+dos->clustsize);
145 		o = 0;
146 	}
147 	k |= p->iobuf[o++]<<8;
148 	if(dos->fatbits == 12){
149 		if(n&1)
150 			k >>= 4;
151 		else
152 			k &= 0xfff;
153 		if(k >= 0xff8)
154 			k = -1;
155 	}
156 	else if (dos->fatbits == 32){
157 		if(o >= dos->sectsize*dos->clustsize){
158 			p = getclust(dos, sect+dos->clustsize);
159 			o = 0;
160 		}
161 		k |= p->iobuf[o++]<<16;
162 		k |= p->iobuf[o]<<24;
163 		if (k >= 0xfffffff8)
164 			k = -1;
165 	}
166 	else
167 		k = k < 0xfff8 ? k : -1;
168 	chat("fatwalk %d -> %lud\n", n, k);
169 	return k;
170 }
171 
172 /*
173  *  map a file's logical cluster address to a physical sector address
174  */
175 static long
fileaddr(Dosfile * fp,long ltarget)176 fileaddr(Dosfile *fp, long ltarget)
177 {
178 	Dos *dos = fp->dos;
179 	long l;
180 	long p;
181 
182 	chat("fileaddr %8.8s %ld\n", fp->name, ltarget);
183 	/*
184 	 *  root directory is contiguous and easy (unless FAT32)
185 	 */
186 	if(fp->pstart == 0 && dos->rootsize != 0) {
187 		if(ltarget*dos->sectsize*dos->clustsize >= dos->rootsize*sizeof(Dosdir))
188 			return -1;
189 		l = dos->rootaddr + ltarget*dos->clustsize;
190 		chat("fileaddr %ld -> %ld\n", ltarget, l);
191 		return l;
192 	}
193 
194 	/*
195 	 *  anything else requires a walk through the fat
196 	 */
197 	if(ltarget >= fp->lcurrent && fp->pcurrent){
198 		/* start at the currrent point */
199 		l = fp->lcurrent;
200 		p = fp->pcurrent;
201 	} else {
202 		/* go back to the beginning */
203 		l = 0;
204 		p = fp->pstart;
205 	}
206 	while(l != ltarget){
207 		/* walk the fat */
208 		p = fatwalk(dos, p);
209 		if(p < 0)
210 			return -1;
211 		l++;
212 	}
213 	fp->lcurrent = l;
214 	fp->pcurrent = p;
215 
216 	/*
217 	 *  clusters start at 2 instead of 0 (why? - presotto)
218 	 */
219 	l =  dos->dataaddr + (p-2)*dos->clustsize;
220 	chat("fileaddr %ld -> %ld\n", ltarget, l);
221 	return l;
222 }
223 
224 /*
225  *  read from a dos file
226  */
227 long
dosread(Dosfile * fp,void * a,long n)228 dosread(Dosfile *fp, void *a, long n)
229 {
230 	long addr;
231 	long rv;
232 	int i;
233 	int off;
234 	Clustbuf *p;
235 	uchar *from, *to;
236 
237 	if((fp->attr & DOSDIR) == 0){
238 		if(fp->offset >= fp->length)
239 			return 0;
240 		if(fp->offset+n > fp->length)
241 			n = fp->length - fp->offset;
242 	}
243 
244 	to = a;
245 	for(rv = 0; rv < n; rv+=i){
246 		/*
247 		 *  read the cluster
248 		 */
249 		addr = fileaddr(fp, fp->offset/fp->dos->clustbytes);
250 		if(addr < 0)
251 			return -1;
252 		p = getclust(fp->dos, addr);
253 		if(p == 0)
254 			return -1;
255 
256 		/*
257 		 *  copy the bytes we need
258 		 */
259 		off = fp->offset % fp->dos->clustbytes;
260 		from = &p->iobuf[off];
261 		i = n - rv;
262 		if(i > fp->dos->clustbytes - off)
263 			i = fp->dos->clustbytes - off;
264 		memmove(to, from, i);
265 		to += i;
266 		fp->offset += i;
267 	}
268 
269 	return rv;
270 }
271 
272 /*
273  *  walk a directory returns
274  * 	-1 if something went wrong
275  *	 0 if not found
276  *	 1 if found
277  */
278 int
doswalk(File * f,char * name)279 doswalk(File *f, char *name)
280 {
281 	Dosdir d;
282 	long n;
283 	Dosfile *file;
284 
285 	chat("doswalk %s\n", name);
286 
287 	file = &f->dos;
288 
289 	if((file->attr & DOSDIR) == 0){
290 		chat("walking non-directory!\n");
291 		return -1;
292 	}
293 
294 	setname(file, name);
295 
296 	file->offset = 0;	/* start at the beginning */
297 	while((n = dosread(file, &d, sizeof(d))) == sizeof(d)){
298 		chat("comparing to %8.8s.%3.3s\n", (char*)d.name, (char*)d.ext);
299 		if(memcmp(file->name, d.name, sizeof(d.name)) != 0)
300 			continue;
301 		if(memcmp(file->ext, d.ext, sizeof(d.ext)) != 0)
302 			continue;
303 		if(d.attr & DOSVLABEL){
304 			chat("%8.8s.%3.3s is a LABEL\n", (char*)d.name, (char*)d.ext);
305 			continue;
306 		}
307 		file->attr = d.attr;
308 		file->pstart = GSHORT(d.start);
309 		if (file->dos->fatbits == 32)
310 			file->pstart |= GSHORT(d.highstart) << 16;
311 		file->length = GLONG(d.length);
312 		file->pcurrent = 0;
313 		file->lcurrent = 0;
314 		file->offset = 0;
315 		return 1;
316 	}
317 	return n >= 0 ? 0 : -1;
318 }
319 
320 void
lowercase(char * s)321 lowercase(char *s)
322 {
323 	for (; *s != '\0'; s++)
324 		if (*s >= 'A' && *s <= 'Z')
325 			*s -= 'A' - 'a';
326 }
327 
328 void
trim(char * s,int len)329 trim(char *s, int len)
330 {
331 	while(len > 0 && s[len-1] == ' ')
332 		s[--len] = '\0';
333 }
334 
335 /*
336  *  read a directory and return the file names in a malloced
337  *	array whose address is stored through nmarray.
338  * 	-1 if something went wrong
339  *	else number of dir. entries
340  */
341 int
dosdirread(File * f,char *** nmarray)342 dosdirread(File *f, char ***nmarray)
343 {
344 	int entries;
345 	long i;
346 	char buf[Dosfilemax+1+Dosextmax+1];
347 	char **nms;
348 	Dosdir d;
349 	Dosfile *file;
350 
351 	chat("dosdirread\n");
352 	file = &f->dos;
353 	if((file->attr & DOSDIR) == 0){
354 		chat("walking non-directory!\n");
355 		return -1;
356 	}
357 
358 	/* allocate the array of char*s */
359 	file->offset = 0;		/* start at the beginning */
360 	for(entries = 0; dosread(file, &d, sizeof d) == sizeof d; entries++)
361 		;
362 	nms = smalloc(sizeof(char *) * (entries + 1));
363 
364 	/* populate the array */
365 	file->offset = 0;		/* rewind */
366 	for(i = 0; i < entries && dosread(file, &d, sizeof d) == sizeof d; ){
367 		trim((char *)d.name, Dosfilemax);
368 		trim((char *)d.ext, Dosextmax);
369 		if (d.name[0] == '\0')
370 			continue;
371 		if (d.ext[0] == '\0')
372 			kstrdup(&nms[i], (char *)d.name);
373 		else {
374 			snprint(buf, sizeof buf, "%.*s.%.*s",
375 				Dosfilemax, (char *)d.name,
376 				Dosextmax, (char *)d.ext);
377 			kstrdup(&nms[i], buf);
378 		}
379 		lowercase(nms[i++]);
380 	}
381 	*nmarray = nms;
382 	return 0;
383 }
384 
385 /*
386  *  instructions that boot blocks can start with
387  */
388 #define	JMPSHORT	0xeb
389 #define JMPNEAR		0xe9
390 
391 /*
392  *  read in a segment
393  */
394 long
dosreadseg(File * f,void * va,long len)395 dosreadseg(File *f, void *va, long len)
396 {
397 	char *a;
398 	long n, sofar;
399 	Dosfile *fp;
400 
401 	fp = &f->dos;
402 	a = va;
403 	for(sofar = 0; sofar < len; sofar += n){
404 		n = 8*1024;
405 		if(len - sofar < n)
406 			n = len - sofar;
407 		n = dosread(fp, a + sofar, n);
408 		if(n <= 0)
409 			break;
410 		print(".");
411 	}
412 	return sofar;
413 }
414 
415 int
dosinit(Bootfs * fs,char * disk)416 dosinit(Bootfs *fs, char *disk)
417 {
418 	Clustbuf *p;
419 	Dosboot *b;
420 	int i;
421 	Dos *dos;
422 	Dosfile *root;
423 
424 chat("dosinit0 %p %s\n", fs, fs->disk);
425 
426 	fs->disk = disk;
427 	fs->devch = namecopen(disk, OREAD);
428 	if (fs->devch == nil) {
429 		print("dosinit: can't open %s\n", disk);
430 		return -1;
431 	}
432 
433 	dos = &fs->dos;
434 	/* defaults till we know better */
435 	dos->sectsize = 512;
436 	dos->clustsize = 1;
437 
438 	/* get first sector */
439 	p = getclust(dos, 0);
440 	if(p == 0){
441 		chat("can't read boot block\n");
442 		return -1;
443 	}
444 
445 chat("dosinit0a\n");
446 
447 	p->dos = 0;				/* don't cache this block */
448 	b = (Dosboot *)p->iobuf;
449 	if(b->magic[0] != JMPNEAR && (b->magic[0] != JMPSHORT || b->magic[2] != 0x90)){
450 		chat("no dos file system %x %x %x %x\n",
451 			b->magic[0], b->magic[1], b->magic[2], b->magic[3]);
452 		return -1;
453 	}
454 
455 	if(chatty)
456 		bootdump(b);
457 
458 	if(b->clustsize == 0) {
459 unreasonable:
460 		if(chatty){
461 			print("unreasonable FAT BPB: ");
462 			for(i=0; i<3+8+2+1; i++)
463 				print(" %.2ux", p->iobuf[i]);
464 			print("\n");
465 		}
466 		return -1;
467 	}
468 
469 chat("dosinit1\n");
470 
471 	/*
472 	 * Determine the systems' wondrous properties.
473 	 * There are heuristics here, but there's no real way
474 	 * of knowing if this is a reasonable FAT.
475 	 */
476 	dos->fatbits = 0;
477 	dos->sectsize = GSHORT(b->sectsize);
478 	if(dos->sectsize & 0xFF)
479 		goto unreasonable;
480 	dos->clustsize = b->clustsize;
481 	dos->clustbytes = dos->sectsize*dos->clustsize;
482 	dos->nresrv = GSHORT(b->nresrv);
483 	dos->nfats = b->nfats;
484 	dos->fatsize = GSHORT(b->fatsize);
485 	dos->rootsize = GSHORT(b->rootsize);
486 	dos->volsize = GSHORT(b->volsize);
487 	if(dos->volsize == 0)
488 		dos->volsize = GLONG(b->bigvolsize);
489 	dos->mediadesc = b->mediadesc;
490 	if(dos->fatsize == 0) {
491 		chat("fat32\n");
492 		dos->rootsize = 0;
493 		dos->fatsize = GLONG(b->bigfatsize);
494 		dos->fatbits = 32;
495 	}
496 	dos->fataddr = dos->nresrv;
497 	if (dos->rootsize == 0) {
498 		dos->rootaddr = 0;
499 		dos->rootclust = GLONG(b->rootdirstartclust);
500 		dos->dataaddr = dos->fataddr + dos->nfats*dos->fatsize;
501 	} else {
502 		dos->rootaddr = dos->fataddr + dos->nfats*dos->fatsize;
503 		i = dos->rootsize*sizeof(Dosdir) + dos->sectsize - 1;
504 		i = i/dos->sectsize;
505 		dos->dataaddr = dos->rootaddr + i;
506 	}
507 	dos->fatclusters = 2+(dos->volsize - dos->dataaddr)/dos->clustsize;
508 	if(dos->fatbits != 32) {
509 		if(dos->fatclusters < 4087)
510 			dos->fatbits = 12;
511 		else
512 			dos->fatbits = 16;
513 	}
514 	dos->freeptr = 2;
515 
516 	if(dos->clustbytes < 512 || dos->clustbytes > 64*1024)
517 		goto unreasonable;
518 
519 chat("dosinit2\n");
520 
521 	/*
522 	 *  set up the root
523 	 */
524 
525 	fs->root.fs = fs;
526 	root = &fs->root.dos;
527 	root->dos = dos;
528 	root->pstart = dos->rootsize == 0 ? dos->rootclust : 0;
529 	root->pcurrent = root->lcurrent = 0;
530 	root->offset = 0;
531 	root->attr = DOSDIR;
532 	root->length = dos->rootsize*sizeof(Dosdir);
533 
534 chat("dosinit3\n");
535 
536 	fs->read = dosreadseg;
537 	fs->walk = doswalk;
538 	return 0;
539 }
540 
541 static void
bootdump(Dosboot * b)542 bootdump(Dosboot *b)
543 {
544 	if(chatty == 0)
545 		return;
546 	print("magic: 0x%2.2x 0x%2.2x 0x%2.2x ",
547 		b->magic[0], b->magic[1], b->magic[2]);
548 	print("version: \"%8.8s\"\n", (char*)b->version);
549 	print("sectsize: %d ", GSHORT(b->sectsize));
550 	print("allocsize: %d ", b->clustsize);
551 	print("nresrv: %d ", GSHORT(b->nresrv));
552 	print("nfats: %d\n", b->nfats);
553 	print("rootsize: %d ", GSHORT(b->rootsize));
554 	print("volsize: %d ", GSHORT(b->volsize));
555 	print("mediadesc: 0x%2.2x\n", b->mediadesc);
556 	print("fatsize: %d ", GSHORT(b->fatsize));
557 	print("trksize: %d ", GSHORT(b->trksize));
558 	print("nheads: %d ", GSHORT(b->nheads));
559 	print("nhidden: %d ", GLONG(b->nhidden));
560 	print("bigvolsize: %d\n", GLONG(b->bigvolsize));
561 /*
562 	print("driveno: %d\n", b->driveno);
563 	print("reserved0: 0x%2.2x\n", b->reserved0);
564 	print("bootsig: 0x%2.2x\n", b->bootsig);
565 	print("volid: 0x%8.8x\n", GLONG(b->volid));
566 	print("label: \"%11.11s\"\n", b->label);
567 */
568 }
569 
570 
571 /*
572  *  set up a dos file name
573  */
574 static void
setname(Dosfile * fp,char * from)575 setname(Dosfile *fp, char *from)
576 {
577 	char *to;
578 
579 	to = fp->name;
580 	for(; *from && to-fp->name < 8; from++, to++){
581 		if(*from == '.'){
582 			from++;
583 			break;
584 		}
585 		if(*from >= 'a' && *from <= 'z')
586 			*to = *from + 'A' - 'a';
587 		else
588 			*to = *from;
589 	}
590 	while(to - fp->name < 8)
591 		*to++ = ' ';
592 
593 	/* from might be 12345678.123: don't save the '.' in ext */
594 	if(*from == '.')
595 		from++;
596 
597 	to = fp->ext;
598 	for(; *from && to-fp->ext < 3; from++, to++){
599 		if(*from >= 'a' && *from <= 'z')
600 			*to = *from + 'A' - 'a';
601 		else
602 			*to = *from;
603 	}
604 	while(to-fp->ext < 3)
605 		*to++ = ' ';
606 
607 	chat("name is %8.8s.%3.3s\n", fp->name, fp->ext);
608 }
609