2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 Google LLC
5 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
6 * Copyright (c) 1995 Martin Husemann
7 * Some structure declaration borrowed from Paul Popelka
8 * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include <sys/cdefs.h>
34 __RCSID("$NetBSD: dir.c,v 1.20 2006/06/05 16:51:18 christos Exp $");
35 static const char rcsid[] =
48 #include <sys/param.h>
53 #define SLOT_EMPTY 0x00 /* slot has never been used */
54 #define SLOT_E5 0x05 /* the real value is 0xe5 */
55 #define SLOT_DELETED 0xe5 /* file in this slot deleted */
57 #define ATTR_NORMAL 0x00 /* normal file */
58 #define ATTR_READONLY 0x01 /* file is readonly */
59 #define ATTR_HIDDEN 0x02 /* file is hidden */
60 #define ATTR_SYSTEM 0x04 /* file is a system file */
61 #define ATTR_VOLUME 0x08 /* entry is a volume label */
62 #define ATTR_DIRECTORY 0x10 /* entry is a directory name */
63 #define ATTR_ARCHIVE 0x20 /* file is new or modified */
65 #define ATTR_WIN95 0x0f /* long name record */
68 * This is the format of the contents of the deTime field in the direntry
70 * We don't use bitfields because we don't know how compilers for
71 * arbitrary machines will lay them out.
73 #define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */
74 #define DT_2SECONDS_SHIFT 0
75 #define DT_MINUTES_MASK 0x7E0 /* minutes */
76 #define DT_MINUTES_SHIFT 5
77 #define DT_HOURS_MASK 0xF800 /* hours */
78 #define DT_HOURS_SHIFT 11
81 * This is the format of the contents of the deDate field in the direntry
84 #define DD_DAY_MASK 0x1F /* day of month */
85 #define DD_DAY_SHIFT 0
86 #define DD_MONTH_MASK 0x1E0 /* month */
87 #define DD_MONTH_SHIFT 5
88 #define DD_YEAR_MASK 0xFE00 /* year - 1980 */
89 #define DD_YEAR_SHIFT 9
93 static struct dosDirEntry *newDosDirEntry(void);
94 static void freeDosDirEntry(struct dosDirEntry *);
95 static struct dirTodoNode *newDirTodo(void);
96 static void freeDirTodo(struct dirTodoNode *);
97 static char *fullpath(struct dosDirEntry *);
98 static u_char calcShortSum(u_char *);
99 static int delete(struct fat_descriptor *, cl_t, int, cl_t, int, int);
100 static int removede(struct fat_descriptor *, u_char *, u_char *,
101 cl_t, cl_t, cl_t, char *, int);
102 static int checksize(struct fat_descriptor *, u_char *, struct dosDirEntry *);
103 static int readDosDirSection(struct fat_descriptor *, struct dosDirEntry *);
106 * Manage free dosDirEntry structures.
108 static struct dosDirEntry *freede;
110 static struct dosDirEntry *
113 struct dosDirEntry *de;
115 if (!(de = freede)) {
116 if (!(de = malloc(sizeof *de)))
124 freeDosDirEntry(struct dosDirEntry *de)
131 * The same for dirTodoNode structures.
133 static struct dirTodoNode *freedt;
135 static struct dirTodoNode *
138 struct dirTodoNode *dt;
140 if (!(dt = freedt)) {
141 if (!(dt = malloc(sizeof *dt)))
149 freeDirTodo(struct dirTodoNode *dt)
156 * The stack of unread directories
158 static struct dirTodoNode *pendingDirectories = NULL;
161 * Return the full pathname for a directory entry.
164 fullpath(struct dosDirEntry *dir)
166 static char namebuf[MAXPATHLEN + 1];
170 cp = namebuf + sizeof namebuf;
174 np = dir->lname[0] ? dir->lname : dir->name;
176 if (cp <= namebuf + 1 + nl) {
192 * Calculate a checksum over an 8.3 alias name
195 calcShortSum(u_char *p)
200 for (i = 0; i < 11; i++) {
201 sum = (sum << 7)|(sum >> 1); /* rotate right */
209 * Global variables temporarily used during a directory scan
211 static char longName[DOSLONGNAMELEN] = "";
212 static u_char *buffer = NULL;
213 static u_char *delbuf = NULL;
215 static struct dosDirEntry *rootDir;
216 static struct dosDirEntry *lostDir;
219 * Init internal state for a new directory scan.
222 resetDosDirSection(struct fat_descriptor *fat)
224 int rootdir_size, cluster_size;
227 struct bootblock *boot;
229 boot = fat_get_boot(fat);
231 rootdir_size = boot->bpbRootDirEnts * 32;
232 cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec;
234 if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) {
235 perr("No space for directory buffer (%zu)", len);
239 if ((delbuf = malloc(len = cluster_size)) == NULL) {
241 perr("No space for directory delbuf (%zu)", len);
245 if ((rootDir = newDosDirEntry()) == NULL) {
248 perr("No space for directory entry");
252 memset(rootDir, 0, sizeof *rootDir);
253 if (boot->flags & FAT32) {
254 if (!fat_is_cl_head(fat, boot->bpbRootClust)) {
255 pfatal("Root directory doesn't start a cluster chain");
258 rootDir->head = boot->bpbRootClust;
265 * Cleanup after a directory scan
268 finishDosDirSection(void)
270 struct dirTodoNode *p, *np;
271 struct dosDirEntry *d, *nd;
273 for (p = pendingDirectories; p; p = np) {
277 pendingDirectories = NULL;
278 for (d = rootDir; d; d = nd) {
279 if ((nd = d->child) != NULL) {
287 rootDir = lostDir = NULL;
295 * Delete directory entries between startcl, startoff and endcl, endoff.
298 delete(struct fat_descriptor *fat, cl_t startcl,
299 int startoff, cl_t endcl, int endoff, int notlast)
304 struct bootblock *boot;
306 boot = fat_get_boot(fat);
307 fd = fat_get_fd(fat);
308 clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
310 s = delbuf + startoff;
312 while (fat_is_valid_cl(fat, startcl)) {
313 if (startcl == endcl) {
318 off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
320 off *= boot->bpbBytesPerSec;
321 if (lseek(fd, off, SEEK_SET) != off) {
322 perr("Unable to lseek to %" PRId64, off);
325 if (read(fd, delbuf, clsz) != clsz) {
326 perr("Unable to read directory");
333 if (lseek(fd, off, SEEK_SET) != off) {
334 perr("Unable to lseek to %" PRId64, off);
337 if (write(fd, delbuf, clsz) != clsz) {
338 perr("Unable to write directory");
341 if (startcl == endcl)
343 startcl = fat_get_cl_next(fat, startcl);
350 removede(struct fat_descriptor *fat, u_char *start,
351 u_char *end, cl_t startcl, cl_t endcl, cl_t curcl,
352 char *path, int type)
356 pwarn("Invalid long filename entry for %s\n", path);
359 pwarn("Invalid long filename entry at end of directory %s\n",
363 pwarn("Invalid long filename entry for volume label\n");
366 if (ask(0, "Remove")) {
367 if (startcl != curcl) {
369 startcl, start - buffer,
371 endcl == curcl) == FSFATAL)
375 /* startcl is < CLUST_FIRST for !FAT32 root */
376 if ((endcl == curcl) || (startcl < CLUST_FIRST))
377 for (; start < end; start += 32)
378 *start = SLOT_DELETED;
385 * Check an in-memory file entry
388 checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir)
392 u_int64_t physicalSize;
393 struct bootblock *boot;
395 boot = fat_get_boot(fat);
398 * Check size on ordinary files
400 if (dir->head == CLUST_FREE) {
403 if (!fat_is_valid_cl(fat, dir->head))
405 ret = checkchain(fat, dir->head, &chainsize);
407 * Upon return, chainsize would hold the chain length
408 * that checkchain() was able to validate, but if the user
409 * refused the proposed repair, it would be unsafe to
410 * proceed with directory entry fix, so bail out in that
413 if (ret == FSERROR) {
417 * The maximum file size on FAT32 is 4GiB - 1, which
418 * will occupy a cluster chain of exactly 4GiB in
419 * size. On 32-bit platforms, since size_t is 32-bit,
420 * it would wrap back to 0.
422 physicalSize = (u_int64_t)chainsize * boot->ClusterSize;
424 if (physicalSize < dir->size) {
425 pwarn("size of %s is %u, should at most be %ju\n",
426 fullpath(dir), dir->size, (uintmax_t)physicalSize);
427 if (ask(1, "Truncate")) {
428 dir->size = physicalSize;
429 p[28] = (u_char)physicalSize;
430 p[29] = (u_char)(physicalSize >> 8);
431 p[30] = (u_char)(physicalSize >> 16);
432 p[31] = (u_char)(physicalSize >> 24);
436 } else if (physicalSize - dir->size >= boot->ClusterSize) {
437 pwarn("%s has too many clusters allocated\n",
439 if (ask(1, "Drop superfluous clusters")) {
443 for (cl = dir->head, len = sz = 0;
444 (sz += boot->ClusterSize) < dir->size; len++)
445 cl = fat_get_cl_next(fat, cl);
446 clearchain(fat, fat_get_cl_next(fat, cl));
447 ret = fat_set_cl_next(fat, cl, CLUST_EOF);
448 return (FSFATMOD | ret);
455 static const u_char dot_name[11] = ". ";
456 static const u_char dotdot_name[11] = ".. ";
459 * Basic sanity check if the subdirectory have good '.' and '..' entries,
460 * and they are directory entries. Further sanity checks are performed
461 * when we traverse into it.
464 check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)
471 struct bootblock *boot;
473 boot = fat_get_boot(fat);
474 fd = fat_get_fd(fat);
477 if (dir->parent && !fat_is_valid_cl(fat, cl)) {
481 if (!(boot->flags & FAT32) && !dir->parent) {
482 off = boot->bpbResSectors + boot->bpbFATs *
485 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
489 * We only need to check the first two entries of the directory,
490 * which is found in the first sector of the directory entry,
491 * so read in only the first sector.
493 buf = malloc(boot->bpbBytesPerSec);
495 perr("No space for directory buffer (%u)",
496 boot->bpbBytesPerSec);
500 off *= boot->bpbBytesPerSec;
501 if (lseek(fd, off, SEEK_SET) != off ||
502 read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) {
503 perr("Unable to read directory");
509 * Both `.' and `..' must be present and be the first two entries
510 * and be ATTR_DIRECTORY of a valid subdirectory.
513 if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
514 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
515 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
519 if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
520 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
521 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
530 * Read a directory and
531 * - resolve long name records
532 * - enter file and directory records into the parent's list
533 * - push directories onto the todo-stack
536 readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir)
538 struct bootblock *boot;
539 struct dosDirEntry dirent, *d;
540 u_char *p, *vallfn, *invlfn, *empty;
542 int fd, i, j, k, iosize, entries;
544 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
550 #define THISMOD 0x8000 /* Only used within this routine */
552 boot = fat_get_boot(fat);
553 fd = fat_get_fd(fat);
556 if (dir->parent && (!fat_is_valid_cl(fat, cl))) {
558 * Already handled somewhere else.
563 vallfn = invlfn = empty = NULL;
566 * If we are checking the legacy root (for FAT12/FAT16),
567 * we will operate on the whole directory; otherwise, we
568 * will operate on one cluster at a time, and also take
569 * this opportunity to examine the chain.
571 * Derive how many entries we are going to encounter from
574 is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32));
576 iosize = boot->bpbRootDirEnts * 32;
577 entries = boot->bpbRootDirEnts;
579 iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec;
580 entries = iosize / 32;
581 mod |= checkchain(fat, dir->head, &dirclusters);
587 * Special case for FAT12/FAT16 root -- read
588 * in the whole root directory.
590 off = boot->bpbResSectors + boot->bpbFATs *
594 * Otherwise, read in a cluster of the
597 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
600 off *= boot->bpbBytesPerSec;
601 if (lseek(fd, off, SEEK_SET) != off ||
602 read(fd, buffer, iosize) != iosize) {
603 perr("Unable to read directory");
607 for (p = buffer, i = 0; i < entries; i++, p += 32) {
608 if (dir->fsckflags & DIREMPWARN) {
613 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
614 if (*p == SLOT_EMPTY) {
615 dir->fsckflags |= DIREMPTY;
622 if (dir->fsckflags & DIREMPTY) {
623 if (!(dir->fsckflags & DIREMPWARN)) {
624 pwarn("%s has entries after end of directory\n",
626 if (ask(1, "Extend")) {
629 dir->fsckflags &= ~DIREMPTY;
631 empcl, empty - buffer,
632 cl, p - buffer, 1) == FSFATAL)
634 q = ((empcl == cl) ? empty : buffer);
636 for (; q < p; q += 32)
638 mod |= THISMOD|FSDIRMOD;
639 } else if (ask(0, "Truncate"))
640 dir->fsckflags |= DIREMPWARN;
642 if (dir->fsckflags & DIREMPWARN) {
644 mod |= THISMOD|FSDIRMOD;
646 } else if (dir->fsckflags & DIREMPTY)
651 if (p[11] == ATTR_WIN95) {
653 if (shortSum != -1) {
659 memset(longName, 0, sizeof longName);
663 } else if (shortSum != p[13]
664 || lidx != (*p & LRNOMASK)) {
675 lidx = *p & LRNOMASK;
677 pwarn("invalid long name\n");
685 t = longName + --lidx * 13;
686 for (k = 1; k < 11 && t < longName +
687 sizeof(longName); k += 2) {
688 if (!p[k] && !p[k + 1])
692 * Warn about those unusable chars in msdosfs here? XXX
698 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
699 if (!p[k] && !p[k + 1])
706 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
707 if (!p[k] && !p[k + 1])
713 if (t >= longName + sizeof(longName)) {
714 pwarn("long filename too long\n");
721 if (p[26] | (p[27] << 8)) {
722 pwarn("long filename record cluster start != 0\n");
729 continue; /* long records don't carry further
734 * This is a standard msdosfs directory entry.
736 memset(&dirent, 0, sizeof dirent);
739 * it's a short name record, but we need to know
740 * more, so get the flags first.
742 dirent.flags = p[11];
745 * Translate from 850 to ISO here XXX
747 for (j = 0; j < 8; j++)
748 dirent.name[j] = p[j];
749 dirent.name[8] = '\0';
750 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
751 dirent.name[k] = '\0';
752 if (k < 0 || dirent.name[k] != '\0')
754 if (dirent.name[0] == SLOT_E5)
755 dirent.name[0] = 0xe5;
757 if (dirent.flags & ATTR_VOLUME) {
758 if (vallfn || invlfn) {
760 invlfn ? invlfn : vallfn, p,
761 invlfn ? invcl : valcl, -1, 0,
770 dirent.name[k++] = '.';
771 for (j = 0; j < 3; j++)
772 dirent.name[k++] = p[j+8];
773 dirent.name[k] = '\0';
774 for (k--; k >= 0 && dirent.name[k] == ' '; k--)
775 dirent.name[k] = '\0';
777 if (vallfn && shortSum != calcShortSum(p)) {
784 dirent.head = p[26] | (p[27] << 8);
785 if (boot->ClustMask == CLUST32_MASK)
786 dirent.head |= (p[20] << 16) | (p[21] << 24);
787 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
789 strlcpy(dirent.lname, longName,
790 sizeof(dirent.lname));
796 dirent.next = dir->child;
799 mod |= k = removede(fat,
800 invlfn, vallfn ? vallfn : p,
801 invcl, vallfn ? valcl : cl, cl,
802 fullpath(&dirent), 0);
806 ? (valcl == cl && vallfn != buffer)
812 vallfn = NULL; /* not used any longer */
816 * Check if the directory entry is sane.
818 * '.' and '..' are skipped, their sanity is
819 * checked somewhere else.
821 * For everything else, check if we have a new,
822 * valid cluster chain (beginning of a file or
823 * directory that was never previously claimed
824 * by another file) when it's a non-empty file
825 * or a directory. The sanity of the cluster
826 * chain is checked at a later time when we
827 * traverse into the directory, or examine the
828 * file's directory entry.
830 * The only possible fix is to delete the entry
831 * if it's a directory; for file, we have to
832 * truncate the size to 0.
834 if (!(dirent.flags & ATTR_DIRECTORY) ||
835 (strcmp(dirent.name, ".") != 0 &&
836 strcmp(dirent.name, "..") != 0)) {
837 if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) &&
838 ((!fat_is_valid_cl(fat, dirent.head) ||
839 !fat_is_cl_head(fat, dirent.head)))) {
840 if (!fat_is_valid_cl(fat, dirent.head)) {
841 pwarn("%s starts with cluster out of range(%u)\n",
845 pwarn("%s doesn't start a new cluster chain\n",
849 if (dirent.flags & ATTR_DIRECTORY) {
850 if (ask(0, "Remove")) {
852 mod |= THISMOD|FSDIRMOD;
857 if (ask(1, "Truncate")) {
858 p[28] = p[29] = p[30] = p[31] = 0;
860 if (boot->ClustMask == CLUST32_MASK)
864 mod |= THISMOD|FSDIRMOD;
870 if (dirent.flags & ATTR_DIRECTORY) {
872 * gather more info for directories
874 struct dirTodoNode *n;
877 pwarn("Directory %s has size != 0\n",
879 if (ask(1, "Correct")) {
880 p[28] = p[29] = p[30] = p[31] = 0;
882 mod |= THISMOD|FSDIRMOD;
887 * handle `.' and `..' specially
889 if (strcmp(dirent.name, ".") == 0) {
890 if (dirent.head != dir->head) {
891 pwarn("`.' entry in %s has incorrect start cluster\n",
893 if (ask(1, "Correct")) {
894 dirent.head = dir->head;
895 p[26] = (u_char)dirent.head;
896 p[27] = (u_char)(dirent.head >> 8);
897 if (boot->ClustMask == CLUST32_MASK) {
898 p[20] = (u_char)(dirent.head >> 16);
899 p[21] = (u_char)(dirent.head >> 24);
901 mod |= THISMOD|FSDIRMOD;
906 } else if (strcmp(dirent.name, "..") == 0) {
907 if (dir->parent) { /* XXX */
908 if (!dir->parent->parent) {
910 pwarn("`..' entry in %s has non-zero start cluster\n",
912 if (ask(1, "Correct")) {
915 if (boot->ClustMask == CLUST32_MASK)
917 mod |= THISMOD|FSDIRMOD;
921 } else if (dirent.head != dir->parent->head) {
922 pwarn("`..' entry in %s has incorrect start cluster\n",
924 if (ask(1, "Correct")) {
925 dirent.head = dir->parent->head;
926 p[26] = (u_char)dirent.head;
927 p[27] = (u_char)(dirent.head >> 8);
928 if (boot->ClustMask == CLUST32_MASK) {
929 p[20] = (u_char)(dirent.head >> 16);
930 p[21] = (u_char)(dirent.head >> 24);
932 mod |= THISMOD|FSDIRMOD;
940 * Only one directory entry can point
941 * to dir->head, it's '.'.
943 if (dirent.head == dir->head) {
944 pwarn("%s entry in %s has incorrect start cluster\n",
945 dirent.name, fullpath(dir));
946 if (ask(1, "Remove")) {
948 mod |= THISMOD|FSDIRMOD;
952 } else if ((check_subdirectory(fat,
953 &dirent) & FSERROR) == FSERROR) {
955 * A subdirectory should have
956 * a dot (.) entry and a dot-dot
957 * (..) entry of ATTR_DIRECTORY,
958 * we will inspect further when
959 * traversing into it.
961 if (ask(1, "Remove")) {
963 mod |= THISMOD|FSDIRMOD;
970 /* create directory tree node */
971 if (!(d = newDosDirEntry())) {
972 perr("No space for directory");
975 memcpy(d, &dirent, sizeof(struct dosDirEntry));
976 /* link it into the tree */
979 /* Enter this directory into the todo list */
980 if (!(n = newDirTodo())) {
981 perr("No space for todo list");
984 n->next = pendingDirectories;
986 pendingDirectories = n;
988 mod |= k = checksize(fat, p, &dirent);
997 * Don't bother to write back right now because
998 * we may continue to make modification to the
999 * non-FAT32 root directory below.
1002 } else if (mod & THISMOD) {
1003 if (lseek(fd, off, SEEK_SET) != off
1004 || write(fd, buffer, iosize) != iosize) {
1005 perr("Unable to write directory");
1010 } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl))));
1011 if (invlfn || vallfn)
1012 mod |= removede(fat,
1013 invlfn ? invlfn : vallfn, p,
1014 invlfn ? invcl : valcl, -1, 0,
1018 * The root directory of non-FAT32 filesystems is in a special
1019 * area and may have been modified above removede() without
1020 * being written out.
1022 if ((mod & FSDIRMOD) && is_legacyroot) {
1023 if (lseek(fd, off, SEEK_SET) != off
1024 || write(fd, buffer, iosize) != iosize) {
1025 perr("Unable to write directory");
1030 return mod & ~THISMOD;
1034 handleDirTree(struct fat_descriptor *fat)
1038 mod = readDosDirSection(fat, rootDir);
1043 * process the directory todo list
1045 while (pendingDirectories) {
1046 struct dosDirEntry *dir = pendingDirectories->dir;
1047 struct dirTodoNode *n = pendingDirectories->next;
1050 * remove TODO entry now, the list might change during
1053 freeDirTodo(pendingDirectories);
1054 pendingDirectories = n;
1057 * handle subdirectory
1059 mod |= readDosDirSection(fat, dir);
1068 * Try to reconnect a FAT chain into dir
1070 static u_char *lfbuf;
1075 reconnect(struct fat_descriptor *fat, cl_t head, size_t length)
1077 struct bootblock *boot = fat_get_boot(fat);
1078 struct dosDirEntry d;
1082 dosfs = fat_get_fd(fat);
1084 if (!ask(1, "Reconnect"))
1088 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1089 if (!strcmp(lostDir->name, LOSTDIR))
1092 if (!lostDir) { /* Create LOSTDIR? XXX */
1093 pwarn("No %s directory\n", LOSTDIR);
1098 lfbuf = malloc(boot->ClusterSize);
1100 perr("No space for buffer");
1108 for (; p < lfbuf + boot->ClusterSize; p += 32)
1109 if (*p == SLOT_EMPTY
1110 || *p == SLOT_DELETED)
1112 if (p && p < lfbuf + boot->ClusterSize)
1114 lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head;
1115 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
1116 /* Extend LOSTDIR? XXX */
1117 pwarn("No space in %s\n", LOSTDIR);
1118 lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
1121 lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
1122 + boot->FirstCluster * boot->bpbBytesPerSec;
1124 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1125 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1126 perr("could not read LOST.DIR");
1133 /* Ensure uniqueness of entry here! XXX */
1134 memset(&d, 0, sizeof d);
1135 /* worst case -1 = 4294967295, 10 digits */
1136 len = snprintf(d.name, sizeof(d.name), "%u", head);
1139 d.size = length * boot->ClusterSize;
1141 memcpy(p, d.name, len);
1142 memset(p + len, ' ', 11 - len);
1143 memset(p + 11, 0, 32 - 11);
1144 p[26] = (u_char)d.head;
1145 p[27] = (u_char)(d.head >> 8);
1146 if (boot->ClustMask == CLUST32_MASK) {
1147 p[20] = (u_char)(d.head >> 16);
1148 p[21] = (u_char)(d.head >> 24);
1150 p[28] = (u_char)d.size;
1151 p[29] = (u_char)(d.size >> 8);
1152 p[30] = (u_char)(d.size >> 16);
1153 p[31] = (u_char)(d.size >> 24);
1154 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1155 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1156 perr("could not write LOST.DIR");