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) || !fat_is_cl_head(fat, dir->head)) {
402 pwarn("Directory entry %s of size %u referencing invalid cluster %u\n",
403 fullpath(dir), dir->size, dir->head);
404 if (ask(1, "Truncate")) {
405 p[28] = p[29] = p[30] = p[31] = 0;
406 p[26] = p[27] = 0;
407 if (boot->ClustMask == CLUST32_MASK)
408 p[20] = p[21] = 0;
409 dir->size = 0;
410 dir->head = CLUST_FREE;
411 return FSDIRMOD;
412 } else {
413 return FSERROR;
414 }
415 }
416 ret = checkchain(fat, dir->head, &chainsize);
417 /*
418 * Upon return, chainsize would hold the chain length
419 * that checkchain() was able to validate, but if the user
420 * refused the proposed repair, it would be unsafe to
421 * proceed with directory entry fix, so bail out in that
422 * case.
423 */
424 if (ret == FSERROR) {
425 return (FSERROR);
426 }
427 /*
428 * The maximum file size on FAT32 is 4GiB - 1, which
429 * will occupy a cluster chain of exactly 4GiB in
430 * size. On 32-bit platforms, since size_t is 32-bit,
431 * it would wrap back to 0.
432 */
433 physicalSize = (u_int64_t)chainsize * boot->ClusterSize;
434 }
435 if (physicalSize < dir->size) {
436 pwarn("size of %s is %u, should at most be %ju\n",
437 fullpath(dir), dir->size, (uintmax_t)physicalSize);
438 if (ask(1, "Truncate")) {
439 dir->size = physicalSize;
440 p[28] = (u_char)physicalSize;
441 p[29] = (u_char)(physicalSize >> 8);
442 p[30] = (u_char)(physicalSize >> 16);
443 p[31] = (u_char)(physicalSize >> 24);
444 return FSDIRMOD;
445 } else
446 return FSERROR;
447 } else if (physicalSize - dir->size >= boot->ClusterSize) {
448 pwarn("%s has too many clusters allocated\n",
449 fullpath(dir));
450 if (ask(1, "Drop superfluous clusters")) {
451 cl_t cl;
452 u_int32_t sz, len;
453
454 for (cl = dir->head, len = sz = 0;
455 (sz += boot->ClusterSize) < dir->size; len++)
456 cl = fat_get_cl_next(fat, cl);
457 clearchain(fat, fat_get_cl_next(fat, cl));
458 ret = fat_set_cl_next(fat, cl, CLUST_EOF);
459 return (FSFATMOD | ret);
460 } else
461 return FSERROR;
462 }
463 return FSOK;
464 }
465
466 static const u_char dot_name[11] = ". ";
467 static const u_char dotdot_name[11] = ".. ";
468
469 /*
470 * Basic sanity check if the subdirectory have good '.' and '..' entries,
471 * and they are directory entries. Further sanity checks are performed
472 * when we traverse into it.
473 */
474 static int
check_subdirectory(struct fat_descriptor * fat,struct dosDirEntry * dir)475 check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)
476 {
477 u_char *buf, *cp;
478 off_t off;
479 cl_t cl;
480 int retval = FSOK;
481 int fd;
482 struct bootblock *boot;
483
484 boot = fat_get_boot(fat);
485 fd = fat_get_fd(fat);
486
487 cl = dir->head;
488 if (dir->parent && !fat_is_valid_cl(fat, cl)) {
489 return FSERROR;
490 }
491
492 if (!(boot->flags & FAT32) && !dir->parent) {
493 off = boot->bpbResSectors + boot->bpbFATs *
494 boot->FATsecs;
495 } else {
496 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
497 }
498
499 /*
500 * We only need to check the first two entries of the directory,
501 * which is found in the first sector of the directory entry,
502 * so read in only the first sector.
503 */
504 buf = malloc(boot->bpbBytesPerSec);
505 if (buf == NULL) {
506 perr("No space for directory buffer (%u)",
507 boot->bpbBytesPerSec);
508 return FSFATAL;
509 }
510
511 off *= boot->bpbBytesPerSec;
512 if (lseek(fd, off, SEEK_SET) != off ||
513 read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) {
514 perr("Unable to read directory");
515 free(buf);
516 return FSFATAL;
517 }
518
519 /*
520 * Both `.' and `..' must be present and be the first two entries
521 * and be ATTR_DIRECTORY of a valid subdirectory.
522 */
523 cp = buf;
524 if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
525 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
526 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
527 retval |= FSERROR;
528 }
529 cp += 32;
530 if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
531 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
532 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
533 retval |= FSERROR;
534 }
535
536 free(buf);
537 return retval;
538 }
539
540 /*
541 * Read a directory and
542 * - resolve long name records
543 * - enter file and directory records into the parent's list
544 * - push directories onto the todo-stack
545 */
546 static int
readDosDirSection(struct fat_descriptor * fat,struct dosDirEntry * dir)547 readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir)
548 {
549 struct bootblock *boot;
550 struct dosDirEntry dirent, *d;
551 u_char *p, *vallfn, *invlfn, *empty;
552 off_t off;
553 int fd, i, j, k, iosize, entries;
554 bool is_legacyroot;
555 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
556 char *t;
557 u_int lidx = 0;
558 int shortSum;
559 int mod = FSOK;
560 size_t dirclusters;
561 #define THISMOD 0x8000 /* Only used within this routine */
562
563 boot = fat_get_boot(fat);
564 fd = fat_get_fd(fat);
565
566 cl = dir->head;
567 if (dir->parent && (!fat_is_valid_cl(fat, cl))) {
568 /*
569 * Already handled somewhere else.
570 */
571 return FSOK;
572 }
573 shortSum = -1;
574 vallfn = invlfn = empty = NULL;
575
576 /*
577 * If we are checking the legacy root (for FAT12/FAT16),
578 * we will operate on the whole directory; otherwise, we
579 * will operate on one cluster at a time, and also take
580 * this opportunity to examine the chain.
581 *
582 * Derive how many entries we are going to encounter from
583 * the I/O size.
584 */
585 is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32));
586 if (is_legacyroot) {
587 iosize = boot->bpbRootDirEnts * 32;
588 entries = boot->bpbRootDirEnts;
589 } else {
590 iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec;
591 entries = iosize / 32;
592 mod |= checkchain(fat, dir->head, &dirclusters);
593 }
594
595 do {
596 if (is_legacyroot) {
597 /*
598 * Special case for FAT12/FAT16 root -- read
599 * in the whole root directory.
600 */
601 off = boot->bpbResSectors + boot->bpbFATs *
602 boot->FATsecs;
603 } else {
604 /*
605 * Otherwise, read in a cluster of the
606 * directory.
607 */
608 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
609 }
610
611 off *= boot->bpbBytesPerSec;
612 if (lseek(fd, off, SEEK_SET) != off ||
613 read(fd, buffer, iosize) != iosize) {
614 perr("Unable to read directory");
615 return FSFATAL;
616 }
617
618 for (p = buffer, i = 0; i < entries; i++, p += 32) {
619 if (dir->fsckflags & DIREMPWARN) {
620 *p = SLOT_EMPTY;
621 continue;
622 }
623
624 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
625 if (*p == SLOT_EMPTY) {
626 dir->fsckflags |= DIREMPTY;
627 empty = p;
628 empcl = cl;
629 }
630 continue;
631 }
632
633 if (dir->fsckflags & DIREMPTY) {
634 if (!(dir->fsckflags & DIREMPWARN)) {
635 pwarn("%s has entries after end of directory\n",
636 fullpath(dir));
637 if (ask(1, "Extend")) {
638 u_char *q;
639
640 dir->fsckflags &= ~DIREMPTY;
641 if (delete(fat,
642 empcl, empty - buffer,
643 cl, p - buffer, 1) == FSFATAL)
644 return FSFATAL;
645 q = ((empcl == cl) ? empty : buffer);
646 assert(q != NULL);
647 for (; q < p; q += 32)
648 *q = SLOT_DELETED;
649 mod |= THISMOD|FSDIRMOD;
650 } else if (ask(0, "Truncate"))
651 dir->fsckflags |= DIREMPWARN;
652 }
653 if (dir->fsckflags & DIREMPWARN) {
654 *p = SLOT_DELETED;
655 mod |= THISMOD|FSDIRMOD;
656 continue;
657 } else if (dir->fsckflags & DIREMPTY)
658 mod |= FSERROR;
659 empty = NULL;
660 }
661
662 if (p[11] == ATTR_WIN95) {
663 if (*p & LRFIRST) {
664 if (shortSum != -1) {
665 if (!invlfn) {
666 invlfn = vallfn;
667 invcl = valcl;
668 }
669 }
670 memset(longName, 0, sizeof longName);
671 shortSum = p[13];
672 vallfn = p;
673 valcl = cl;
674 } else if (shortSum != p[13]
675 || lidx != (*p & LRNOMASK)) {
676 if (!invlfn) {
677 invlfn = vallfn;
678 invcl = valcl;
679 }
680 if (!invlfn) {
681 invlfn = p;
682 invcl = cl;
683 }
684 vallfn = NULL;
685 }
686 lidx = *p & LRNOMASK;
687 if (lidx == 0) {
688 pwarn("invalid long name\n");
689 if (!invlfn) {
690 invlfn = vallfn;
691 invcl = valcl;
692 }
693 vallfn = NULL;
694 continue;
695 }
696 t = longName + --lidx * 13;
697 for (k = 1; k < 11 && t < longName +
698 sizeof(longName); k += 2) {
699 if (!p[k] && !p[k + 1])
700 break;
701 *t++ = p[k];
702 /*
703 * Warn about those unusable chars in msdosfs here? XXX
704 */
705 if (p[k + 1])
706 t[-1] = '?';
707 }
708 if (k >= 11)
709 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
710 if (!p[k] && !p[k + 1])
711 break;
712 *t++ = p[k];
713 if (p[k + 1])
714 t[-1] = '?';
715 }
716 if (k >= 26)
717 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
718 if (!p[k] && !p[k + 1])
719 break;
720 *t++ = p[k];
721 if (p[k + 1])
722 t[-1] = '?';
723 }
724 if (t >= longName + sizeof(longName)) {
725 pwarn("long filename too long\n");
726 if (!invlfn) {
727 invlfn = vallfn;
728 invcl = valcl;
729 }
730 vallfn = NULL;
731 }
732 if (p[26] | (p[27] << 8)) {
733 pwarn("long filename record cluster start != 0\n");
734 if (!invlfn) {
735 invlfn = vallfn;
736 invcl = cl;
737 }
738 vallfn = NULL;
739 }
740 continue; /* long records don't carry further
741 * information */
742 }
743
744 /*
745 * This is a standard msdosfs directory entry.
746 */
747 memset(&dirent, 0, sizeof dirent);
748
749 /*
750 * it's a short name record, but we need to know
751 * more, so get the flags first.
752 */
753 dirent.flags = p[11];
754
755 /*
756 * Translate from 850 to ISO here XXX
757 */
758 for (j = 0; j < 8; j++)
759 dirent.name[j] = p[j];
760 dirent.name[8] = '\0';
761 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
762 dirent.name[k] = '\0';
763 if (k < 0 || dirent.name[k] != '\0')
764 k++;
765 if (dirent.name[0] == SLOT_E5)
766 dirent.name[0] = 0xe5;
767
768 if (dirent.flags & ATTR_VOLUME) {
769 if (vallfn || invlfn) {
770 mod |= removede(fat,
771 invlfn ? invlfn : vallfn, p,
772 invlfn ? invcl : valcl, -1, 0,
773 fullpath(dir), 2);
774 vallfn = NULL;
775 invlfn = NULL;
776 }
777 continue;
778 }
779
780 if (p[8] != ' ')
781 dirent.name[k++] = '.';
782 for (j = 0; j < 3; j++)
783 dirent.name[k++] = p[j+8];
784 dirent.name[k] = '\0';
785 for (k--; k >= 0 && dirent.name[k] == ' '; k--)
786 dirent.name[k] = '\0';
787
788 if (vallfn && shortSum != calcShortSum(p)) {
789 if (!invlfn) {
790 invlfn = vallfn;
791 invcl = valcl;
792 }
793 vallfn = NULL;
794 }
795 dirent.head = p[26] | (p[27] << 8);
796 if (boot->ClustMask == CLUST32_MASK)
797 dirent.head |= (p[20] << 16) | (p[21] << 24);
798 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
799 if (vallfn) {
800 strlcpy(dirent.lname, longName,
801 sizeof(dirent.lname));
802 longName[0] = '\0';
803 shortSum = -1;
804 }
805
806 dirent.parent = dir;
807 dirent.next = dir->child;
808
809 if (invlfn) {
810 mod |= k = removede(fat,
811 invlfn, vallfn ? vallfn : p,
812 invcl, vallfn ? valcl : cl, cl,
813 fullpath(&dirent), 0);
814 if (mod & FSFATAL)
815 return FSFATAL;
816 if (vallfn
817 ? (valcl == cl && vallfn != buffer)
818 : p != buffer)
819 if (k & FSDIRMOD)
820 mod |= THISMOD;
821 }
822
823 vallfn = NULL; /* not used any longer */
824 invlfn = NULL;
825
826 /*
827 * Check if the directory entry is sane.
828 *
829 * '.' and '..' are skipped, their sanity is
830 * checked somewhere else.
831 *
832 * For everything else, check if we have a new,
833 * valid cluster chain (beginning of a file or
834 * directory that was never previously claimed
835 * by another file) when it's a non-empty file
836 * or a directory. The sanity of the cluster
837 * chain is checked at a later time when we
838 * traverse into the directory, or examine the
839 * file's directory entry.
840 *
841 * The only possible fix is to delete the entry
842 * if it's a directory; for file, we have to
843 * truncate the size to 0.
844 */
845 if (!(dirent.flags & ATTR_DIRECTORY) ||
846 (strcmp(dirent.name, ".") != 0 &&
847 strcmp(dirent.name, "..") != 0)) {
848 if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) &&
849 ((!fat_is_valid_cl(fat, dirent.head) ||
850 !fat_is_cl_head(fat, dirent.head)))) {
851 if (!fat_is_valid_cl(fat, dirent.head)) {
852 pwarn("%s starts with cluster out of range(%u)\n",
853 fullpath(&dirent),
854 dirent.head);
855 } else {
856 pwarn("%s doesn't start a new cluster chain\n",
857 fullpath(&dirent));
858 }
859
860 if (dirent.flags & ATTR_DIRECTORY) {
861 if (ask(0, "Remove")) {
862 *p = SLOT_DELETED;
863 mod |= THISMOD|FSDIRMOD;
864 } else
865 mod |= FSERROR;
866 continue;
867 } else {
868 if (ask(1, "Truncate")) {
869 p[28] = p[29] = p[30] = p[31] = 0;
870 p[26] = p[27] = 0;
871 if (boot->ClustMask == CLUST32_MASK)
872 p[20] = p[21] = 0;
873 dirent.size = 0;
874 dirent.head = 0;
875 mod |= THISMOD|FSDIRMOD;
876 } else
877 mod |= FSERROR;
878 }
879 }
880 }
881 if (dirent.flags & ATTR_DIRECTORY) {
882 /*
883 * gather more info for directories
884 */
885 struct dirTodoNode *n;
886
887 if (dirent.size) {
888 pwarn("Directory %s has size != 0\n",
889 fullpath(&dirent));
890 if (ask(1, "Correct")) {
891 p[28] = p[29] = p[30] = p[31] = 0;
892 dirent.size = 0;
893 mod |= THISMOD|FSDIRMOD;
894 } else
895 mod |= FSERROR;
896 }
897 /*
898 * handle `.' and `..' specially
899 */
900 if (strcmp(dirent.name, ".") == 0) {
901 if (dirent.head != dir->head) {
902 pwarn("`.' entry in %s has incorrect start cluster\n",
903 fullpath(dir));
904 if (ask(1, "Correct")) {
905 dirent.head = dir->head;
906 p[26] = (u_char)dirent.head;
907 p[27] = (u_char)(dirent.head >> 8);
908 if (boot->ClustMask == CLUST32_MASK) {
909 p[20] = (u_char)(dirent.head >> 16);
910 p[21] = (u_char)(dirent.head >> 24);
911 }
912 mod |= THISMOD|FSDIRMOD;
913 } else
914 mod |= FSERROR;
915 }
916 continue;
917 } else if (strcmp(dirent.name, "..") == 0) {
918 if (dir->parent) { /* XXX */
919 if (!dir->parent->parent) {
920 if (dirent.head) {
921 pwarn("`..' entry in %s has non-zero start cluster\n",
922 fullpath(dir));
923 if (ask(1, "Correct")) {
924 dirent.head = 0;
925 p[26] = p[27] = 0;
926 if (boot->ClustMask == CLUST32_MASK)
927 p[20] = p[21] = 0;
928 mod |= THISMOD|FSDIRMOD;
929 } else
930 mod |= FSERROR;
931 }
932 } else if (dirent.head != dir->parent->head) {
933 pwarn("`..' entry in %s has incorrect start cluster\n",
934 fullpath(dir));
935 if (ask(1, "Correct")) {
936 dirent.head = dir->parent->head;
937 p[26] = (u_char)dirent.head;
938 p[27] = (u_char)(dirent.head >> 8);
939 if (boot->ClustMask == CLUST32_MASK) {
940 p[20] = (u_char)(dirent.head >> 16);
941 p[21] = (u_char)(dirent.head >> 24);
942 }
943 mod |= THISMOD|FSDIRMOD;
944 } else
945 mod |= FSERROR;
946 }
947 }
948 continue;
949 } else {
950 /*
951 * Only one directory entry can point
952 * to dir->head, it's '.'.
953 */
954 if (dirent.head == dir->head) {
955 pwarn("%s entry in %s has incorrect start cluster\n",
956 dirent.name, fullpath(dir));
957 if (ask(1, "Remove")) {
958 *p = SLOT_DELETED;
959 mod |= THISMOD|FSDIRMOD;
960 } else
961 mod |= FSERROR;
962 continue;
963 } else if ((check_subdirectory(fat,
964 &dirent) & FSERROR) == FSERROR) {
965 /*
966 * A subdirectory should have
967 * a dot (.) entry and a dot-dot
968 * (..) entry of ATTR_DIRECTORY,
969 * we will inspect further when
970 * traversing into it.
971 */
972 if (ask(1, "Remove")) {
973 *p = SLOT_DELETED;
974 mod |= THISMOD|FSDIRMOD;
975 } else
976 mod |= FSERROR;
977 continue;
978 }
979 }
980
981 /* create directory tree node */
982 if (!(d = newDosDirEntry())) {
983 perr("No space for directory");
984 return FSFATAL;
985 }
986 memcpy(d, &dirent, sizeof(struct dosDirEntry));
987 /* link it into the tree */
988 dir->child = d;
989
990 /* Enter this directory into the todo list */
991 if (!(n = newDirTodo())) {
992 perr("No space for todo list");
993 return FSFATAL;
994 }
995 n->next = pendingDirectories;
996 n->dir = d;
997 pendingDirectories = n;
998 } else if (!(mod & FSERROR)) {
999 mod |= k = checksize(fat, p, &dirent);
1000 if (k & FSDIRMOD)
1001 mod |= THISMOD;
1002 }
1003 boot->NumFiles++;
1004 }
1005
1006 if (is_legacyroot) {
1007 /*
1008 * Don't bother to write back right now because
1009 * we may continue to make modification to the
1010 * non-FAT32 root directory below.
1011 */
1012 break;
1013 } else if (mod & THISMOD) {
1014 if (lseek(fd, off, SEEK_SET) != off
1015 || write(fd, buffer, iosize) != iosize) {
1016 perr("Unable to write directory");
1017 return FSFATAL;
1018 }
1019 mod &= ~THISMOD;
1020 }
1021 } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl))));
1022 if (invlfn || vallfn)
1023 mod |= removede(fat,
1024 invlfn ? invlfn : vallfn, p,
1025 invlfn ? invcl : valcl, -1, 0,
1026 fullpath(dir), 1);
1027
1028 /*
1029 * The root directory of non-FAT32 filesystems is in a special
1030 * area and may have been modified above removede() without
1031 * being written out.
1032 */
1033 if ((mod & FSDIRMOD) && is_legacyroot) {
1034 if (lseek(fd, off, SEEK_SET) != off
1035 || write(fd, buffer, iosize) != iosize) {
1036 perr("Unable to write directory");
1037 return FSFATAL;
1038 }
1039 mod &= ~THISMOD;
1040 }
1041 return mod & ~THISMOD;
1042 }
1043
1044 int
handleDirTree(struct fat_descriptor * fat)1045 handleDirTree(struct fat_descriptor *fat)
1046 {
1047 int mod;
1048
1049 mod = readDosDirSection(fat, rootDir);
1050 if (mod & FSFATAL)
1051 return FSFATAL;
1052
1053 /*
1054 * process the directory todo list
1055 */
1056 while (pendingDirectories) {
1057 struct dosDirEntry *dir = pendingDirectories->dir;
1058 struct dirTodoNode *n = pendingDirectories->next;
1059
1060 /*
1061 * remove TODO entry now, the list might change during
1062 * directory reads
1063 */
1064 freeDirTodo(pendingDirectories);
1065 pendingDirectories = n;
1066
1067 /*
1068 * handle subdirectory
1069 */
1070 mod |= readDosDirSection(fat, dir);
1071 if (mod & FSFATAL)
1072 return FSFATAL;
1073 }
1074
1075 return mod;
1076 }
1077
1078 /*
1079 * Try to reconnect a FAT chain into dir
1080 */
1081 static u_char *lfbuf;
1082 static cl_t lfcl;
1083 static off_t lfoff;
1084
1085 int
reconnect(struct fat_descriptor * fat,cl_t head,size_t length)1086 reconnect(struct fat_descriptor *fat, cl_t head, size_t length)
1087 {
1088 struct bootblock *boot = fat_get_boot(fat);
1089 struct dosDirEntry d;
1090 int len, dosfs;
1091 u_char *p;
1092
1093 dosfs = fat_get_fd(fat);
1094
1095 if (!ask(1, "Reconnect"))
1096 return FSERROR;
1097
1098 if (!lostDir) {
1099 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1100 if (!strcmp(lostDir->name, LOSTDIR))
1101 break;
1102 }
1103 if (!lostDir) { /* Create LOSTDIR? XXX */
1104 pwarn("No %s directory\n", LOSTDIR);
1105 return FSERROR;
1106 }
1107 }
1108 if (!lfbuf) {
1109 lfbuf = malloc(boot->ClusterSize);
1110 if (!lfbuf) {
1111 perr("No space for buffer");
1112 return FSFATAL;
1113 }
1114 p = NULL;
1115 } else
1116 p = lfbuf;
1117 while (1) {
1118 if (p)
1119 for (; p < lfbuf + boot->ClusterSize; p += 32)
1120 if (*p == SLOT_EMPTY
1121 || *p == SLOT_DELETED)
1122 break;
1123 if (p && p < lfbuf + boot->ClusterSize)
1124 break;
1125 lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head;
1126 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
1127 /* Extend LOSTDIR? XXX */
1128 pwarn("No space in %s\n", LOSTDIR);
1129 lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
1130 return FSERROR;
1131 }
1132 lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
1133 + boot->FirstCluster * boot->bpbBytesPerSec;
1134
1135 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1136 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1137 perr("could not read LOST.DIR");
1138 return FSFATAL;
1139 }
1140 p = lfbuf;
1141 }
1142
1143 boot->NumFiles++;
1144 /* Ensure uniqueness of entry here! XXX */
1145 memset(&d, 0, sizeof d);
1146 /* worst case -1 = 4294967295, 10 digits */
1147 len = snprintf(d.name, sizeof(d.name), "%u", head);
1148 d.flags = 0;
1149 d.head = head;
1150 d.size = length * boot->ClusterSize;
1151
1152 memcpy(p, d.name, len);
1153 memset(p + len, ' ', 11 - len);
1154 memset(p + 11, 0, 32 - 11);
1155 p[26] = (u_char)d.head;
1156 p[27] = (u_char)(d.head >> 8);
1157 if (boot->ClustMask == CLUST32_MASK) {
1158 p[20] = (u_char)(d.head >> 16);
1159 p[21] = (u_char)(d.head >> 24);
1160 }
1161 p[28] = (u_char)d.size;
1162 p[29] = (u_char)(d.size >> 8);
1163 p[30] = (u_char)(d.size >> 16);
1164 p[31] = (u_char)(d.size >> 24);
1165 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1166 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1167 perr("could not write LOST.DIR");
1168 return FSFATAL;
1169 }
1170 return FSDIRMOD;
1171 }
1172
1173 void
finishlf(void)1174 finishlf(void)
1175 {
1176 if (lfbuf)
1177 free(lfbuf);
1178 lfbuf = NULL;
1179 }
1180