1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 static const char rcsid[] =
36 "$FreeBSD$";
37 #endif /* not lint */
38
39 #include <assert.h>
40 #include <inttypes.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <ctype.h>
45 #include <unistd.h>
46 #include <time.h>
47
48 #include <sys/param.h>
49
50 #include "ext.h"
51 #include "fsutil.h"
52
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 */
56
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 */
64
65 #define ATTR_WIN95 0x0f /* long name record */
66
67 /*
68 * This is the format of the contents of the deTime field in the direntry
69 * structure.
70 * We don't use bitfields because we don't know how compilers for
71 * arbitrary machines will lay them out.
72 */
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
79
80 /*
81 * This is the format of the contents of the deDate field in the direntry
82 * structure.
83 */
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
90
91
92 /* dir.c */
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 *);
104
105 /*
106 * Manage free dosDirEntry structures.
107 */
108 static struct dosDirEntry *freede;
109
110 static struct dosDirEntry *
newDosDirEntry(void)111 newDosDirEntry(void)
112 {
113 struct dosDirEntry *de;
114
115 if (!(de = freede)) {
116 if (!(de = malloc(sizeof *de)))
117 return (NULL);
118 } else
119 freede = de->next;
120 return de;
121 }
122
123 static void
freeDosDirEntry(struct dosDirEntry * de)124 freeDosDirEntry(struct dosDirEntry *de)
125 {
126 de->next = freede;
127 freede = de;
128 }
129
130 /*
131 * The same for dirTodoNode structures.
132 */
133 static struct dirTodoNode *freedt;
134
135 static struct dirTodoNode *
newDirTodo(void)136 newDirTodo(void)
137 {
138 struct dirTodoNode *dt;
139
140 if (!(dt = freedt)) {
141 if (!(dt = malloc(sizeof *dt)))
142 return 0;
143 } else
144 freedt = dt->next;
145 return dt;
146 }
147
148 static void
freeDirTodo(struct dirTodoNode * dt)149 freeDirTodo(struct dirTodoNode *dt)
150 {
151 dt->next = freedt;
152 freedt = dt;
153 }
154
155 /*
156 * The stack of unread directories
157 */
158 static struct dirTodoNode *pendingDirectories = NULL;
159
160 /*
161 * Return the full pathname for a directory entry.
162 */
163 static char *
fullpath(struct dosDirEntry * dir)164 fullpath(struct dosDirEntry *dir)
165 {
166 static char namebuf[MAXPATHLEN + 1];
167 char *cp, *np;
168 int nl;
169
170 cp = namebuf + sizeof namebuf;
171 *--cp = '\0';
172
173 for(;;) {
174 np = dir->lname[0] ? dir->lname : dir->name;
175 nl = strlen(np);
176 if (cp <= namebuf + 1 + nl) {
177 *--cp = '?';
178 break;
179 }
180 cp -= nl;
181 memcpy(cp, np, nl);
182 dir = dir->parent;
183 if (!dir)
184 break;
185 *--cp = '/';
186 }
187
188 return cp;
189 }
190
191 /*
192 * Calculate a checksum over an 8.3 alias name
193 */
194 static inline u_char
calcShortSum(u_char * p)195 calcShortSum(u_char *p)
196 {
197 u_char sum = 0;
198 int i;
199
200 for (i = 0; i < 11; i++) {
201 sum = (sum << 7)|(sum >> 1); /* rotate right */
202 sum += p[i];
203 }
204
205 return sum;
206 }
207
208 /*
209 * Global variables temporarily used during a directory scan
210 */
211 static char longName[DOSLONGNAMELEN] = "";
212 static u_char *buffer = NULL;
213 static u_char *delbuf = NULL;
214
215 static struct dosDirEntry *rootDir;
216 static struct dosDirEntry *lostDir;
217
218 /*
219 * Init internal state for a new directory scan.
220 */
221 int
resetDosDirSection(struct fat_descriptor * fat)222 resetDosDirSection(struct fat_descriptor *fat)
223 {
224 int rootdir_size, cluster_size;
225 int ret = FSOK;
226 size_t len;
227 struct bootblock *boot;
228
229 boot = fat_get_boot(fat);
230
231 rootdir_size = boot->bpbRootDirEnts * 32;
232 cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec;
233
234 if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) {
235 perr("No space for directory buffer (%zu)", len);
236 return FSFATAL;
237 }
238
239 if ((delbuf = malloc(len = cluster_size)) == NULL) {
240 free(buffer);
241 perr("No space for directory delbuf (%zu)", len);
242 return FSFATAL;
243 }
244
245 if ((rootDir = newDosDirEntry()) == NULL) {
246 free(buffer);
247 free(delbuf);
248 perr("No space for directory entry");
249 return FSFATAL;
250 }
251
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");
256 return FSFATAL;
257 }
258 rootDir->head = boot->bpbRootClust;
259 }
260
261 return ret;
262 }
263
264 /*
265 * Cleanup after a directory scan
266 */
267 void
finishDosDirSection(void)268 finishDosDirSection(void)
269 {
270 struct dirTodoNode *p, *np;
271 struct dosDirEntry *d, *nd;
272
273 for (p = pendingDirectories; p; p = np) {
274 np = p->next;
275 freeDirTodo(p);
276 }
277 pendingDirectories = NULL;
278 for (d = rootDir; d; d = nd) {
279 if ((nd = d->child) != NULL) {
280 d->child = 0;
281 continue;
282 }
283 if (!(nd = d->next))
284 nd = d->parent;
285 freeDosDirEntry(d);
286 }
287 rootDir = lostDir = NULL;
288 free(buffer);
289 free(delbuf);
290 buffer = NULL;
291 delbuf = NULL;
292 }
293
294 /*
295 * Delete directory entries between startcl, startoff and endcl, endoff.
296 */
297 static int
delete(struct fat_descriptor * fat,cl_t startcl,int startoff,cl_t endcl,int endoff,int notlast)298 delete(struct fat_descriptor *fat, cl_t startcl,
299 int startoff, cl_t endcl, int endoff, int notlast)
300 {
301 u_char *s, *e;
302 off_t off;
303 int clsz, fd;
304 struct bootblock *boot;
305
306 boot = fat_get_boot(fat);
307 fd = fat_get_fd(fat);
308 clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
309
310 s = delbuf + startoff;
311 e = delbuf + clsz;
312 while (fat_is_valid_cl(fat, startcl)) {
313 if (startcl == endcl) {
314 if (notlast)
315 break;
316 e = delbuf + endoff;
317 }
318 off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
319
320 off *= boot->bpbBytesPerSec;
321 if (lseek(fd, off, SEEK_SET) != off) {
322 perr("Unable to lseek to %" PRId64, off);
323 return FSFATAL;
324 }
325 if (read(fd, delbuf, clsz) != clsz) {
326 perr("Unable to read directory");
327 return FSFATAL;
328 }
329 while (s < e) {
330 *s = SLOT_DELETED;
331 s += 32;
332 }
333 if (lseek(fd, off, SEEK_SET) != off) {
334 perr("Unable to lseek to %" PRId64, off);
335 return FSFATAL;
336 }
337 if (write(fd, delbuf, clsz) != clsz) {
338 perr("Unable to write directory");
339 return FSFATAL;
340 }
341 if (startcl == endcl)
342 break;
343 startcl = fat_get_cl_next(fat, startcl);
344 s = delbuf;
345 }
346 return FSOK;
347 }
348
349 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)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)
353 {
354 switch (type) {
355 case 0:
356 pwarn("Invalid long filename entry for %s\n", path);
357 break;
358 case 1:
359 pwarn("Invalid long filename entry at end of directory %s\n",
360 path);
361 break;
362 case 2:
363 pwarn("Invalid long filename entry for volume label\n");
364 break;
365 }
366 if (ask(0, "Remove")) {
367 if (startcl != curcl) {
368 if (delete(fat,
369 startcl, start - buffer,
370 endcl, end - buffer,
371 endcl == curcl) == FSFATAL)
372 return FSFATAL;
373 start = buffer;
374 }
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;
379 return FSDIRMOD;
380 }
381 return FSERROR;
382 }
383
384 /*
385 * Check an in-memory file entry
386 */
387 static int
checksize(struct fat_descriptor * fat,u_char * p,struct dosDirEntry * dir)388 checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir)
389 {
390 int ret = FSOK;
391 size_t chainsize;
392 u_int64_t physicalSize;
393 struct bootblock *boot;
394
395 boot = fat_get_boot(fat);
396
397 /*
398 * Check size on ordinary files
399 */
400 if (dir->head == CLUST_FREE) {
401 physicalSize = 0;
402 } else {
403 if (!fat_is_valid_cl(fat, dir->head))
404 return FSERROR;
405 ret = checkchain(fat, dir->head, &chainsize);
406 /*
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
411 * case.
412 */
413 if (ret == FSERROR) {
414 return (FSERROR);
415 }
416 /*
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.
421 */
422 physicalSize = (u_int64_t)chainsize * boot->ClusterSize;
423 }
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);
433 return FSDIRMOD;
434 } else
435 return FSERROR;
436 } else if (physicalSize - dir->size >= boot->ClusterSize) {
437 pwarn("%s has too many clusters allocated\n",
438 fullpath(dir));
439 if (ask(1, "Drop superfluous clusters")) {
440 cl_t cl;
441 u_int32_t sz, len;
442
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);
449 } else
450 return FSERROR;
451 }
452 return FSOK;
453 }
454
455 static const u_char dot_name[11] = ". ";
456 static const u_char dotdot_name[11] = ".. ";
457
458 /*
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.
462 */
463 static int
check_subdirectory(struct fat_descriptor * fat,struct dosDirEntry * dir)464 check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)
465 {
466 u_char *buf, *cp;
467 off_t off;
468 cl_t cl;
469 int retval = FSOK;
470 int fd;
471 struct bootblock *boot;
472
473 boot = fat_get_boot(fat);
474 fd = fat_get_fd(fat);
475
476 cl = dir->head;
477 if (dir->parent && !fat_is_valid_cl(fat, cl)) {
478 return FSERROR;
479 }
480
481 if (!(boot->flags & FAT32) && !dir->parent) {
482 off = boot->bpbResSectors + boot->bpbFATs *
483 boot->FATsecs;
484 } else {
485 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
486 }
487
488 /*
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.
492 */
493 buf = malloc(boot->bpbBytesPerSec);
494 if (buf == NULL) {
495 perr("No space for directory buffer (%u)",
496 boot->bpbBytesPerSec);
497 return FSFATAL;
498 }
499
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");
504 free(buf);
505 return FSFATAL;
506 }
507
508 /*
509 * Both `.' and `..' must be present and be the first two entries
510 * and be ATTR_DIRECTORY of a valid subdirectory.
511 */
512 cp = buf;
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);
516 retval |= FSERROR;
517 }
518 cp += 32;
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);
522 retval |= FSERROR;
523 }
524
525 free(buf);
526 return retval;
527 }
528
529 /*
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
534 */
535 static int
readDosDirSection(struct fat_descriptor * fat,struct dosDirEntry * dir)536 readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir)
537 {
538 struct bootblock *boot;
539 struct dosDirEntry dirent, *d;
540 u_char *p, *vallfn, *invlfn, *empty;
541 off_t off;
542 int fd, i, j, k, iosize, entries;
543 bool is_legacyroot;
544 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
545 char *t;
546 u_int lidx = 0;
547 int shortSum;
548 int mod = FSOK;
549 size_t dirclusters;
550 #define THISMOD 0x8000 /* Only used within this routine */
551
552 boot = fat_get_boot(fat);
553 fd = fat_get_fd(fat);
554
555 cl = dir->head;
556 if (dir->parent && (!fat_is_valid_cl(fat, cl))) {
557 /*
558 * Already handled somewhere else.
559 */
560 return FSOK;
561 }
562 shortSum = -1;
563 vallfn = invlfn = empty = NULL;
564
565 /*
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.
570 *
571 * Derive how many entries we are going to encounter from
572 * the I/O size.
573 */
574 is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32));
575 if (is_legacyroot) {
576 iosize = boot->bpbRootDirEnts * 32;
577 entries = boot->bpbRootDirEnts;
578 } else {
579 iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec;
580 entries = iosize / 32;
581 mod |= checkchain(fat, dir->head, &dirclusters);
582 }
583
584 do {
585 if (is_legacyroot) {
586 /*
587 * Special case for FAT12/FAT16 root -- read
588 * in the whole root directory.
589 */
590 off = boot->bpbResSectors + boot->bpbFATs *
591 boot->FATsecs;
592 } else {
593 /*
594 * Otherwise, read in a cluster of the
595 * directory.
596 */
597 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
598 }
599
600 off *= boot->bpbBytesPerSec;
601 if (lseek(fd, off, SEEK_SET) != off ||
602 read(fd, buffer, iosize) != iosize) {
603 perr("Unable to read directory");
604 return FSFATAL;
605 }
606
607 for (p = buffer, i = 0; i < entries; i++, p += 32) {
608 if (dir->fsckflags & DIREMPWARN) {
609 *p = SLOT_EMPTY;
610 continue;
611 }
612
613 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
614 if (*p == SLOT_EMPTY) {
615 dir->fsckflags |= DIREMPTY;
616 empty = p;
617 empcl = cl;
618 }
619 continue;
620 }
621
622 if (dir->fsckflags & DIREMPTY) {
623 if (!(dir->fsckflags & DIREMPWARN)) {
624 pwarn("%s has entries after end of directory\n",
625 fullpath(dir));
626 if (ask(1, "Extend")) {
627 u_char *q;
628
629 dir->fsckflags &= ~DIREMPTY;
630 if (delete(fat,
631 empcl, empty - buffer,
632 cl, p - buffer, 1) == FSFATAL)
633 return FSFATAL;
634 q = ((empcl == cl) ? empty : buffer);
635 assert(q != NULL);
636 for (; q < p; q += 32)
637 *q = SLOT_DELETED;
638 mod |= THISMOD|FSDIRMOD;
639 } else if (ask(0, "Truncate"))
640 dir->fsckflags |= DIREMPWARN;
641 }
642 if (dir->fsckflags & DIREMPWARN) {
643 *p = SLOT_DELETED;
644 mod |= THISMOD|FSDIRMOD;
645 continue;
646 } else if (dir->fsckflags & DIREMPTY)
647 mod |= FSERROR;
648 empty = NULL;
649 }
650
651 if (p[11] == ATTR_WIN95) {
652 if (*p & LRFIRST) {
653 if (shortSum != -1) {
654 if (!invlfn) {
655 invlfn = vallfn;
656 invcl = valcl;
657 }
658 }
659 memset(longName, 0, sizeof longName);
660 shortSum = p[13];
661 vallfn = p;
662 valcl = cl;
663 } else if (shortSum != p[13]
664 || lidx != (*p & LRNOMASK)) {
665 if (!invlfn) {
666 invlfn = vallfn;
667 invcl = valcl;
668 }
669 if (!invlfn) {
670 invlfn = p;
671 invcl = cl;
672 }
673 vallfn = NULL;
674 }
675 lidx = *p & LRNOMASK;
676 if (lidx == 0) {
677 pwarn("invalid long name\n");
678 if (!invlfn) {
679 invlfn = vallfn;
680 invcl = valcl;
681 }
682 vallfn = NULL;
683 continue;
684 }
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])
689 break;
690 *t++ = p[k];
691 /*
692 * Warn about those unusable chars in msdosfs here? XXX
693 */
694 if (p[k + 1])
695 t[-1] = '?';
696 }
697 if (k >= 11)
698 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
699 if (!p[k] && !p[k + 1])
700 break;
701 *t++ = p[k];
702 if (p[k + 1])
703 t[-1] = '?';
704 }
705 if (k >= 26)
706 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
707 if (!p[k] && !p[k + 1])
708 break;
709 *t++ = p[k];
710 if (p[k + 1])
711 t[-1] = '?';
712 }
713 if (t >= longName + sizeof(longName)) {
714 pwarn("long filename too long\n");
715 if (!invlfn) {
716 invlfn = vallfn;
717 invcl = valcl;
718 }
719 vallfn = NULL;
720 }
721 if (p[26] | (p[27] << 8)) {
722 pwarn("long filename record cluster start != 0\n");
723 if (!invlfn) {
724 invlfn = vallfn;
725 invcl = cl;
726 }
727 vallfn = NULL;
728 }
729 continue; /* long records don't carry further
730 * information */
731 }
732
733 /*
734 * This is a standard msdosfs directory entry.
735 */
736 memset(&dirent, 0, sizeof dirent);
737
738 /*
739 * it's a short name record, but we need to know
740 * more, so get the flags first.
741 */
742 dirent.flags = p[11];
743
744 /*
745 * Translate from 850 to ISO here XXX
746 */
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')
753 k++;
754 if (dirent.name[0] == SLOT_E5)
755 dirent.name[0] = 0xe5;
756
757 if (dirent.flags & ATTR_VOLUME) {
758 if (vallfn || invlfn) {
759 mod |= removede(fat,
760 invlfn ? invlfn : vallfn, p,
761 invlfn ? invcl : valcl, -1, 0,
762 fullpath(dir), 2);
763 vallfn = NULL;
764 invlfn = NULL;
765 }
766 continue;
767 }
768
769 if (p[8] != ' ')
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';
776
777 if (vallfn && shortSum != calcShortSum(p)) {
778 if (!invlfn) {
779 invlfn = vallfn;
780 invcl = valcl;
781 }
782 vallfn = NULL;
783 }
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);
788 if (vallfn) {
789 strlcpy(dirent.lname, longName,
790 sizeof(dirent.lname));
791 longName[0] = '\0';
792 shortSum = -1;
793 }
794
795 dirent.parent = dir;
796 dirent.next = dir->child;
797
798 if (invlfn) {
799 mod |= k = removede(fat,
800 invlfn, vallfn ? vallfn : p,
801 invcl, vallfn ? valcl : cl, cl,
802 fullpath(&dirent), 0);
803 if (mod & FSFATAL)
804 return FSFATAL;
805 if (vallfn
806 ? (valcl == cl && vallfn != buffer)
807 : p != buffer)
808 if (k & FSDIRMOD)
809 mod |= THISMOD;
810 }
811
812 vallfn = NULL; /* not used any longer */
813 invlfn = NULL;
814
815 /*
816 * Check if the directory entry is sane.
817 *
818 * '.' and '..' are skipped, their sanity is
819 * checked somewhere else.
820 *
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.
829 *
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.
833 */
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",
842 fullpath(&dirent),
843 dirent.head);
844 } else {
845 pwarn("%s doesn't start a new cluster chain\n",
846 fullpath(&dirent));
847 }
848
849 if (dirent.flags & ATTR_DIRECTORY) {
850 if (ask(0, "Remove")) {
851 *p = SLOT_DELETED;
852 mod |= THISMOD|FSDIRMOD;
853 } else
854 mod |= FSERROR;
855 continue;
856 } else {
857 if (ask(1, "Truncate")) {
858 p[28] = p[29] = p[30] = p[31] = 0;
859 p[26] = p[27] = 0;
860 if (boot->ClustMask == CLUST32_MASK)
861 p[20] = p[21] = 0;
862 dirent.size = 0;
863 dirent.head = 0;
864 mod |= THISMOD|FSDIRMOD;
865 } else
866 mod |= FSERROR;
867 }
868 }
869 }
870 if (dirent.flags & ATTR_DIRECTORY) {
871 /*
872 * gather more info for directories
873 */
874 struct dirTodoNode *n;
875
876 if (dirent.size) {
877 pwarn("Directory %s has size != 0\n",
878 fullpath(&dirent));
879 if (ask(1, "Correct")) {
880 p[28] = p[29] = p[30] = p[31] = 0;
881 dirent.size = 0;
882 mod |= THISMOD|FSDIRMOD;
883 } else
884 mod |= FSERROR;
885 }
886 /*
887 * handle `.' and `..' specially
888 */
889 if (strcmp(dirent.name, ".") == 0) {
890 if (dirent.head != dir->head) {
891 pwarn("`.' entry in %s has incorrect start cluster\n",
892 fullpath(dir));
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);
900 }
901 mod |= THISMOD|FSDIRMOD;
902 } else
903 mod |= FSERROR;
904 }
905 continue;
906 } else if (strcmp(dirent.name, "..") == 0) {
907 if (dir->parent) { /* XXX */
908 if (!dir->parent->parent) {
909 if (dirent.head) {
910 pwarn("`..' entry in %s has non-zero start cluster\n",
911 fullpath(dir));
912 if (ask(1, "Correct")) {
913 dirent.head = 0;
914 p[26] = p[27] = 0;
915 if (boot->ClustMask == CLUST32_MASK)
916 p[20] = p[21] = 0;
917 mod |= THISMOD|FSDIRMOD;
918 } else
919 mod |= FSERROR;
920 }
921 } else if (dirent.head != dir->parent->head) {
922 pwarn("`..' entry in %s has incorrect start cluster\n",
923 fullpath(dir));
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);
931 }
932 mod |= THISMOD|FSDIRMOD;
933 } else
934 mod |= FSERROR;
935 }
936 }
937 continue;
938 } else {
939 /*
940 * Only one directory entry can point
941 * to dir->head, it's '.'.
942 */
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")) {
947 *p = SLOT_DELETED;
948 mod |= THISMOD|FSDIRMOD;
949 } else
950 mod |= FSERROR;
951 continue;
952 } else if ((check_subdirectory(fat,
953 &dirent) & FSERROR) == FSERROR) {
954 /*
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.
960 */
961 if (ask(1, "Remove")) {
962 *p = SLOT_DELETED;
963 mod |= THISMOD|FSDIRMOD;
964 } else
965 mod |= FSERROR;
966 continue;
967 }
968 }
969
970 /* create directory tree node */
971 if (!(d = newDosDirEntry())) {
972 perr("No space for directory");
973 return FSFATAL;
974 }
975 memcpy(d, &dirent, sizeof(struct dosDirEntry));
976 /* link it into the tree */
977 dir->child = d;
978
979 /* Enter this directory into the todo list */
980 if (!(n = newDirTodo())) {
981 perr("No space for todo list");
982 return FSFATAL;
983 }
984 n->next = pendingDirectories;
985 n->dir = d;
986 pendingDirectories = n;
987 } else {
988 mod |= k = checksize(fat, p, &dirent);
989 if (k & FSDIRMOD)
990 mod |= THISMOD;
991 }
992 boot->NumFiles++;
993 }
994
995 if (is_legacyroot) {
996 /*
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.
1000 */
1001 break;
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");
1006 return FSFATAL;
1007 }
1008 mod &= ~THISMOD;
1009 }
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,
1015 fullpath(dir), 1);
1016
1017 /*
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.
1021 */
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");
1026 return FSFATAL;
1027 }
1028 mod &= ~THISMOD;
1029 }
1030 return mod & ~THISMOD;
1031 }
1032
1033 int
handleDirTree(struct fat_descriptor * fat)1034 handleDirTree(struct fat_descriptor *fat)
1035 {
1036 int mod;
1037
1038 mod = readDosDirSection(fat, rootDir);
1039 if (mod & FSFATAL)
1040 return FSFATAL;
1041
1042 /*
1043 * process the directory todo list
1044 */
1045 while (pendingDirectories) {
1046 struct dosDirEntry *dir = pendingDirectories->dir;
1047 struct dirTodoNode *n = pendingDirectories->next;
1048
1049 /*
1050 * remove TODO entry now, the list might change during
1051 * directory reads
1052 */
1053 freeDirTodo(pendingDirectories);
1054 pendingDirectories = n;
1055
1056 /*
1057 * handle subdirectory
1058 */
1059 mod |= readDosDirSection(fat, dir);
1060 if (mod & FSFATAL)
1061 return FSFATAL;
1062 }
1063
1064 return mod;
1065 }
1066
1067 /*
1068 * Try to reconnect a FAT chain into dir
1069 */
1070 static u_char *lfbuf;
1071 static cl_t lfcl;
1072 static off_t lfoff;
1073
1074 int
reconnect(struct fat_descriptor * fat,cl_t head,size_t length)1075 reconnect(struct fat_descriptor *fat, cl_t head, size_t length)
1076 {
1077 struct bootblock *boot = fat_get_boot(fat);
1078 struct dosDirEntry d;
1079 int len, dosfs;
1080 u_char *p;
1081
1082 dosfs = fat_get_fd(fat);
1083
1084 if (!ask(1, "Reconnect"))
1085 return FSERROR;
1086
1087 if (!lostDir) {
1088 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1089 if (!strcmp(lostDir->name, LOSTDIR))
1090 break;
1091 }
1092 if (!lostDir) { /* Create LOSTDIR? XXX */
1093 pwarn("No %s directory\n", LOSTDIR);
1094 return FSERROR;
1095 }
1096 }
1097 if (!lfbuf) {
1098 lfbuf = malloc(boot->ClusterSize);
1099 if (!lfbuf) {
1100 perr("No space for buffer");
1101 return FSFATAL;
1102 }
1103 p = NULL;
1104 } else
1105 p = lfbuf;
1106 while (1) {
1107 if (p)
1108 for (; p < lfbuf + boot->ClusterSize; p += 32)
1109 if (*p == SLOT_EMPTY
1110 || *p == SLOT_DELETED)
1111 break;
1112 if (p && p < lfbuf + boot->ClusterSize)
1113 break;
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;
1119 return FSERROR;
1120 }
1121 lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
1122 + boot->FirstCluster * boot->bpbBytesPerSec;
1123
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");
1127 return FSFATAL;
1128 }
1129 p = lfbuf;
1130 }
1131
1132 boot->NumFiles++;
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);
1137 d.flags = 0;
1138 d.head = head;
1139 d.size = length * boot->ClusterSize;
1140
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);
1149 }
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");
1157 return FSFATAL;
1158 }
1159 return FSDIRMOD;
1160 }
1161
1162 void
finishlf(void)1163 finishlf(void)
1164 {
1165 if (lfbuf)
1166 free(lfbuf);
1167 lfbuf = NULL;
1168 }
1169