1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
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.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
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.
18 *
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.
29 */
30
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: dir.c,v 1.20 2006/06/05 16:51:18 christos Exp $");
35 #endif /* not lint */
36
37 #include <assert.h>
38 #include <inttypes.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <unistd.h>
44 #include <time.h>
45
46 #include <sys/param.h>
47
48 #include "ext.h"
49 #include "fsutil.h"
50
51 #define SLOT_EMPTY 0x00 /* slot has never been used */
52 #define SLOT_E5 0x05 /* the real value is 0xe5 */
53 #define SLOT_DELETED 0xe5 /* file in this slot deleted */
54
55 #define ATTR_NORMAL 0x00 /* normal file */
56 #define ATTR_READONLY 0x01 /* file is readonly */
57 #define ATTR_HIDDEN 0x02 /* file is hidden */
58 #define ATTR_SYSTEM 0x04 /* file is a system file */
59 #define ATTR_VOLUME 0x08 /* entry is a volume label */
60 #define ATTR_DIRECTORY 0x10 /* entry is a directory name */
61 #define ATTR_ARCHIVE 0x20 /* file is new or modified */
62
63 #define ATTR_WIN95 0x0f /* long name record */
64
65 /*
66 * This is the format of the contents of the deTime field in the direntry
67 * structure.
68 * We don't use bitfields because we don't know how compilers for
69 * arbitrary machines will lay them out.
70 */
71 #define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */
72 #define DT_2SECONDS_SHIFT 0
73 #define DT_MINUTES_MASK 0x7E0 /* minutes */
74 #define DT_MINUTES_SHIFT 5
75 #define DT_HOURS_MASK 0xF800 /* hours */
76 #define DT_HOURS_SHIFT 11
77
78 /*
79 * This is the format of the contents of the deDate field in the direntry
80 * structure.
81 */
82 #define DD_DAY_MASK 0x1F /* day of month */
83 #define DD_DAY_SHIFT 0
84 #define DD_MONTH_MASK 0x1E0 /* month */
85 #define DD_MONTH_SHIFT 5
86 #define DD_YEAR_MASK 0xFE00 /* year - 1980 */
87 #define DD_YEAR_SHIFT 9
88
89
90 /* dir.c */
91 static struct dosDirEntry *newDosDirEntry(void);
92 static void freeDosDirEntry(struct dosDirEntry *);
93 static struct dirTodoNode *newDirTodo(void);
94 static void freeDirTodo(struct dirTodoNode *);
95 static char *fullpath(struct dosDirEntry *);
96 static u_char calcShortSum(u_char *);
97 static int delete(struct fat_descriptor *, cl_t, int, cl_t, int, int);
98 static int removede(struct fat_descriptor *, u_char *, u_char *,
99 cl_t, cl_t, cl_t, char *, int);
100 static int checksize(struct fat_descriptor *, u_char *, struct dosDirEntry *);
101 static int readDosDirSection(struct fat_descriptor *, struct dosDirEntry *);
102
103 /*
104 * Manage free dosDirEntry structures.
105 */
106 static struct dosDirEntry *freede;
107
108 static struct dosDirEntry *
newDosDirEntry(void)109 newDosDirEntry(void)
110 {
111 struct dosDirEntry *de;
112
113 if (!(de = freede)) {
114 if (!(de = malloc(sizeof *de)))
115 return (NULL);
116 } else
117 freede = de->next;
118 return de;
119 }
120
121 static void
freeDosDirEntry(struct dosDirEntry *de)122 freeDosDirEntry(struct dosDirEntry *de)
123 {
124 de->next = freede;
125 freede = de;
126 }
127
128 /*
129 * The same for dirTodoNode structures.
130 */
131 static struct dirTodoNode *freedt;
132
133 static struct dirTodoNode *
newDirTodo(void)134 newDirTodo(void)
135 {
136 struct dirTodoNode *dt;
137
138 if (!(dt = freedt)) {
139 if (!(dt = malloc(sizeof *dt)))
140 return 0;
141 } else
142 freedt = dt->next;
143 return dt;
144 }
145
146 static void
freeDirTodo(struct dirTodoNode *dt)147 freeDirTodo(struct dirTodoNode *dt)
148 {
149 dt->next = freedt;
150 freedt = dt;
151 }
152
153 /*
154 * The stack of unread directories
155 */
156 static struct dirTodoNode *pendingDirectories = NULL;
157
158 /*
159 * Return the full pathname for a directory entry.
160 */
161 static char *
fullpath(struct dosDirEntry *dir)162 fullpath(struct dosDirEntry *dir)
163 {
164 static char namebuf[MAXPATHLEN + 1];
165 char *cp, *np;
166 int nl;
167
168 cp = namebuf + sizeof namebuf;
169 *--cp = '\0';
170
171 for(;;) {
172 np = dir->lname[0] ? dir->lname : dir->name;
173 nl = strlen(np);
174 if (cp <= namebuf + 1 + nl) {
175 *--cp = '?';
176 break;
177 }
178 cp -= nl;
179 memcpy(cp, np, nl);
180 dir = dir->parent;
181 if (!dir)
182 break;
183 *--cp = '/';
184 }
185
186 return cp;
187 }
188
189 /*
190 * Calculate a checksum over an 8.3 alias name
191 */
192 static inline u_char
calcShortSum(u_char *p)193 calcShortSum(u_char *p)
194 {
195 u_char sum = 0;
196 int i;
197
198 for (i = 0; i < 11; i++) {
199 sum = (sum << 7)|(sum >> 1); /* rotate right */
200 sum += p[i];
201 }
202
203 return sum;
204 }
205
206 /*
207 * Global variables temporarily used during a directory scan
208 */
209 static char longName[DOSLONGNAMELEN] = "";
210 static u_char *buffer = NULL;
211 static u_char *delbuf = NULL;
212
213 static struct dosDirEntry *rootDir;
214 static struct dosDirEntry *lostDir;
215
216 /*
217 * Init internal state for a new directory scan.
218 */
219 int
resetDosDirSection(struct fat_descriptor *fat)220 resetDosDirSection(struct fat_descriptor *fat)
221 {
222 int rootdir_size, cluster_size;
223 int ret = FSOK;
224 size_t len;
225 struct bootblock *boot;
226
227 boot = fat_get_boot(fat);
228
229 rootdir_size = boot->bpbRootDirEnts * 32;
230 cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec;
231
232 if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) {
233 perr("No space for directory buffer (%zu)", len);
234 return FSFATAL;
235 }
236
237 if ((delbuf = malloc(len = cluster_size)) == NULL) {
238 free(buffer);
239 perr("No space for directory delbuf (%zu)", len);
240 return FSFATAL;
241 }
242
243 if ((rootDir = newDosDirEntry()) == NULL) {
244 free(buffer);
245 free(delbuf);
246 perr("No space for directory entry");
247 return FSFATAL;
248 }
249
250 memset(rootDir, 0, sizeof *rootDir);
251 if (boot->flags & FAT32) {
252 if (!fat_is_cl_head(fat, boot->bpbRootClust)) {
253 pfatal("Root directory doesn't start a cluster chain");
254 return FSFATAL;
255 }
256 rootDir->head = boot->bpbRootClust;
257 }
258
259 return ret;
260 }
261
262 /*
263 * Cleanup after a directory scan
264 */
265 void
finishDosDirSection(void)266 finishDosDirSection(void)
267 {
268 struct dirTodoNode *p, *np;
269 struct dosDirEntry *d, *nd;
270
271 for (p = pendingDirectories; p; p = np) {
272 np = p->next;
273 freeDirTodo(p);
274 }
275 pendingDirectories = NULL;
276 for (d = rootDir; d; d = nd) {
277 if ((nd = d->child) != NULL) {
278 d->child = 0;
279 continue;
280 }
281 if (!(nd = d->next))
282 nd = d->parent;
283 freeDosDirEntry(d);
284 }
285 rootDir = lostDir = NULL;
286 free(buffer);
287 free(delbuf);
288 buffer = NULL;
289 delbuf = NULL;
290 }
291
292 /*
293 * Delete directory entries between startcl, startoff and endcl, endoff.
294 */
295 static int
delete(struct fat_descriptor *fat, cl_t startcl, int startoff, cl_t endcl, int endoff, int notlast)296 delete(struct fat_descriptor *fat, cl_t startcl,
297 int startoff, cl_t endcl, int endoff, int notlast)
298 {
299 u_char *s, *e;
300 off_t off;
301 int clsz, fd;
302 struct bootblock *boot;
303
304 boot = fat_get_boot(fat);
305 fd = fat_get_fd(fat);
306 clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
307
308 s = delbuf + startoff;
309 e = delbuf + clsz;
310 while (fat_is_valid_cl(fat, startcl)) {
311 if (startcl == endcl) {
312 if (notlast)
313 break;
314 e = delbuf + endoff;
315 }
316 off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
317
318 off *= boot->bpbBytesPerSec;
319 if (lseek(fd, off, SEEK_SET) != off) {
320 perr("Unable to lseek to %" PRId64, off);
321 return FSFATAL;
322 }
323 if (read(fd, delbuf, clsz) != clsz) {
324 perr("Unable to read directory");
325 return FSFATAL;
326 }
327 while (s < e) {
328 *s = SLOT_DELETED;
329 s += 32;
330 }
331 if (lseek(fd, off, SEEK_SET) != off) {
332 perr("Unable to lseek to %" PRId64, off);
333 return FSFATAL;
334 }
335 if (write(fd, delbuf, clsz) != clsz) {
336 perr("Unable to write directory");
337 return FSFATAL;
338 }
339 if (startcl == endcl)
340 break;
341 startcl = fat_get_cl_next(fat, startcl);
342 s = delbuf;
343 }
344 return FSOK;
345 }
346
347 static int
removede(struct fat_descriptor *fat, u_char *start, u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type)348 removede(struct fat_descriptor *fat, u_char *start,
349 u_char *end, cl_t startcl, cl_t endcl, cl_t curcl,
350 char *path, int type)
351 {
352 switch (type) {
353 case 0:
354 pwarn("Invalid long filename entry for %s\n", path);
355 break;
356 case 1:
357 pwarn("Invalid long filename entry at end of directory %s\n",
358 path);
359 break;
360 case 2:
361 pwarn("Invalid long filename entry for volume label\n");
362 break;
363 }
364 if (ask(0, "Remove")) {
365 if (startcl != curcl) {
366 if (delete(fat,
367 startcl, start - buffer,
368 endcl, end - buffer,
369 endcl == curcl) == FSFATAL)
370 return FSFATAL;
371 start = buffer;
372 }
373 /* startcl is < CLUST_FIRST for !FAT32 root */
374 if ((endcl == curcl) || (startcl < CLUST_FIRST))
375 for (; start < end; start += 32)
376 *start = SLOT_DELETED;
377 return FSDIRMOD;
378 }
379 return FSERROR;
380 }
381
382 /*
383 * Check an in-memory file entry
384 */
385 static int
checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir)386 checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir)
387 {
388 int ret = FSOK;
389 size_t chainsize;
390 u_int64_t physicalSize;
391 struct bootblock *boot;
392
393 boot = fat_get_boot(fat);
394
395 /*
396 * Check size on ordinary files
397 */
398 if (dir->head == CLUST_FREE) {
399 physicalSize = 0;
400 } else {
401 if (!fat_is_valid_cl(fat, dir->head))
402 return FSERROR;
403 ret = checkchain(fat, dir->head, &chainsize);
404 /*
405 * Upon return, chainsize would hold the chain length
406 * that checkchain() was able to validate, but if the user
407 * refused the proposed repair, it would be unsafe to
408 * proceed with directory entry fix, so bail out in that
409 * case.
410 */
411 if (ret == FSERROR) {
412 return (FSERROR);
413 }
414 /*
415 * The maximum file size on FAT32 is 4GiB - 1, which
416 * will occupy a cluster chain of exactly 4GiB in
417 * size. On 32-bit platforms, since size_t is 32-bit,
418 * it would wrap back to 0.
419 */
420 physicalSize = (u_int64_t)chainsize * boot->ClusterSize;
421 }
422 if (physicalSize < dir->size) {
423 pwarn("size of %s is %u, should at most be %ju\n",
424 fullpath(dir), dir->size, (uintmax_t)physicalSize);
425 if (ask(1, "Truncate")) {
426 dir->size = physicalSize;
427 p[28] = (u_char)physicalSize;
428 p[29] = (u_char)(physicalSize >> 8);
429 p[30] = (u_char)(physicalSize >> 16);
430 p[31] = (u_char)(physicalSize >> 24);
431 return FSDIRMOD;
432 } else
433 return FSERROR;
434 } else if (physicalSize - dir->size >= boot->ClusterSize) {
435 pwarn("%s has too many clusters allocated\n",
436 fullpath(dir));
437 if (ask(1, "Drop superfluous clusters")) {
438 cl_t cl;
439 u_int32_t sz, len;
440
441 for (cl = dir->head, len = sz = 0;
442 (sz += boot->ClusterSize) < dir->size; len++)
443 cl = fat_get_cl_next(fat, cl);
444 clearchain(fat, fat_get_cl_next(fat, cl));
445 ret = fat_set_cl_next(fat, cl, CLUST_EOF);
446 return (FSFATMOD | ret);
447 } else
448 return FSERROR;
449 }
450 return FSOK;
451 }
452
453 static const u_char dot_name[11] = ". ";
454 static const u_char dotdot_name[11] = ".. ";
455
456 /*
457 * Basic sanity check if the subdirectory have good '.' and '..' entries,
458 * and they are directory entries. Further sanity checks are performed
459 * when we traverse into it.
460 */
461 static int
check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)462 check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)
463 {
464 u_char *buf, *cp;
465 off_t off;
466 cl_t cl;
467 int retval = FSOK;
468 int fd;
469 struct bootblock *boot;
470
471 boot = fat_get_boot(fat);
472 fd = fat_get_fd(fat);
473
474 cl = dir->head;
475 if (dir->parent && !fat_is_valid_cl(fat, cl)) {
476 return FSERROR;
477 }
478
479 if (!(boot->flags & FAT32) && !dir->parent) {
480 off = boot->bpbResSectors + boot->bpbFATs *
481 boot->FATsecs;
482 } else {
483 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
484 }
485
486 /*
487 * We only need to check the first two entries of the directory,
488 * which is found in the first sector of the directory entry,
489 * so read in only the first sector.
490 */
491 buf = malloc(boot->bpbBytesPerSec);
492 if (buf == NULL) {
493 perr("No space for directory buffer (%u)",
494 boot->bpbBytesPerSec);
495 return FSFATAL;
496 }
497
498 off *= boot->bpbBytesPerSec;
499 if (lseek(fd, off, SEEK_SET) != off ||
500 read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) {
501 perr("Unable to read directory");
502 free(buf);
503 return FSFATAL;
504 }
505
506 /*
507 * Both `.' and `..' must be present and be the first two entries
508 * and be ATTR_DIRECTORY of a valid subdirectory.
509 */
510 cp = buf;
511 if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
512 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
513 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
514 retval |= FSERROR;
515 }
516 cp += 32;
517 if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
518 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
519 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
520 retval |= FSERROR;
521 }
522
523 free(buf);
524 return retval;
525 }
526
527 /*
528 * Read a directory and
529 * - resolve long name records
530 * - enter file and directory records into the parent's list
531 * - push directories onto the todo-stack
532 */
533 static int
readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir)534 readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir)
535 {
536 struct bootblock *boot;
537 struct dosDirEntry dirent, *d;
538 u_char *p, *vallfn, *invlfn, *empty;
539 off_t off;
540 int fd, i, j, k, iosize, entries;
541 bool is_legacyroot;
542 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
543 char *t;
544 u_int lidx = 0;
545 int shortSum;
546 int mod = FSOK;
547 size_t dirclusters;
548 #define THISMOD 0x8000 /* Only used within this routine */
549
550 boot = fat_get_boot(fat);
551 fd = fat_get_fd(fat);
552
553 cl = dir->head;
554 if (dir->parent && (!fat_is_valid_cl(fat, cl))) {
555 /*
556 * Already handled somewhere else.
557 */
558 return FSOK;
559 }
560 shortSum = -1;
561 vallfn = invlfn = empty = NULL;
562
563 /*
564 * If we are checking the legacy root (for FAT12/FAT16),
565 * we will operate on the whole directory; otherwise, we
566 * will operate on one cluster at a time, and also take
567 * this opportunity to examine the chain.
568 *
569 * Derive how many entries we are going to encounter from
570 * the I/O size.
571 */
572 is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32));
573 if (is_legacyroot) {
574 iosize = boot->bpbRootDirEnts * 32;
575 entries = boot->bpbRootDirEnts;
576 } else {
577 iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec;
578 entries = iosize / 32;
579 mod |= checkchain(fat, dir->head, &dirclusters);
580 }
581
582 do {
583 if (is_legacyroot) {
584 /*
585 * Special case for FAT12/FAT16 root -- read
586 * in the whole root directory.
587 */
588 off = boot->bpbResSectors + boot->bpbFATs *
589 boot->FATsecs;
590 } else {
591 /*
592 * Otherwise, read in a cluster of the
593 * directory.
594 */
595 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
596 }
597
598 off *= boot->bpbBytesPerSec;
599 if (lseek(fd, off, SEEK_SET) != off ||
600 read(fd, buffer, iosize) != iosize) {
601 perr("Unable to read directory");
602 return FSFATAL;
603 }
604
605 for (p = buffer, i = 0; i < entries; i++, p += 32) {
606 if (dir->fsckflags & DIREMPWARN) {
607 *p = SLOT_EMPTY;
608 continue;
609 }
610
611 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
612 if (*p == SLOT_EMPTY) {
613 dir->fsckflags |= DIREMPTY;
614 empty = p;
615 empcl = cl;
616 }
617 continue;
618 }
619
620 if (dir->fsckflags & DIREMPTY) {
621 if (!(dir->fsckflags & DIREMPWARN)) {
622 pwarn("%s has entries after end of directory\n",
623 fullpath(dir));
624 if (ask(1, "Extend")) {
625 u_char *q;
626
627 dir->fsckflags &= ~DIREMPTY;
628 if (delete(fat,
629 empcl, empty - buffer,
630 cl, p - buffer, 1) == FSFATAL)
631 return FSFATAL;
632 q = ((empcl == cl) ? empty : buffer);
633 assert(q != NULL);
634 for (; q < p; q += 32)
635 *q = SLOT_DELETED;
636 mod |= THISMOD|FSDIRMOD;
637 } else if (ask(0, "Truncate"))
638 dir->fsckflags |= DIREMPWARN;
639 }
640 if (dir->fsckflags & DIREMPWARN) {
641 *p = SLOT_DELETED;
642 mod |= THISMOD|FSDIRMOD;
643 continue;
644 } else if (dir->fsckflags & DIREMPTY)
645 mod |= FSERROR;
646 empty = NULL;
647 }
648
649 if (p[11] == ATTR_WIN95) {
650 if (*p & LRFIRST) {
651 if (shortSum != -1) {
652 if (!invlfn) {
653 invlfn = vallfn;
654 invcl = valcl;
655 }
656 }
657 memset(longName, 0, sizeof longName);
658 shortSum = p[13];
659 vallfn = p;
660 valcl = cl;
661 } else if (shortSum != p[13]
662 || lidx != (*p & LRNOMASK)) {
663 if (!invlfn) {
664 invlfn = vallfn;
665 invcl = valcl;
666 }
667 if (!invlfn) {
668 invlfn = p;
669 invcl = cl;
670 }
671 vallfn = NULL;
672 }
673 lidx = *p & LRNOMASK;
674 if (lidx == 0) {
675 pwarn("invalid long name\n");
676 if (!invlfn) {
677 invlfn = vallfn;
678 invcl = valcl;
679 }
680 vallfn = NULL;
681 continue;
682 }
683 t = longName + --lidx * 13;
684 for (k = 1; k < 11 && t < longName +
685 sizeof(longName); k += 2) {
686 if (!p[k] && !p[k + 1])
687 break;
688 *t++ = p[k];
689 /*
690 * Warn about those unusable chars in msdosfs here? XXX
691 */
692 if (p[k + 1])
693 t[-1] = '?';
694 }
695 if (k >= 11)
696 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
697 if (!p[k] && !p[k + 1])
698 break;
699 *t++ = p[k];
700 if (p[k + 1])
701 t[-1] = '?';
702 }
703 if (k >= 26)
704 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
705 if (!p[k] && !p[k + 1])
706 break;
707 *t++ = p[k];
708 if (p[k + 1])
709 t[-1] = '?';
710 }
711 if (t >= longName + sizeof(longName)) {
712 pwarn("long filename too long\n");
713 if (!invlfn) {
714 invlfn = vallfn;
715 invcl = valcl;
716 }
717 vallfn = NULL;
718 }
719 if (p[26] | (p[27] << 8)) {
720 pwarn("long filename record cluster start != 0\n");
721 if (!invlfn) {
722 invlfn = vallfn;
723 invcl = cl;
724 }
725 vallfn = NULL;
726 }
727 continue; /* long records don't carry further
728 * information */
729 }
730
731 /*
732 * This is a standard msdosfs directory entry.
733 */
734 memset(&dirent, 0, sizeof dirent);
735
736 /*
737 * it's a short name record, but we need to know
738 * more, so get the flags first.
739 */
740 dirent.flags = p[11];
741
742 /*
743 * Translate from 850 to ISO here XXX
744 */
745 for (j = 0; j < 8; j++)
746 dirent.name[j] = p[j];
747 dirent.name[8] = '\0';
748 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
749 dirent.name[k] = '\0';
750 if (k < 0 || dirent.name[k] != '\0')
751 k++;
752 if (dirent.name[0] == SLOT_E5)
753 dirent.name[0] = 0xe5;
754
755 if (dirent.flags & ATTR_VOLUME) {
756 if (vallfn || invlfn) {
757 mod |= removede(fat,
758 invlfn ? invlfn : vallfn, p,
759 invlfn ? invcl : valcl, -1, 0,
760 fullpath(dir), 2);
761 vallfn = NULL;
762 invlfn = NULL;
763 }
764 continue;
765 }
766
767 if (p[8] != ' ')
768 dirent.name[k++] = '.';
769 for (j = 0; j < 3; j++)
770 dirent.name[k++] = p[j+8];
771 dirent.name[k] = '\0';
772 for (k--; k >= 0 && dirent.name[k] == ' '; k--)
773 dirent.name[k] = '\0';
774
775 if (vallfn && shortSum != calcShortSum(p)) {
776 if (!invlfn) {
777 invlfn = vallfn;
778 invcl = valcl;
779 }
780 vallfn = NULL;
781 }
782 dirent.head = p[26] | (p[27] << 8);
783 if (boot->ClustMask == CLUST32_MASK)
784 dirent.head |= (p[20] << 16) | (p[21] << 24);
785 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
786 if (vallfn) {
787 strlcpy(dirent.lname, longName,
788 sizeof(dirent.lname));
789 longName[0] = '\0';
790 shortSum = -1;
791 }
792
793 dirent.parent = dir;
794 dirent.next = dir->child;
795
796 if (invlfn) {
797 mod |= k = removede(fat,
798 invlfn, vallfn ? vallfn : p,
799 invcl, vallfn ? valcl : cl, cl,
800 fullpath(&dirent), 0);
801 if (mod & FSFATAL)
802 return FSFATAL;
803 if (vallfn
804 ? (valcl == cl && vallfn != buffer)
805 : p != buffer)
806 if (k & FSDIRMOD)
807 mod |= THISMOD;
808 }
809
810 vallfn = NULL; /* not used any longer */
811 invlfn = NULL;
812
813 /*
814 * Check if the directory entry is sane.
815 *
816 * '.' and '..' are skipped, their sanity is
817 * checked somewhere else.
818 *
819 * For everything else, check if we have a new,
820 * valid cluster chain (beginning of a file or
821 * directory that was never previously claimed
822 * by another file) when it's a non-empty file
823 * or a directory. The sanity of the cluster
824 * chain is checked at a later time when we
825 * traverse into the directory, or examine the
826 * file's directory entry.
827 *
828 * The only possible fix is to delete the entry
829 * if it's a directory; for file, we have to
830 * truncate the size to 0.
831 */
832 if (!(dirent.flags & ATTR_DIRECTORY) ||
833 (strcmp(dirent.name, ".") != 0 &&
834 strcmp(dirent.name, "..") != 0)) {
835 if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) &&
836 ((!fat_is_valid_cl(fat, dirent.head) ||
837 !fat_is_cl_head(fat, dirent.head)))) {
838 if (!fat_is_valid_cl(fat, dirent.head)) {
839 pwarn("%s starts with cluster out of range(%u)\n",
840 fullpath(&dirent),
841 dirent.head);
842 } else {
843 pwarn("%s doesn't start a new cluster chain\n",
844 fullpath(&dirent));
845 }
846
847 if (dirent.flags & ATTR_DIRECTORY) {
848 if (ask(0, "Remove")) {
849 *p = SLOT_DELETED;
850 mod |= THISMOD|FSDIRMOD;
851 } else
852 mod |= FSERROR;
853 continue;
854 } else {
855 if (ask(1, "Truncate")) {
856 p[28] = p[29] = p[30] = p[31] = 0;
857 p[26] = p[27] = 0;
858 if (boot->ClustMask == CLUST32_MASK)
859 p[20] = p[21] = 0;
860 dirent.size = 0;
861 dirent.head = 0;
862 mod |= THISMOD|FSDIRMOD;
863 } else
864 mod |= FSERROR;
865 }
866 }
867 }
868 if (dirent.flags & ATTR_DIRECTORY) {
869 /*
870 * gather more info for directories
871 */
872 struct dirTodoNode *n;
873
874 if (dirent.size) {
875 pwarn("Directory %s has size != 0\n",
876 fullpath(&dirent));
877 if (ask(1, "Correct")) {
878 p[28] = p[29] = p[30] = p[31] = 0;
879 dirent.size = 0;
880 mod |= THISMOD|FSDIRMOD;
881 } else
882 mod |= FSERROR;
883 }
884 /*
885 * handle `.' and `..' specially
886 */
887 if (strcmp(dirent.name, ".") == 0) {
888 if (dirent.head != dir->head) {
889 pwarn("`.' entry in %s has incorrect start cluster\n",
890 fullpath(dir));
891 if (ask(1, "Correct")) {
892 dirent.head = dir->head;
893 p[26] = (u_char)dirent.head;
894 p[27] = (u_char)(dirent.head >> 8);
895 if (boot->ClustMask == CLUST32_MASK) {
896 p[20] = (u_char)(dirent.head >> 16);
897 p[21] = (u_char)(dirent.head >> 24);
898 }
899 mod |= THISMOD|FSDIRMOD;
900 } else
901 mod |= FSERROR;
902 }
903 continue;
904 } else if (strcmp(dirent.name, "..") == 0) {
905 if (dir->parent) { /* XXX */
906 if (!dir->parent->parent) {
907 if (dirent.head) {
908 pwarn("`..' entry in %s has non-zero start cluster\n",
909 fullpath(dir));
910 if (ask(1, "Correct")) {
911 dirent.head = 0;
912 p[26] = p[27] = 0;
913 if (boot->ClustMask == CLUST32_MASK)
914 p[20] = p[21] = 0;
915 mod |= THISMOD|FSDIRMOD;
916 } else
917 mod |= FSERROR;
918 }
919 } else if (dirent.head != dir->parent->head) {
920 pwarn("`..' entry in %s has incorrect start cluster\n",
921 fullpath(dir));
922 if (ask(1, "Correct")) {
923 dirent.head = dir->parent->head;
924 p[26] = (u_char)dirent.head;
925 p[27] = (u_char)(dirent.head >> 8);
926 if (boot->ClustMask == CLUST32_MASK) {
927 p[20] = (u_char)(dirent.head >> 16);
928 p[21] = (u_char)(dirent.head >> 24);
929 }
930 mod |= THISMOD|FSDIRMOD;
931 } else
932 mod |= FSERROR;
933 }
934 }
935 continue;
936 } else {
937 /*
938 * Only one directory entry can point
939 * to dir->head, it's '.'.
940 */
941 if (dirent.head == dir->head) {
942 pwarn("%s entry in %s has incorrect start cluster\n",
943 dirent.name, fullpath(dir));
944 if (ask(1, "Remove")) {
945 *p = SLOT_DELETED;
946 mod |= THISMOD|FSDIRMOD;
947 } else
948 mod |= FSERROR;
949 continue;
950 } else if ((check_subdirectory(fat,
951 &dirent) & FSERROR) == FSERROR) {
952 /*
953 * A subdirectory should have
954 * a dot (.) entry and a dot-dot
955 * (..) entry of ATTR_DIRECTORY,
956 * we will inspect further when
957 * traversing into it.
958 */
959 if (ask(1, "Remove")) {
960 *p = SLOT_DELETED;
961 mod |= THISMOD|FSDIRMOD;
962 } else
963 mod |= FSERROR;
964 continue;
965 }
966 }
967
968 /* create directory tree node */
969 if (!(d = newDosDirEntry())) {
970 perr("No space for directory");
971 return FSFATAL;
972 }
973 memcpy(d, &dirent, sizeof(struct dosDirEntry));
974 /* link it into the tree */
975 dir->child = d;
976
977 /* Enter this directory into the todo list */
978 if (!(n = newDirTodo())) {
979 perr("No space for todo list");
980 return FSFATAL;
981 }
982 n->next = pendingDirectories;
983 n->dir = d;
984 pendingDirectories = n;
985 } else {
986 mod |= k = checksize(fat, p, &dirent);
987 if (k & FSDIRMOD)
988 mod |= THISMOD;
989 }
990 boot->NumFiles++;
991 }
992
993 if (is_legacyroot) {
994 /*
995 * Don't bother to write back right now because
996 * we may continue to make modification to the
997 * non-FAT32 root directory below.
998 */
999 break;
1000 } else if (mod & THISMOD) {
1001 if (lseek(fd, off, SEEK_SET) != off
1002 || write(fd, buffer, iosize) != iosize) {
1003 perr("Unable to write directory");
1004 return FSFATAL;
1005 }
1006 mod &= ~THISMOD;
1007 }
1008 } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl))));
1009 if (invlfn || vallfn)
1010 mod |= removede(fat,
1011 invlfn ? invlfn : vallfn, p,
1012 invlfn ? invcl : valcl, -1, 0,
1013 fullpath(dir), 1);
1014
1015 /*
1016 * The root directory of non-FAT32 filesystems is in a special
1017 * area and may have been modified above removede() without
1018 * being written out.
1019 */
1020 if ((mod & FSDIRMOD) && is_legacyroot) {
1021 if (lseek(fd, off, SEEK_SET) != off
1022 || write(fd, buffer, iosize) != iosize) {
1023 perr("Unable to write directory");
1024 return FSFATAL;
1025 }
1026 mod &= ~THISMOD;
1027 }
1028 return mod & ~THISMOD;
1029 }
1030
1031 int
handleDirTree(struct fat_descriptor *fat)1032 handleDirTree(struct fat_descriptor *fat)
1033 {
1034 int mod;
1035
1036 mod = readDosDirSection(fat, rootDir);
1037 if (mod & FSFATAL)
1038 return FSFATAL;
1039
1040 /*
1041 * process the directory todo list
1042 */
1043 while (pendingDirectories) {
1044 struct dosDirEntry *dir = pendingDirectories->dir;
1045 struct dirTodoNode *n = pendingDirectories->next;
1046
1047 /*
1048 * remove TODO entry now, the list might change during
1049 * directory reads
1050 */
1051 freeDirTodo(pendingDirectories);
1052 pendingDirectories = n;
1053
1054 /*
1055 * handle subdirectory
1056 */
1057 mod |= readDosDirSection(fat, dir);
1058 if (mod & FSFATAL)
1059 return FSFATAL;
1060 }
1061
1062 return mod;
1063 }
1064
1065 /*
1066 * Try to reconnect a FAT chain into dir
1067 */
1068 static u_char *lfbuf;
1069 static cl_t lfcl;
1070 static off_t lfoff;
1071
1072 int
reconnect(struct fat_descriptor *fat, cl_t head, size_t length)1073 reconnect(struct fat_descriptor *fat, cl_t head, size_t length)
1074 {
1075 struct bootblock *boot = fat_get_boot(fat);
1076 struct dosDirEntry d;
1077 int len, dosfs;
1078 u_char *p;
1079
1080 dosfs = fat_get_fd(fat);
1081
1082 if (!ask(1, "Reconnect"))
1083 return FSERROR;
1084
1085 if (!lostDir) {
1086 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1087 if (!strcmp(lostDir->name, LOSTDIR))
1088 break;
1089 }
1090 if (!lostDir) { /* Create LOSTDIR? XXX */
1091 pwarn("No %s directory\n", LOSTDIR);
1092 return FSERROR;
1093 }
1094 }
1095 if (!lfbuf) {
1096 lfbuf = malloc(boot->ClusterSize);
1097 if (!lfbuf) {
1098 perr("No space for buffer");
1099 return FSFATAL;
1100 }
1101 p = NULL;
1102 } else
1103 p = lfbuf;
1104 while (1) {
1105 if (p)
1106 for (; p < lfbuf + boot->ClusterSize; p += 32)
1107 if (*p == SLOT_EMPTY
1108 || *p == SLOT_DELETED)
1109 break;
1110 if (p && p < lfbuf + boot->ClusterSize)
1111 break;
1112 lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head;
1113 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
1114 /* Extend LOSTDIR? XXX */
1115 pwarn("No space in %s\n", LOSTDIR);
1116 lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
1117 return FSERROR;
1118 }
1119 lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
1120 + boot->FirstCluster * boot->bpbBytesPerSec;
1121
1122 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1123 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1124 perr("could not read LOST.DIR");
1125 return FSFATAL;
1126 }
1127 p = lfbuf;
1128 }
1129
1130 boot->NumFiles++;
1131 /* Ensure uniqueness of entry here! XXX */
1132 memset(&d, 0, sizeof d);
1133 /* worst case -1 = 4294967295, 10 digits */
1134 len = snprintf(d.name, sizeof(d.name), "%u", head);
1135 d.flags = 0;
1136 d.head = head;
1137 d.size = length * boot->ClusterSize;
1138
1139 memcpy(p, d.name, len);
1140 memset(p + len, ' ', 11 - len);
1141 memset(p + 11, 0, 32 - 11);
1142 p[26] = (u_char)d.head;
1143 p[27] = (u_char)(d.head >> 8);
1144 if (boot->ClustMask == CLUST32_MASK) {
1145 p[20] = (u_char)(d.head >> 16);
1146 p[21] = (u_char)(d.head >> 24);
1147 }
1148 p[28] = (u_char)d.size;
1149 p[29] = (u_char)(d.size >> 8);
1150 p[30] = (u_char)(d.size >> 16);
1151 p[31] = (u_char)(d.size >> 24);
1152 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1153 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1154 perr("could not write LOST.DIR");
1155 return FSFATAL;
1156 }
1157 return FSDIRMOD;
1158 }
1159
1160 void
finishlf(void)1161 finishlf(void)
1162 {
1163 if (lfbuf)
1164 free(lfbuf);
1165 lfbuf = NULL;
1166 }
1167