xref: /dragonfly/sbin/fsck_msdosfs/dir.c (revision 1351cfd71b069b3338915f3ac9908051ac283d6f)
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