Initial import from FreeBSD RELENG_4:
[dragonfly.git] / lib / libstand / dosfs.c
1 /*
2  * Copyright (c) 1996, 1998 Robert Nordier
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in
12  *    the documentation and/or other materials provided with the
13  *    distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
16  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
21  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
23  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
25  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  * $FreeBSD: src/lib/libstand/dosfs.c,v 1.4.2.1 2000/05/04 13:47:49 ps Exp $
28  */
29
30 /*
31  * Readonly filesystem for Microsoft FAT12/FAT16/FAT32 filesystems,
32  * also supports VFAT.
33  */
34
35 #include <sys/types.h>
36 #include <string.h>
37 #include <stddef.h>
38
39 #include "stand.h"
40
41 #include "dosfs.h"
42
43
44 static int      dos_open(const char *path, struct open_file *fd);
45 static int      dos_close(struct open_file *fd);
46 static int      dos_read(struct open_file *fd, void *buf, size_t size, size_t *resid);
47 static off_t    dos_seek(struct open_file *fd, off_t offset, int whence);
48 static int      dos_stat(struct open_file *fd, struct stat *sb);
49
50 struct fs_ops dosfs_fsops = {
51         "dosfs",
52         dos_open,
53         dos_close,
54         dos_read,
55         null_write,
56         dos_seek,
57         dos_stat,
58         null_readdir
59 };
60
61 #define SECSIZ  512             /* sector size */
62 #define SSHIFT    9             /* SECSIZ shift */
63 #define DEPSEC   16             /* directory entries per sector */
64 #define DSHIFT    4             /* DEPSEC shift */
65 #define LOCLUS    2             /* lowest cluster number */
66
67 /* DOS "BIOS Parameter Block" */
68 typedef struct {
69     u_char secsiz[2];           /* sector size */
70     u_char spc;                 /* sectors per cluster */
71     u_char ressec[2];           /* reserved sectors */
72     u_char fats;                /* FATs */
73     u_char dirents[2];          /* root directory entries */
74     u_char secs[2];             /* total sectors */
75     u_char media;               /* media descriptor */
76     u_char spf[2];              /* sectors per FAT */
77     u_char spt[2];              /* sectors per track */
78     u_char heads[2];            /* drive heads */
79     u_char hidsec[4];           /* hidden sectors */
80     u_char lsecs[4];            /* huge sectors */
81     u_char lspf[4];             /* huge sectors per FAT */
82     u_char xflg[2];             /* flags */
83     u_char vers[2];             /* filesystem version */
84     u_char rdcl[4];             /* root directory start cluster */
85     u_char infs[2];             /* filesystem info sector */
86     u_char bkbs[2];             /* backup boot sector */
87 } DOS_BPB;
88
89 /* Initial portion of DOS boot sector */
90 typedef struct {
91     u_char jmp[3];              /* usually 80x86 'jmp' opcode */
92     u_char oem[8];              /* OEM name and version */
93     DOS_BPB bpb;                /* BPB */
94 } DOS_BS;
95
96 /* Supply missing "." and ".." root directory entries */
97 static const char *const dotstr[2] = {".", ".."};
98 static DOS_DE dot[2] = {
99     {".       ", "   ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
100      {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}},
101     {"..      ", "   ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
102      {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}}
103 };
104
105 /* The usual conversion macros to avoid multiplication and division */
106 #define bytsec(n)      ((n) >> SSHIFT)
107 #define secbyt(s)      ((s) << SSHIFT)
108 #define entsec(e)      ((e) >> DSHIFT)
109 #define bytblk(fs, n)  ((n) >> (fs)->bshift)
110 #define blkbyt(fs, b)  ((b) << (fs)->bshift)
111 #define secblk(fs, s)  ((s) >> ((fs)->bshift - SSHIFT))
112 #define blksec(fs, b)  ((b) << ((fs)->bshift - SSHIFT))
113
114 /* Convert cluster number to offset within filesystem */
115 #define blkoff(fs, b) (secbyt((fs)->lsndta) + blkbyt(fs, (b) - LOCLUS))
116
117 /* Convert cluster number to logical sector number */
118 #define blklsn(fs, b)  ((fs)->lsndta + blksec(fs, (b) - LOCLUS))
119
120 /* Convert cluster number to offset within FAT */
121 #define fatoff(sz, c)  ((sz) == 12 ? (c) + ((c) >> 1) :  \
122                         (sz) == 16 ? (c) << 1 :          \
123                         (c) << 2)
124
125 /* Does cluster number reference a valid data cluster? */
126 #define okclus(fs, c)  ((c) >= LOCLUS && (c) <= (fs)->xclus)
127
128 /* Get start cluster from directory entry */
129 #define stclus(sz, de)  ((sz) != 32 ? cv2((de)->clus) :          \
130                          ((u_int)cv2((de)->dex.h_clus) << 16) |  \
131                          cv2((de)->clus))
132     
133 static int dosunmount(DOS_FS *);
134 static int parsebs(DOS_FS *, DOS_BS *);
135 static int namede(DOS_FS *, const char *, DOS_DE **);
136 static int lookup(DOS_FS *, u_int, const char *, DOS_DE **);
137 static void cp_xdnm(u_char *, DOS_XDE *);
138 static void cp_sfn(u_char *, DOS_DE *);
139 static off_t fsize(DOS_FS *, DOS_DE *);
140 static int fatcnt(DOS_FS *, u_int);
141 static int fatget(DOS_FS *, u_int *);
142 static int fatend(u_int, u_int);
143 static int ioread(DOS_FS *, u_int, void *, u_int);
144 static int iobuf(DOS_FS *, u_int);
145 static int ioget(struct open_file *, u_int, void *, u_int);
146
147 /*
148  * Mount DOS filesystem
149  */
150 static int
151 dos_mount(DOS_FS *fs, struct open_file *fd)
152 {
153     int err;
154
155     bzero(fs, sizeof(DOS_FS));
156     fs->fd = fd;
157     if ((err = !(fs->buf = malloc(SECSIZ)) ? errno : 0) ||
158         (err = ioget(fs->fd, 0, fs->buf, 1)) ||
159         (err = parsebs(fs, (DOS_BS *)fs->buf))) {
160         (void)dosunmount(fs);
161         return(err);
162     }
163     return 0;
164 }
165
166 /*
167  * Unmount mounted filesystem
168  */
169 static int
170 dos_unmount(DOS_FS *fs)
171 {
172     int err;
173
174     if (fs->links)
175         return(EBUSY);
176     if ((err = dosunmount(fs)))
177         return(err);
178     return 0;
179 }
180
181 /*
182  * Common code shared by dos_mount() and dos_unmount()
183  */
184 static int
185 dosunmount(DOS_FS *fs)
186 {
187     if (fs->buf)
188         free(fs->buf);
189     free(fs);
190     return(0);
191 }
192
193 /*
194  * Open DOS file
195  */
196 static int
197 dos_open(const char *path, struct open_file *fd)
198 {
199     DOS_DE *de;
200     DOS_FILE *f;
201     DOS_FS *fs;
202     u_int size, clus;
203     int err = 0;
204
205     /* Allocate mount structure, associate with open */
206     fs = malloc(sizeof(DOS_FS));
207     
208     if ((err = dos_mount(fs, fd)))
209         goto out;
210
211     if ((err = namede(fs, path, &de)))
212         goto out;
213
214     clus = stclus(fs->fatsz, de);
215     size = cv4(de->size);
216
217     if ((!(de->attr & FA_DIR) && (!clus != !size)) ||
218         ((de->attr & FA_DIR) && size) ||
219         (clus && !okclus(fs, clus))) {
220         err = EINVAL;
221         goto out;
222     }
223     f = malloc(sizeof(DOS_FILE));
224     bzero(f, sizeof(DOS_FILE));
225     f->fs = fs;
226     fs->links++;
227     f->de = *de;
228     fd->f_fsdata = (void *)f;
229
230  out:
231     return(err);
232 }
233
234 /*
235  * Read from file
236  */
237 static int
238 dos_read(struct open_file *fd, void *buf, size_t nbyte, size_t *resid)
239 {
240     off_t size;
241     u_int nb, off, clus, c, cnt, n;
242     DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
243     int err = 0;
244
245     nb = (u_int)nbyte;
246     if ((size = fsize(f->fs, &f->de)) == -1)
247         return EINVAL;
248     if (nb > (n = size - f->offset))
249         nb = n;
250     off = f->offset;
251     if ((clus = stclus(f->fs->fatsz, &f->de)))
252         off &= f->fs->bsize - 1;
253     c = f->c;
254     cnt = nb;
255     while (cnt) {
256         n = 0;
257         if (!c) {
258             if ((c = clus))
259                 n = bytblk(f->fs, f->offset);
260         } else if (!off)
261             n++;
262         while (n--) {
263             if ((err = fatget(f->fs, &c)))
264                 goto out;
265             if (!okclus(f->fs, c)) {
266                 err = EINVAL;
267                 goto out;
268             }
269         }
270         if (!clus || (n = f->fs->bsize - off) > cnt)
271             n = cnt;
272         if ((err = ioread(f->fs, (c ? blkoff(f->fs, c) :
273                                       secbyt(f->fs->lsndir)) + off,
274                           buf, n)))
275             goto out;
276         f->offset += n;
277         f->c = c;
278         off = 0;
279         buf += n;
280         cnt -= n;
281     }
282  out:
283     if (resid)
284         *resid = nbyte - nb + cnt;
285     return(err);
286 }
287
288 /*
289  * Reposition within file
290  */
291 static off_t
292 dos_seek(struct open_file *fd, off_t offset, int whence)
293 {
294     off_t off;
295     u_int size;
296     DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
297
298     size = cv4(f->de.size);
299     switch (whence) {
300     case SEEK_SET:
301         off = 0;
302         break;
303     case SEEK_CUR:
304         off = f->offset;
305         break;
306     case SEEK_END:
307         off = size;
308         break;
309     default:
310         return(-1);
311     }
312     off += offset;
313     if (off < 0 || off > size)
314         return(-1);
315     f->offset = (u_int)off;
316     f->c = 0;
317     return(off);
318 }
319
320 /*
321  * Close open file
322  */
323 static int
324 dos_close(struct open_file *fd)
325 {
326     DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
327     DOS_FS *fs = f->fs;
328
329     f->fs->links--;
330     free(f);
331     dos_unmount(fs);
332     return 0;
333 }
334
335 /*
336  * Return some stat information on a file.
337  */
338 static int
339 dos_stat(struct open_file *fd, struct stat *sb)
340 {
341     DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
342
343     /* only important stuff */
344     sb->st_mode = f->de.attr & FA_DIR ? S_IFDIR | 0555 : S_IFREG | 0444;
345     sb->st_nlink = 1;
346     sb->st_uid = 0;
347     sb->st_gid = 0;
348     if ((sb->st_size = fsize(f->fs, &f->de)) == -1)
349         return EINVAL;
350     return (0);
351 }
352
353 /*
354  * Parse DOS boot sector
355  */
356 static int
357 parsebs(DOS_FS *fs, DOS_BS *bs)
358 {
359     u_int sc;
360
361     if ((bs->jmp[0] != 0x69 &&
362          bs->jmp[0] != 0xe9 &&
363          (bs->jmp[0] != 0xeb || bs->jmp[2] != 0x90)) ||
364         bs->bpb.media < 0xf0)
365         return EINVAL;
366     if (cv2(bs->bpb.secsiz) != SECSIZ)
367         return EINVAL;
368     if (!(fs->spc = bs->bpb.spc) || fs->spc & (fs->spc - 1))
369         return EINVAL;
370     fs->bsize = secbyt(fs->spc);
371     fs->bshift = ffs(fs->bsize) - 1;
372     if ((fs->spf = cv2(bs->bpb.spf))) {
373         if (bs->bpb.fats != 2)
374             return EINVAL;
375         if (!(fs->dirents = cv2(bs->bpb.dirents)))
376             return EINVAL;
377     } else {
378         if (!(fs->spf = cv4(bs->bpb.lspf)))
379             return EINVAL;
380         if (!bs->bpb.fats || bs->bpb.fats > 16)
381             return EINVAL;
382         if ((fs->rdcl = cv4(bs->bpb.rdcl)) < LOCLUS)
383             return EINVAL;
384     }
385     if (!(fs->lsnfat = cv2(bs->bpb.ressec)))
386         return EINVAL;
387     fs->lsndir = fs->lsnfat + fs->spf * bs->bpb.fats;
388     fs->lsndta = fs->lsndir + entsec(fs->dirents);
389     if (!(sc = cv2(bs->bpb.secs)) && !(sc = cv4(bs->bpb.lsecs)))
390         return EINVAL;
391     if (fs->lsndta > sc)
392         return EINVAL;
393     if ((fs->xclus = secblk(fs, sc - fs->lsndta) + 1) < LOCLUS)
394         return EINVAL;
395     fs->fatsz = fs->dirents ? fs->xclus < 0xff6 ? 12 : 16 : 32;
396     sc = (secbyt(fs->spf) << 1) / (fs->fatsz >> 2) - 1;
397     if (fs->xclus > sc)
398         fs->xclus = sc;
399     return 0;
400 }
401
402 /*
403  * Return directory entry from path
404  */
405 static int
406 namede(DOS_FS *fs, const char *path, DOS_DE **dep)
407 {
408     char name[256];
409     DOS_DE *de;
410     char *s;
411     size_t n;
412     int err;
413
414     err = 0;
415     de = dot;
416     if (*path == '/')
417         path++;
418     while (*path) {
419         if (!(s = strchr(path, '/')))
420             s = strchr(path, 0);
421         if ((n = s - path) > 255)
422             return ENAMETOOLONG;
423         memcpy(name, path, n);
424         name[n] = 0;
425         path = s;
426         if (!(de->attr & FA_DIR))
427             return ENOTDIR;
428         if ((err = lookup(fs, stclus(fs->fatsz, de), name, &de)))
429             return err;
430         if (*path == '/')
431             path++;
432     }
433     *dep = de;
434     return 0;
435 }
436
437 /*
438  * Lookup path segment
439  */
440 static int
441 lookup(DOS_FS *fs, u_int clus, const char *name, DOS_DE **dep)
442 {
443     static DOS_DIR dir[DEPSEC];
444     u_char lfn[261];
445     u_char sfn[13];
446     u_int nsec, lsec, xdn, chk, sec, ent, x;
447     int err, ok, i;
448
449     if (!clus)
450         for (ent = 0; ent < 2; ent++)
451             if (!strcasecmp(name, dotstr[ent])) {
452                 *dep = dot + ent;
453                 return 0;
454             }
455     if (!clus && fs->fatsz == 32)
456         clus = fs->rdcl;
457     nsec = !clus ? entsec(fs->dirents) : fs->spc;
458     lsec = 0;
459     xdn = chk = 0;
460     for (;;) {
461         if (!clus && !lsec)
462             lsec = fs->lsndir;
463         else if (okclus(fs, clus))
464             lsec = blklsn(fs, clus);
465         else
466             return EINVAL;
467         for (sec = 0; sec < nsec; sec++) {
468             if ((err = ioget(fs->fd, lsec + sec, dir, 1)))
469                 return err;
470             for (ent = 0; ent < DEPSEC; ent++) {
471                 if (!*dir[ent].de.name)
472                     return ENOENT;
473                 if (*dir[ent].de.name != 0xe5) {
474                     if ((dir[ent].de.attr & FA_MASK) == FA_XDE) {
475                         x = dir[ent].xde.seq;
476                         if (x & 0x40 || (x + 1 == xdn &&
477                                          dir[ent].xde.chk == chk)) {
478                             if (x & 0x40) {
479                                 chk = dir[ent].xde.chk;
480                                 x &= ~0x40;
481                             }
482                             if (x >= 1 && x <= 20) {
483                                 cp_xdnm(lfn, &dir[ent].xde);
484                                 xdn = x;
485                                 continue;
486                             }
487                         }
488                     } else if (!(dir[ent].de.attr & FA_LABEL)) {
489                         if ((ok = xdn == 1)) {
490                             for (x = 0, i = 0; i < 11; i++)
491                                 x = ((((x & 1) << 7) | (x >> 1)) +
492                                      dir[ent].de.name[i]) & 0xff;
493                             ok = chk == x &&
494                                 !strcasecmp(name, (const char *)lfn);
495                         }
496                         if (!ok) {
497                             cp_sfn(sfn, &dir[ent].de);
498                             ok = !strcasecmp(name, (const char *)sfn);
499                         }
500                         if (ok) {
501                             *dep = &dir[ent].de;
502                             return 0;
503                         }
504                     }
505                 }
506                 xdn = 0;
507             }
508         }
509         if (!clus)
510             break;
511         if ((err = fatget(fs, &clus)))
512             return err;
513         if (fatend(fs->fatsz, clus))
514             break;
515     }
516     return ENOENT;
517 }
518
519 /*
520  * Copy name from extended directory entry
521  */
522 static void
523 cp_xdnm(u_char *lfn, DOS_XDE *xde)
524 {
525     static struct {
526         u_int off;
527         u_int dim;
528     } ix[3] = {
529         {offsetof(DOS_XDE, name1), sizeof(xde->name1) / 2},
530         {offsetof(DOS_XDE, name2), sizeof(xde->name2) / 2},
531         {offsetof(DOS_XDE, name3), sizeof(xde->name3) / 2}
532     };
533     u_char *p;
534     u_int n, x, c;
535
536     lfn += 13 * ((xde->seq & ~0x40) - 1);
537     for (n = 0; n < 3; n++)
538         for (p = (u_char *)xde + ix[n].off, x = ix[n].dim; x;
539              p += 2, x--) {
540             if ((c = cv2(p)) && (c < 32 || c > 127))
541                 c = '?';
542             if (!(*lfn++ = c))
543                 return;
544         }
545     if (xde->seq & 0x40)
546         *lfn = 0;
547 }
548
549 /*
550  * Copy short filename
551  */
552 static void
553 cp_sfn(u_char *sfn, DOS_DE *de)
554 {
555     u_char *p;
556     int j, i;
557
558     p = sfn;
559     if (*de->name != ' ') {
560         for (j = 7; de->name[j] == ' '; j--);
561         for (i = 0; i <= j; i++)
562             *p++ = de->name[i];
563         if (*de->ext != ' ') {
564             *p++ = '.';
565             for (j = 2; de->ext[j] == ' '; j--);
566             for (i = 0; i <= j; i++)
567                 *p++ = de->ext[i];
568         }
569     }
570     *p = 0;
571     if (*sfn == 5)
572         *sfn = 0xe5;
573 }
574
575 /*
576  * Return size of file in bytes
577  */
578 static off_t
579 fsize(DOS_FS *fs, DOS_DE *de)
580 {
581    u_long size;
582    u_int c;
583    int n;
584
585    if (!(size = cv4(de->size)) && de->attr & FA_DIR) {
586       if (!(c = cv2(de->clus)))
587          size = fs->dirents * sizeof(DOS_DE);
588       else {
589          if ((n = fatcnt(fs, c)) == -1)
590             return n;
591          size = blkbyt(fs, n);
592       }
593    }
594    return size;
595 }
596
597 /*
598  * Count number of clusters in chain
599  */
600 static int
601 fatcnt(DOS_FS *fs, u_int c)
602 {
603    int n;
604
605    for (n = 0; okclus(fs, c); n++)
606       if (fatget(fs, &c))
607           return -1;
608    return fatend(fs->fatsz, c) ? n : -1;
609 }
610
611 /*
612  * Get next cluster in cluster chain
613  */
614 static int
615 fatget(DOS_FS *fs, u_int *c)
616 {
617     u_char buf[4];
618     u_int x;
619     int err;
620
621     err = ioread(fs, secbyt(fs->lsnfat) + fatoff(fs->fatsz, *c), buf,
622                  fs->fatsz != 32 ? 2 : 4);
623     if (err)
624         return err;
625     x = fs->fatsz != 32 ? cv2(buf) : cv4(buf);
626     *c = fs->fatsz == 12 ? *c & 1 ? x >> 4 : x & 0xfff : x;
627     return 0;
628 }
629
630 /*
631  * Is cluster an end-of-chain marker?
632  */
633 static int
634 fatend(u_int sz, u_int c)
635 {
636     return c > (sz == 12 ? 0xff7U : sz == 16 ? 0xfff7U : 0xffffff7);
637 }
638
639 /*
640  * Offset-based I/O primitive
641  */
642 static int
643 ioread(DOS_FS *fs, u_int offset, void *buf, u_int nbyte)
644 {
645     char *s;
646     u_int off, n;
647     int err;
648
649     s = buf;
650     if ((off = offset & (SECSIZ - 1))) {
651         offset -= off;
652         if ((err = iobuf(fs, bytsec(offset))))
653             return err;
654         offset += SECSIZ;
655         if ((n = SECSIZ - off) > nbyte)
656             n = nbyte;
657         memcpy(s, fs->buf + off, n);
658         s += n;
659         nbyte -= n;
660     }
661     n = nbyte & (SECSIZ - 1);
662     if (nbyte -= n) {
663         if ((err = ioget(fs->fd, bytsec(offset), s, bytsec(nbyte))))
664             return err;
665         offset += nbyte;
666         s += nbyte;
667     }
668     if (n) {
669         if ((err = iobuf(fs, bytsec(offset))))
670             return err;
671         memcpy(s, fs->buf, n);
672     }
673     return 0;
674 }
675
676 /*
677  * Buffered sector-based I/O primitive
678  */
679 static int
680 iobuf(DOS_FS *fs, u_int lsec)
681 {
682     int err;
683
684     if (fs->bufsec != lsec) {
685         if ((err = ioget(fs->fd, lsec, fs->buf, 1)))
686             return err;
687         fs->bufsec = lsec;
688     }
689     return 0;
690 }
691
692 /*
693  * Sector-based I/O primitive
694  */
695 static int
696 ioget(struct open_file *fd, u_int lsec, void *buf, u_int nsec)
697 {
698     int err;
699     
700     if ((err = (fd->f_dev->dv_strategy)(fd->f_devdata, F_READ, lsec, 
701                                         secbyt(nsec), buf, NULL)))
702         return(err);
703     return(0);
704 }