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