2 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
3 * Copyright (c) 1995 Martin Husemann
4 * Some structure declaration borrowed from Paul Popelka
5 * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by Martin Husemann
18 * and Wolfgang Solfrank.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 * $NetBSD: dir.c,v 1.14 1998/08/25 19:18:15 ross Exp $
35 * $FreeBSD: src/sbin/fsck_msdosfs/dir.c,v 1.1.2.1 2001/08/01 05:47:55 obrien Exp $
36 * $DragonFly: src/sbin/fsck_msdosfs/dir.c,v 1.2 2003/06/17 04:27:32 dillon Exp $
40 #include <sys/cdefs.h>
50 #include <sys/param.h>
55 #define SLOT_EMPTY 0x00 /* slot has never been used */
56 #define SLOT_E5 0x05 /* the real value is 0xe5 */
57 #define SLOT_DELETED 0xe5 /* file in this slot deleted */
59 #define ATTR_NORMAL 0x00 /* normal file */
60 #define ATTR_READONLY 0x01 /* file is readonly */
61 #define ATTR_HIDDEN 0x02 /* file is hidden */
62 #define ATTR_SYSTEM 0x04 /* file is a system file */
63 #define ATTR_VOLUME 0x08 /* entry is a volume label */
64 #define ATTR_DIRECTORY 0x10 /* entry is a directory name */
65 #define ATTR_ARCHIVE 0x20 /* file is new or modified */
67 #define ATTR_WIN95 0x0f /* long name record */
70 * This is the format of the contents of the deTime field in the direntry
72 * We don't use bitfields because we don't know how compilers for
73 * arbitrary machines will lay them out.
75 #define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */
76 #define DT_2SECONDS_SHIFT 0
77 #define DT_MINUTES_MASK 0x7E0 /* minutes */
78 #define DT_MINUTES_SHIFT 5
79 #define DT_HOURS_MASK 0xF800 /* hours */
80 #define DT_HOURS_SHIFT 11
83 * This is the format of the contents of the deDate field in the direntry
86 #define DD_DAY_MASK 0x1F /* day of month */
87 #define DD_DAY_SHIFT 0
88 #define DD_MONTH_MASK 0x1E0 /* month */
89 #define DD_MONTH_SHIFT 5
90 #define DD_YEAR_MASK 0xFE00 /* year - 1980 */
91 #define DD_YEAR_SHIFT 9
95 static struct dosDirEntry *newDosDirEntry __P((void));
96 static void freeDosDirEntry __P((struct dosDirEntry *));
97 static struct dirTodoNode *newDirTodo __P((void));
98 static void freeDirTodo __P((struct dirTodoNode *));
99 static char *fullpath __P((struct dosDirEntry *));
100 static u_char calcShortSum __P((u_char *));
101 static int delete __P((int, struct bootblock *, struct fatEntry *, cl_t, int,
103 static int removede __P((int, struct bootblock *, struct fatEntry *, u_char *,
104 u_char *, cl_t, cl_t, cl_t, char *, int));
105 static int checksize __P((struct bootblock *, struct fatEntry *, u_char *,
106 struct dosDirEntry *));
107 static int readDosDirSection __P((int, struct bootblock *, struct fatEntry *,
108 struct dosDirEntry *));
111 * Manage free dosDirEntry structures.
113 static struct dosDirEntry *freede;
115 static struct dosDirEntry *
118 struct dosDirEntry *de;
120 if (!(de = freede)) {
121 if (!(de = (struct dosDirEntry *)malloc(sizeof *de)))
130 struct dosDirEntry *de;
137 * The same for dirTodoNode structures.
139 static struct dirTodoNode *freedt;
141 static struct dirTodoNode *
144 struct dirTodoNode *dt;
146 if (!(dt = freedt)) {
147 if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt)))
156 struct dirTodoNode *dt;
163 * The stack of unread directories
165 struct dirTodoNode *pendingDirectories = NULL;
168 * Return the full pathname for a directory entry.
172 struct dosDirEntry *dir;
174 static char namebuf[MAXPATHLEN + 1];
178 cp = namebuf + sizeof namebuf - 1;
181 np = dir->lname[0] ? dir->lname : dir->name;
183 if ((cp -= nl) <= namebuf + 1)
187 } while ((dir = dir->parent) != NULL);
196 * Calculate a checksum over an 8.3 alias name
205 for (i = 0; i < 11; i++) {
206 sum = (sum << 7)|(sum >> 1); /* rotate right */
214 * Global variables temporarily used during a directory scan
216 static char longName[DOSLONGNAMELEN] = "";
217 static u_char *buffer = NULL;
218 static u_char *delbuf = NULL;
220 struct dosDirEntry *rootDir;
221 static struct dosDirEntry *lostDir;
224 * Init internal state for a new directory scan.
227 resetDosDirSection(boot, fat)
228 struct bootblock *boot;
229 struct fatEntry *fat;
235 b1 = boot->RootDirEnts * 32;
236 b2 = boot->SecPerClust * boot->BytesPerSec;
238 if (!(buffer = malloc(b1 > b2 ? b1 : b2))
239 || !(delbuf = malloc(b2))
240 || !(rootDir = newDosDirEntry())) {
241 perror("No space for directory");
244 memset(rootDir, 0, sizeof *rootDir);
245 if (boot->flags & FAT32) {
246 if (boot->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) {
247 pfatal("Root directory starts with cluster out of range(%u)",
251 cl = fat[boot->RootCl].next;
253 || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
254 || fat[boot->RootCl].head != boot->RootCl) {
255 if (cl == CLUST_FREE)
256 pwarn("Root directory starts with free cluster\n");
257 else if (cl >= CLUST_RSRVD)
258 pwarn("Root directory starts with cluster marked %s\n",
261 pfatal("Root directory doesn't start a cluster chain");
265 fat[boot->RootCl].next = CLUST_FREE;
271 fat[boot->RootCl].flags |= FAT_USED;
272 rootDir->head = boot->RootCl;
279 * Cleanup after a directory scan
282 finishDosDirSection()
284 struct dirTodoNode *p, *np;
285 struct dosDirEntry *d, *nd;
287 for (p = pendingDirectories; p; p = np) {
291 pendingDirectories = 0;
292 for (d = rootDir; d; d = nd) {
293 if ((nd = d->child) != NULL) {
301 rootDir = lostDir = NULL;
309 * Delete directory entries between startcl, startoff and endcl, endoff.
312 delete(f, boot, fat, startcl, startoff, endcl, endoff, notlast)
314 struct bootblock *boot;
315 struct fatEntry *fat;
324 int clsz = boot->SecPerClust * boot->BytesPerSec;
326 s = delbuf + startoff;
328 while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
329 if (startcl == endcl) {
334 off = startcl * boot->SecPerClust + boot->ClusterOffset;
335 off *= boot->BytesPerSec;
336 if (lseek(f, off, SEEK_SET) != off
337 || read(f, delbuf, clsz) != clsz) {
338 perror("Unable to read directory");
345 if (lseek(f, off, SEEK_SET) != off
346 || write(f, delbuf, clsz) != clsz) {
347 perror("Unable to write directory");
350 if (startcl == endcl)
352 startcl = fat[startcl].next;
359 removede(f, boot, fat, start, end, startcl, endcl, curcl, path, type)
361 struct bootblock *boot;
362 struct fatEntry *fat;
373 pwarn("Invalid long filename entry for %s\n", path);
376 pwarn("Invalid long filename entry at end of directory %s\n", path);
379 pwarn("Invalid long filename entry for volume label\n");
382 if (ask(0, "Remove")) {
383 if (startcl != curcl) {
384 if (delete(f, boot, fat,
385 startcl, start - buffer,
387 endcl == curcl) == FSFATAL)
392 for (; start < end; start += 32)
393 *start = SLOT_DELETED;
400 * Check an in-memory file entry
403 checksize(boot, fat, p, dir)
404 struct bootblock *boot;
405 struct fatEntry *fat;
407 struct dosDirEntry *dir;
410 * Check size on ordinary files
412 int32_t physicalSize;
414 if (dir->head == CLUST_FREE)
417 if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
419 physicalSize = fat[dir->head].length * boot->ClusterSize;
421 if (physicalSize < dir->size) {
422 pwarn("size of %s is %u, should at most be %u\n",
423 fullpath(dir), dir->size, physicalSize);
424 if (ask(1, "Truncate")) {
425 dir->size = physicalSize;
426 p[28] = (u_char)physicalSize;
427 p[29] = (u_char)(physicalSize >> 8);
428 p[30] = (u_char)(physicalSize >> 16);
429 p[31] = (u_char)(physicalSize >> 24);
433 } else if (physicalSize - dir->size >= boot->ClusterSize) {
434 pwarn("%s has too many clusters allocated\n",
436 if (ask(1, "Drop superfluous clusters")) {
440 for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;)
442 clearchain(boot, fat, fat[cl].next);
443 fat[cl].next = CLUST_EOF;
452 * Read a directory and
453 * - resolve long name records
454 * - enter file and directory records into the parent's list
455 * - push directories onto the todo-stack
458 readDosDirSection(f, boot, fat, dir)
460 struct bootblock *boot;
461 struct fatEntry *fat;
462 struct dosDirEntry *dir;
464 struct dosDirEntry dirent, *d;
465 u_char *p, *vallfn, *invlfn, *empty;
468 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
473 #define THISMOD 0x8000 /* Only used within this routine */
476 if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
478 * Already handled somewhere else.
483 vallfn = invlfn = empty = NULL;
485 if (!(boot->flags & FAT32) && !dir->parent) {
486 last = boot->RootDirEnts * 32;
487 off = boot->ResSectors + boot->FATs * boot->FATsecs;
489 last = boot->SecPerClust * boot->BytesPerSec;
490 off = cl * boot->SecPerClust + boot->ClusterOffset;
493 off *= boot->BytesPerSec;
494 if (lseek(f, off, SEEK_SET) != off
495 || read(f, buffer, last) != last) {
496 perror("Unable to read directory");
501 * Check `.' and `..' entries here? XXX
503 for (p = buffer, i = 0; i < last; i++, p += 32) {
504 if (dir->fsckflags & DIREMPWARN) {
509 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
510 if (*p == SLOT_EMPTY) {
511 dir->fsckflags |= DIREMPTY;
518 if (dir->fsckflags & DIREMPTY) {
519 if (!(dir->fsckflags & DIREMPWARN)) {
520 pwarn("%s has entries after end of directory\n",
522 if (ask(1, "Extend")) {
525 dir->fsckflags &= ~DIREMPTY;
526 if (delete(f, boot, fat,
527 empcl, empty - buffer,
528 cl, p - buffer, 1) == FSFATAL)
530 q = empcl == cl ? empty : buffer;
531 for (; q < p; q += 32)
533 mod |= THISMOD|FSDIRMOD;
534 } else if (ask(0, "Truncate"))
535 dir->fsckflags |= DIREMPWARN;
537 if (dir->fsckflags & DIREMPWARN) {
539 mod |= THISMOD|FSDIRMOD;
541 } else if (dir->fsckflags & DIREMPTY)
546 if (p[11] == ATTR_WIN95) {
548 if (shortSum != -1) {
554 memset(longName, 0, sizeof longName);
558 } else if (shortSum != p[13]
559 || lidx != (*p & LRNOMASK)) {
570 lidx = *p & LRNOMASK;
571 t = longName + --lidx * 13;
572 for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) {
573 if (!p[k] && !p[k + 1])
577 * Warn about those unusable chars in msdosfs here? XXX
583 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
584 if (!p[k] && !p[k + 1])
591 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
592 if (!p[k] && !p[k + 1])
598 if (t >= longName + sizeof(longName)) {
599 pwarn("long filename too long\n");
606 if (p[26] | (p[27] << 8)) {
607 pwarn("long filename record cluster start != 0\n");
614 continue; /* long records don't carry further
619 * This is a standard msdosfs directory entry.
621 memset(&dirent, 0, sizeof dirent);
624 * it's a short name record, but we need to know
625 * more, so get the flags first.
627 dirent.flags = p[11];
630 * Translate from 850 to ISO here XXX
632 for (j = 0; j < 8; j++)
633 dirent.name[j] = p[j];
634 dirent.name[8] = '\0';
635 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
636 dirent.name[k] = '\0';
637 if (dirent.name[k] != '\0')
639 if (dirent.name[0] == SLOT_E5)
640 dirent.name[0] = 0xe5;
642 if (dirent.flags & ATTR_VOLUME) {
643 if (vallfn || invlfn) {
644 mod |= removede(f, boot, fat,
645 invlfn ? invlfn : vallfn, p,
646 invlfn ? invcl : valcl, -1, 0,
655 dirent.name[k++] = '.';
656 for (j = 0; j < 3; j++)
657 dirent.name[k++] = p[j+8];
658 dirent.name[k] = '\0';
659 for (k--; k >= 0 && dirent.name[k] == ' '; k--)
660 dirent.name[k] = '\0';
662 if (vallfn && shortSum != calcShortSum(p)) {
669 dirent.head = p[26] | (p[27] << 8);
670 if (boot->ClustMask == CLUST32_MASK)
671 dirent.head |= (p[20] << 16) | (p[21] << 24);
672 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
674 strcpy(dirent.lname, longName);
680 dirent.next = dir->child;
683 mod |= k = removede(f, boot, fat,
684 invlfn, vallfn ? vallfn : p,
685 invcl, vallfn ? valcl : cl, cl,
686 fullpath(&dirent), 0);
690 ? (valcl == cl && vallfn != buffer)
696 vallfn = NULL; /* not used any longer */
699 if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
700 if (dirent.head != 0) {
701 pwarn("%s has clusters, but size 0\n",
703 if (ask(1, "Drop allocated clusters")) {
705 if (boot->ClustMask == CLUST32_MASK)
707 clearchain(boot, fat, dirent.head);
709 mod |= THISMOD|FSDIRMOD|FSFATMOD;
713 } else if (dirent.head == 0
714 && !strcmp(dirent.name, "..")
715 && dir->parent /* XXX */
716 && !dir->parent->parent) {
718 * Do nothing, the parent is the root
720 } else if (dirent.head < CLUST_FIRST
721 || dirent.head >= boot->NumClusters
722 || fat[dirent.head].next == CLUST_FREE
723 || (fat[dirent.head].next >= CLUST_RSRVD
724 && fat[dirent.head].next < CLUST_EOFS)
725 || fat[dirent.head].head != dirent.head) {
726 if (dirent.head == 0)
727 pwarn("%s has no clusters\n",
729 else if (dirent.head < CLUST_FIRST
730 || dirent.head >= boot->NumClusters)
731 pwarn("%s starts with cluster out of range(%u)\n",
734 else if (fat[dirent.head].next == CLUST_FREE)
735 pwarn("%s starts with free cluster\n",
737 else if (fat[dirent.head].next >= CLUST_RSRVD)
738 pwarn("%s starts with cluster marked %s\n",
740 rsrvdcltype(fat[dirent.head].next));
742 pwarn("%s doesn't start a cluster chain\n",
744 if (dirent.flags & ATTR_DIRECTORY) {
745 if (ask(0, "Remove")) {
747 mod |= THISMOD|FSDIRMOD;
752 if (ask(1, "Truncate")) {
753 p[28] = p[29] = p[30] = p[31] = 0;
755 if (boot->ClustMask == CLUST32_MASK)
758 mod |= THISMOD|FSDIRMOD;
764 if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
765 fat[dirent.head].flags |= FAT_USED;
767 if (dirent.flags & ATTR_DIRECTORY) {
769 * gather more info for directories
771 struct dirTodoNode *n;
774 pwarn("Directory %s has size != 0\n",
776 if (ask(1, "Correct")) {
777 p[28] = p[29] = p[30] = p[31] = 0;
779 mod |= THISMOD|FSDIRMOD;
784 * handle `.' and `..' specially
786 if (strcmp(dirent.name, ".") == 0) {
787 if (dirent.head != dir->head) {
788 pwarn("`.' entry in %s has incorrect start cluster\n",
790 if (ask(1, "Correct")) {
791 dirent.head = dir->head;
792 p[26] = (u_char)dirent.head;
793 p[27] = (u_char)(dirent.head >> 8);
794 if (boot->ClustMask == CLUST32_MASK) {
795 p[20] = (u_char)(dirent.head >> 16);
796 p[21] = (u_char)(dirent.head >> 24);
798 mod |= THISMOD|FSDIRMOD;
804 if (strcmp(dirent.name, "..") == 0) {
805 if (dir->parent) { /* XXX */
806 if (!dir->parent->parent) {
808 pwarn("`..' entry in %s has non-zero start cluster\n",
810 if (ask(1, "Correct")) {
813 if (boot->ClustMask == CLUST32_MASK)
815 mod |= THISMOD|FSDIRMOD;
819 } else if (dirent.head != dir->parent->head) {
820 pwarn("`..' entry in %s has incorrect start cluster\n",
822 if (ask(1, "Correct")) {
823 dirent.head = dir->parent->head;
824 p[26] = (u_char)dirent.head;
825 p[27] = (u_char)(dirent.head >> 8);
826 if (boot->ClustMask == CLUST32_MASK) {
827 p[20] = (u_char)(dirent.head >> 16);
828 p[21] = (u_char)(dirent.head >> 24);
830 mod |= THISMOD|FSDIRMOD;
838 /* create directory tree node */
839 if (!(d = newDosDirEntry())) {
840 perror("No space for directory");
843 memcpy(d, &dirent, sizeof(struct dosDirEntry));
844 /* link it into the tree */
847 /* Enter this directory into the todo list */
848 if (!(n = newDirTodo())) {
849 perror("No space for todo list");
852 n->next = pendingDirectories;
854 pendingDirectories = n;
856 mod |= k = checksize(boot, fat, p, &dirent);
864 if (lseek(f, off, SEEK_SET) != off
865 || write(f, buffer, last) != last) {
866 perror("Unable to write directory");
871 } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
872 if (invlfn || vallfn)
873 mod |= removede(f, boot, fat,
874 invlfn ? invlfn : vallfn, p,
875 invlfn ? invcl : valcl, -1, 0,
877 return mod & ~THISMOD;
881 handleDirTree(dosfs, boot, fat)
883 struct bootblock *boot;
884 struct fatEntry *fat;
888 mod = readDosDirSection(dosfs, boot, fat, rootDir);
893 * process the directory todo list
895 while (pendingDirectories) {
896 struct dosDirEntry *dir = pendingDirectories->dir;
897 struct dirTodoNode *n = pendingDirectories->next;
900 * remove TODO entry now, the list might change during
903 freeDirTodo(pendingDirectories);
904 pendingDirectories = n;
907 * handle subdirectory
909 mod |= readDosDirSection(dosfs, boot, fat, dir);
918 * Try to reconnect a FAT chain into dir
920 static u_char *lfbuf;
925 reconnect(dosfs, boot, fat, head)
927 struct bootblock *boot;
928 struct fatEntry *fat;
931 struct dosDirEntry d;
934 if (!ask(1, "Reconnect"))
938 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
939 if (!strcmp(lostDir->name, LOSTDIR))
942 if (!lostDir) { /* Create LOSTDIR? XXX */
943 pwarn("No %s directory\n", LOSTDIR);
948 lfbuf = malloc(boot->ClusterSize);
950 perror("No space for buffer");
958 for (; p < lfbuf + boot->ClusterSize; p += 32)
960 || *p == SLOT_DELETED)
962 if (p && p < lfbuf + boot->ClusterSize)
964 lfcl = p ? fat[lfcl].next : lostDir->head;
965 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
966 /* Extend LOSTDIR? XXX */
967 pwarn("No space in %s\n", LOSTDIR);
970 lfoff = lfcl * boot->ClusterSize
971 + boot->ClusterOffset * boot->BytesPerSec;
972 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
973 || read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
974 perror("could not read LOST.DIR");
981 /* Ensure uniqueness of entry here! XXX */
982 memset(&d, 0, sizeof d);
983 (void)snprintf(d.name, sizeof(d.name), "%u", head);
986 d.size = fat[head].length * boot->ClusterSize;
990 memcpy(p, d.name, strlen(d.name));
991 p[26] = (u_char)d.head;
992 p[27] = (u_char)(d.head >> 8);
993 if (boot->ClustMask == CLUST32_MASK) {
994 p[20] = (u_char)(d.head >> 16);
995 p[21] = (u_char)(d.head >> 24);
997 p[28] = (u_char)d.size;
998 p[29] = (u_char)(d.size >> 8);
999 p[30] = (u_char)(d.size >> 16);
1000 p[31] = (u_char)(d.size >> 24);
1001 fat[head].flags |= FAT_USED;
1002 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1003 || write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1004 perror("could not write LOST.DIR");