1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29/*
30 * Basic file system reading code for standalone I/O system.
31 * Simulates a primitive UNIX I/O system (read(), write(), open(), etc).
32 * Does not support writes.
33 */
34
35/*
36 * WARNING:
37 * This is currently used by installgrub for creating bootable floppy.
38 * The special part is diskread_callback/fileread_callback for gathering
39 * fileblock list.
40 */
41
42#include <sys/param.h>
43#include <sys/sysmacros.h>
44#include <sys/vnode.h>
45#include <sys/fs/pc_label.h>
46#include <sys/bootvfs.h>
47#include <sys/filep.h>
48#include "pcfilep.h"
49
50#if	defined(_BOOT)
51#include "../common/util.h"
52#elif	defined(_KERNEL)
53#include <sys/sunddi.h>
54#else
55#include <stdio.h>
56#include <strings.h>
57#include <ctype.h>
58#endif
59
60#if	defined(_BOOT)
61#define	dprintf	if (bootrd_debug) printf
62#elif	defined(_KERNEL)
63#define	printf	kobj_printf
64#define	dprintf	if (bootrd_debug) kobj_printf
65
66/* PRINTLIKE */
67extern void kobj_printf(char *, ...);
68#else
69#define	dprintf if (bootrd_debug) printf
70#endif
71
72#define	FI_STARTCLUST(fp)	(*(ushort_t *)(fp)->fi_buf)
73#define	FI_LENGTH(fp)		(*(long *)((fp)->fi_buf + 4))
74
75extern int bootrd_debug;
76extern void *bkmem_alloc(size_t);
77extern void bkmem_free(void *, size_t);
78
79/*
80 * NOTE: The fileread_callback is set by the calling program
81 * during a file read. diskread_callback is set to fileread_callback
82 * only if reading a file block. It needs to be NULL while reading
83 * cluster blocks.
84 */
85extern int (*diskread_callback)(int, int);
86extern int (*fileread_callback)(int, int);
87
88/*
89 *  Local prototypes
90 */
91static int lookuppn(char *, _dir_entry_p);
92static fileid_t *find_fp(int);
93static void *readblock(int, int);
94static int fat_map(int, int);
95static int cluster_valid(long, int);
96static int fat_ctodb(int, int);
97
98static int bpcfs_mountroot(char *str);
99static int bpcfs_unmountroot(void);
100static int bpcfs_open(char *str, int flags);
101static int bpcfs_close(int fd);
102static void bpcfs_closeall(void);
103static ssize_t bpcfs_read(int fdesc, char *buf, size_t count);
104static off_t bpcfs_lseek(int fdesc, off_t addr, int whence);
105
106static fileid_t *head;
107static _fat_controller_p pcfsp;
108
109/* cache the cluster */
110static int nsec_cache;
111static int nsec_start;
112static char *cluster_cache;
113
114/*ARGSUSED*/
115static int
116bpcfs_mountroot(char *str)
117{
118	int ncluster;
119	if (pcfsp != NULL)
120		return (0);	/* already mounted */
121
122	pcfsp = bkmem_alloc(sizeof (_fat_controller_t));
123	head = (fileid_t *)bkmem_alloc(sizeof (fileid_t));
124	head->fi_back = head->fi_forw = head;
125	head->fi_filedes = 0;
126	head->fi_taken = 0;
127
128	/* read of first floppy sector */
129	head->fi_blocknum = 0;
130	head->fi_count = SECSIZ;
131	head->fi_memp = (caddr_t)pcfsp->f_sector;
132	if (diskread(head)) {
133		printf("failed to read first sector\n");
134		bkmem_free(pcfsp, sizeof (*pcfsp));
135		pcfsp = NULL;
136		return (-1);
137	}
138
139	if (pcfsp->f_bpb.bs_spc == 0) {
140		printf("invalid bios paramet block\n");
141		return (-1);
142	}
143
144	pcfsp->f_rootsec =
145	    (pcfsp->f_bpb.bs_num_fats * ltohs(pcfsp->f_bpb.bs_spf)) +
146	    ltohs(pcfsp->f_bpb.bs_resv_sectors);
147	pcfsp->f_rootlen =
148	    ltohs(pcfsp->f_bpb.bs_num_root_entries) *
149	    sizeof (_dir_entry_t) / SECSIZ;
150	pcfsp->f_adjust = 0;
151	pcfsp->f_dclust = CLUSTER_ROOTDIR;
152	pcfsp->f_filesec = pcfsp->f_rootsec + pcfsp->f_rootlen;
153	pcfsp->f_nxtfree = CLUSTER_FIRST;
154
155	/* figure out the number of clusters in this partition */
156	ncluster = (((ulong_t)ltohs(pcfsp->f_bpb.bs_siv) ?
157	    (ulong_t)ltohs(pcfsp->f_bpb.bs_siv) :
158	    (ulong_t)ltohi(pcfsp->f_bpb.bs_siv)) -
159	    pcfsp->f_filesec) / (ulong_t)pcfsp->f_bpb.bs_spc;
160	pcfsp->f_16bit = ncluster >= CLUSTER_MAX_12;
161	pcfsp->f_ncluster = ncluster;
162
163	/* cache the cluster */
164	if (pcfsp->f_16bit)
165		nsec_cache = (((ncluster << 1) + 511) >> 9);
166	else
167		nsec_cache = (ncluster + ((ncluster + 1) >> 1) + 511) >> 9;
168	cluster_cache = bkmem_alloc(nsec_cache * SECSIZ);
169	if (cluster_cache == NULL) {
170		printf("bpcfs_mountroot: out of memory\n");
171		bkmem_free(pcfsp, sizeof (*pcfsp));
172		pcfsp = NULL;
173		return (-1);
174	}
175
176	head->fi_blocknum = nsec_start =
177	    ltohs(pcfsp->f_bpb.bs_resv_sectors) + pcfsp->f_adjust;
178	head->fi_count = nsec_cache * SECSIZ;
179	head->fi_memp = cluster_cache;
180	if (diskread(head)) {
181		printf("bpcfs_mountroot: failed to read cluster\n");
182		bkmem_free(pcfsp, sizeof (*pcfsp));
183		pcfsp = NULL;
184		return (-1);
185	}
186	dprintf("read cluster sectors %d starting at %d\n",
187	    nsec_cache, nsec_start);
188	return (0);
189}
190
191static int
192bpcfs_unmountroot(void)
193{
194	if (pcfsp == NULL)
195		return (-1);
196
197	(void) bpcfs_closeall();
198
199	return (0);
200}
201
202/*
203 * Open a file.
204 */
205/*ARGSUSED*/
206int
207bpcfs_open(char *str, int flags)
208{
209	static int filedes = 1;
210
211	fileid_t *filep;
212	_dir_entry_t d;
213
214	dprintf("open %s\n", str);
215	filep = (fileid_t *)bkmem_alloc(sizeof (fileid_t));
216	filep->fi_back = head->fi_back;
217	filep->fi_forw = head;
218	head->fi_back->fi_forw = filep;
219	head->fi_back = filep;
220	filep->fi_filedes = filedes++;
221	filep->fi_taken = 1;
222	filep->fi_path = (char *)bkmem_alloc(strlen(str) + 1);
223	(void) strcpy(filep->fi_path, str);
224
225	if (lookuppn(str, &d)) {
226		(void) bpcfs_close(filep->fi_filedes);
227		return (-1);
228	}
229
230	filep->fi_offset = 0;
231	FI_STARTCLUST(filep) = d.d_cluster;
232	FI_LENGTH(filep) = d.d_size;
233	dprintf("file %s size = %ld\n", str, d.d_size);
234	return (filep->fi_filedes);
235}
236
237int
238bpcfs_close(int fd)
239{
240	fileid_t *filep;
241
242	dprintf("close %d\n", fd);
243	if (!(filep = find_fp(fd)))
244		return (-1);
245
246	if (filep->fi_taken == 0 || filep == head) {
247		printf("File descripter %d no allocated!\n", fd);
248		return (-1);
249	}
250
251	/* unlink and deallocate node */
252	filep->fi_forw->fi_back = filep->fi_back;
253	filep->fi_back->fi_forw = filep->fi_forw;
254	bkmem_free(filep->fi_path, strlen(filep->fi_path) + 1);
255	bkmem_free((char *)filep, sizeof (fileid_t));
256	dprintf("close done\n");
257	return (0);
258}
259
260static void
261bpcfs_closeall(void)
262{
263	fileid_t *filep;
264
265	while ((filep = head->fi_forw) != head)
266		if (filep->fi_taken && bpcfs_close(filep->fi_filedes))
267			printf("Filesystem may be inconsistent.\n");
268
269	bkmem_free(pcfsp, sizeof (*pcfsp));
270	bkmem_free(head, sizeof (fileid_t));
271	pcfsp = NULL;
272	head = NULL;
273}
274
275static ssize_t
276bpcfs_read(int fd, caddr_t b, size_t c)
277{
278	ulong_t sector;
279	uint_t count = 0, xfer, i;
280	char *block;
281	ulong_t off, blk;
282	int rd, spc;
283	fileid_t *fp;
284
285	dprintf("bpcfs_read: fd = %d, buf = %p, size = %d\n",
286		fd, (void *)b, c);
287	fp = find_fp(fd);
288	if (fp == NULL) {
289		printf("invalid file descriptor %d\n", fd);
290		return (-1);
291	}
292
293	spc = pcfsp->f_bpb.bs_spc;
294	off = fp->fi_offset;
295	blk = FI_STARTCLUST(fp);
296	rd = blk == CLUSTER_ROOTDIR ? 1 : 0;
297
298	spc = pcfsp->f_bpb.bs_spc;
299	off = fp->fi_offset;
300	blk = FI_STARTCLUST(fp);
301	rd = (blk == CLUSTER_ROOTDIR) ? 1 : 0;
302
303	if ((c = MIN(FI_LENGTH(fp) - off, c)) == 0)
304		return (0);
305
306	while (off >= pcfsp->f_bpb.bs_spc * SECSIZ) {
307		blk = fat_map(blk, rd);
308		off -= pcfsp->f_bpb.bs_spc * SECSIZ;
309
310		if (!cluster_valid(blk, rd)) {
311			printf("bpcfs_read: invalid cluster: %ld, %d\n",
312			    blk, rd);
313			return (-1);
314		}
315	}
316
317	while (count < c) {
318		sector = fat_ctodb(blk, rd);
319		diskread_callback = fileread_callback;
320		for (i = ((off / SECSIZ) % pcfsp->f_bpb.bs_spc); i < spc; i++) {
321			xfer = MIN(SECSIZ - (off % SECSIZ), c - count);
322			if (xfer == 0)
323				break;	/* last sector done */
324
325			block = (char *)readblock(sector + i, 1);
326			if (block == NULL) {
327				return (-1);
328			}
329			dprintf("bpcfs_read: read %d bytes\n", xfer);
330			if (diskread_callback == NULL)
331				(void) bcopy(&block[off % SECSIZ], b, xfer);
332			count += xfer;
333			off += xfer;
334			b += xfer;
335		}
336
337		diskread_callback = NULL;
338		if (count < c) {
339			blk = fat_map(blk, rd);
340			if (!cluster_valid(blk, rd)) {
341				printf("bpcfs_read: invalid cluster: %ld, %d\n",
342				    blk, rd);
343				break;
344			}
345		}
346	}
347
348	fp->fi_offset += count;
349	return (count);
350}
351
352/*
353 * This version of seek() only performs absolute seeks (whence == 0).
354 */
355static off_t
356bpcfs_lseek(int fd, off_t addr, int whence)
357{
358	fileid_t *filep;
359
360	dprintf("lseek %d, off = %lx\n", fd, addr);
361	if (!(filep = find_fp(fd)))
362		return (-1);
363
364	switch (whence) {
365	case SEEK_CUR:
366		filep->fi_offset += addr;
367		break;
368	case SEEK_SET:
369		filep->fi_offset = addr;
370		break;
371	default:
372	case SEEK_END:
373		printf("lseek(): invalid whence value %d\n", whence);
374		break;
375	}
376
377	filep->fi_blocknum = addr / DEV_BSIZE;
378	filep->fi_count = 0;
379	return (0);
380}
381
382static fileid_t *
383find_fp(int fd)
384{
385	fileid_t *filep = head;
386
387	if (fd >= 0) {
388		while ((filep = filep->fi_forw) != head)
389			if (fd == filep->fi_filedes)
390				return (filep->fi_taken ? filep : 0);
391	}
392
393	return (0);
394}
395
396static int
397cluster_valid(long c, int rd)
398{
399	return ((rd && (c == 0)) ? 1 : (c >= CLUSTER_RES_16_0 ? 0 : c));
400}
401
402static int
403fat_ctodb(int blk, int r)
404{
405	uint_t s;
406
407	s = r ? blk + pcfsp->f_rootsec + pcfsp->f_adjust :
408	    ((blk - 2) * pcfsp->f_bpb.bs_spc) +
409	    pcfsp->f_filesec + pcfsp->f_adjust;
410
411	return (s);
412}
413
414static int
415fat_map(int blk, int rootdir)
416{
417	ulong_t sectn, fat_index;
418	uchar_t *fp;
419
420	if (rootdir) {
421		return (blk > pcfsp->f_rootlen ? CLUSTER_EOF : blk + 1);
422	}
423
424	/* ---- Find out what sector this cluster is in ---- */
425	fat_index = (pcfsp->f_16bit) ? ((ulong_t)blk << 1) :
426	    ((ulong_t)blk + ((uint_t)blk >> 1));
427
428	sectn = (fat_index / SECSIZ) + ltohs(pcfsp->f_bpb.bs_resv_sectors)
429	    + pcfsp->f_adjust;
430
431	/*
432	 * Read two sectors so that if our fat_index points at the last byte
433	 * byte we'll have the data needed.  This is only a problem for fat12
434	 * entries.
435	 */
436	if (!(fp = (uchar_t *)readblock(sectn, 2))) {
437		printf("fat_map: bad cluster\n");
438		return (CLUSTER_BAD_16);
439	}
440
441	fp += (fat_index % SECSIZ);
442
443	if (pcfsp->f_16bit)
444		blk = fp[0] | (fp[1] << 8);
445	else {
446		if (blk & 1)
447			blk = ((fp[0] >> 4) & 0xf) | (fp[1] << 4);
448		else
449			blk = ((fp[1] & 0xf) << 8) | fp[0];
450
451		/*
452		 * This makes compares easier because we can just compare
453		 * against one value instead of two.
454		 */
455		if (blk >= CLUSTER_RES_12_0)
456			blk |= CLUSTER_RES_16_0;
457	}
458	return (blk);
459}
460
461static int
462namecmp(char *pn, char *dn, int cs)
463{
464	dprintf("namecmp %s, %s, len = %d\n", pn, dn, cs);
465
466	/* starting char must match */
467	while (*pn && *dn) {
468		--cs;
469		if (toupper(*pn++) != toupper(*dn++))
470			return (1);
471	}
472
473	dprintf("namecmp: cs = %d\n", cs);
474	/* remainder should be either ~# or all spaces */
475	if (cs > 0 && *dn == '~')
476		return (0);
477	while (cs > 0) {
478		if (*dn++ != ' ')
479			return (1);
480		--cs;
481	}
482	return (0);
483}
484
485static int
486dircmp(char *name, char *d_name, char *d_ext)
487{
488	int ret;
489	char *sep, *ext;
490
491	sep = (char *)strchr(name, '.');
492
493	if (sep) {
494		*sep = '\0';
495		ext = sep + 1;
496	} else
497		ext = "   ";
498
499	if (namecmp(name, d_name, NAMESIZ) || namecmp(ext, d_ext, EXTSIZ))
500		ret = 1;
501	else
502		ret = 0;
503	if (sep)
504		*sep = '.';
505	return (ret);
506}
507
508static int
509lookup(char *n, _dir_entry_p dp, ulong_t dir_blk)
510{
511	int spc = pcfsp->f_bpb.bs_spc;
512	int rd = (dir_blk == CLUSTER_ROOTDIR ? 1 : 0);
513	_dir_entry_p dxp;
514	int j, sector;
515
516	dprintf("lookup: name = %s\n", n);
517
518	while (cluster_valid(dir_blk, rd)) {
519		sector = fat_ctodb(dir_blk, rd);
520		dxp = readblock(sector, 1);	/* read one sector */
521		if (dxp == NULL)
522			return (0);
523		for (j = 0; j < DIRENTS * spc; j++, dxp++) {
524			dprintf("lookup: dir entry %s.%s;\n",
525			    dxp->d_name, dxp->d_ext);
526			if (dxp->d_name[0] == 0)
527				return (0);
528			if ((uchar_t)dxp->d_name[0] != 0xE5 &&
529			    (dxp->d_attr & (DE_LABEL|DE_HIDDEN)) == 0 &&
530			    dircmp(n, dxp->d_name, dxp->d_ext) == 0) {
531				dprintf("lookup: match found\n");
532				(void) bcopy(dxp, dp, sizeof (*dp));
533				return (1);
534			}
535		}
536		/* next cluster */
537		dir_blk = fat_map(dir_blk, rd);
538	}
539
540	return (0);
541}
542
543static int
544lookuppn(char *n, _dir_entry_p dp)
545{
546	long dir_blk;
547	char name[8 + 1 + 3 + 1];	/* <8>.<3>'\0' */
548	char *p, *ep;
549	_dir_entry_t dd;
550
551	dprintf("lookuppn: path = %s\n", n);
552	dir_blk = pcfsp->f_dclust;
553	if ((*n == '\\') || (*n == '/')) {
554		dir_blk = CLUSTER_ROOTDIR;
555		while ((*n == '\\') || (*n == '/'))
556			n++;
557		if (*n == '\0') {
558			(void) bzero(dp, sizeof (*dp));
559			dp->d_cluster = CLUSTER_ROOTDIR;
560			dp->d_attr = DE_DIRECTORY;
561			return (0);
562		}
563	}
564
565	ep = &name[0] + sizeof (name);
566	while (*n) {
567		(void) bzero(name, sizeof (name));
568		p = &name[0];
569		while (*n && (*n != '\\') && (*n != '/'))
570			if (p != ep)
571				*p++ = *n++;
572			else {
573				dprintf("return, name %s is too long\n", name);
574				return (-1);	/* name is too long */
575			}
576		while ((*n == '\\') || (*n == '/'))
577			n++;
578		if (lookup(name, &dd, dir_blk) == 0) {
579			dprintf("return, name %s not found\n", name);
580			return (-1);
581		}
582		dprintf("dd = %x:%x:%x attr = %x\n",
583		    *(int *)&dd, *(((int *)&dd) + 1),
584		    *(((int *)&dd) + 2), dd.d_attr);
585		if (*n && ((dd.d_attr & DE_DIRECTORY) == 0)) {
586			dprintf("return, not a directory\n");
587			return (-1);
588		}
589
590		dir_blk = dd.d_cluster;
591	}
592	(void) bcopy(&dd, dp, sizeof (dd));
593	return (0);
594}
595
596static void *
597readblock(int sector, int nsec)
598{
599	if (sector >= nsec_start && sector + nsec <= nsec_start + nsec_cache)
600		return (cluster_cache + (sector - nsec_start) * SECSIZ);
601
602	/* read disk sectors */
603	head->fi_blocknum = sector;
604	head->fi_count = nsec * SECSIZ;
605	head->fi_memp = head->fi_buf;
606	if (diskread(head)) {
607		printf("failed to %d sectors at %d\n", nsec, sector);
608		return (NULL);
609	}
610
611	return (head->fi_buf);
612}
613
614struct boot_fs_ops bpcfs_ops = {
615	"boot_pcfs",
616	bpcfs_mountroot,
617	bpcfs_unmountroot,
618	bpcfs_open,
619	bpcfs_close,
620	bpcfs_read,
621	bpcfs_lseek,
622	NULL
623};
624