1 /*        $NetBSD: dir.c,v 1.29 2017/04/28 11:33:00 christos Exp $    */
2 
3 /*
4  * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
5  * Copyright (c) 1995 Martin Husemann
6  * Some structure declaration borrowed from Paul Popelka
7  * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 
31 #include <sys/cdefs.h>
32 #ifndef lint
33 __RCSID("$NetBSD: dir.c,v 1.29 2017/04/28 11:33:00 christos Exp $");
34 #endif /* not lint */
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <unistd.h>
41 #include <assert.h>
42 #include <time.h>
43 
44 #include <sys/param.h>
45 
46 #include "ext.h"
47 #include "fsutil.h"
48 
49 #define   SLOT_EMPTY          0x00                /* slot has never been used */
50 #define   SLOT_E5             0x05                /* the real value is 0xe5 */
51 #define   SLOT_DELETED        0xe5                /* file in this slot deleted */
52 
53 #define   ATTR_NORMAL         0x00                /* normal file */
54 #define   ATTR_READONLY       0x01                /* file is readonly */
55 #define   ATTR_HIDDEN         0x02                /* file is hidden */
56 #define   ATTR_SYSTEM         0x04                /* file is a system file */
57 #define   ATTR_VOLUME         0x08                /* entry is a volume label */
58 #define   ATTR_DIRECTORY      0x10                /* entry is a directory name */
59 #define   ATTR_ARCHIVE        0x20                /* file is new or modified */
60 
61 #define   ATTR_WIN95          0x0f                /* long name record */
62 
63 /*
64  * This is the format of the contents of the deTime field in the direntry
65  * structure.
66  * We don't use bitfields because we don't know how compilers for
67  * arbitrary machines will lay them out.
68  */
69 #define DT_2SECONDS_MASK      0x1F      /* seconds divided by 2 */
70 #define DT_2SECONDS_SHIFT     0
71 #define DT_MINUTES_MASK                 0x7E0     /* minutes */
72 #define DT_MINUTES_SHIFT      5
73 #define DT_HOURS_MASK                   0xF800    /* hours */
74 #define DT_HOURS_SHIFT                  11
75 
76 /*
77  * This is the format of the contents of the deDate field in the direntry
78  * structure.
79  */
80 #define DD_DAY_MASK           0x1F      /* day of month */
81 #define DD_DAY_SHIFT                    0
82 #define DD_MONTH_MASK                   0x1E0     /* month */
83 #define DD_MONTH_SHIFT                  5
84 #define DD_YEAR_MASK                    0xFE00    /* year - 1980 */
85 #define DD_YEAR_SHIFT                   9
86 
87 
88 /* dir.c */
89 static struct dosDirEntry *newDosDirEntry(void);
90 static void freeDosDirEntry(struct dosDirEntry *);
91 static struct dirTodoNode *newDirTodo(void);
92 static void freeDirTodo(struct dirTodoNode *);
93 static char *fullpath(struct dosDirEntry *);
94 static u_char calcShortSum(u_char *);
95 static int delete(int, struct bootblock *, struct fatEntry *, cl_t, int,
96     cl_t, int, int);
97 static int removede(int, struct bootblock *, struct fatEntry *, u_char *,
98     u_char *, cl_t, cl_t, cl_t, char *, int);
99 static int checksize(struct bootblock *, struct fatEntry *, u_char *,
100     struct dosDirEntry *);
101 static int readDosDirSection(int, struct bootblock *, struct fatEntry *,
102     struct dosDirEntry *);
103 
104 /*
105  * Manage free dosDirEntry structures.
106  */
107 static struct dosDirEntry *freede;
108 
109 static struct dosDirEntry *
newDosDirEntry(void)110 newDosDirEntry(void)
111 {
112           struct dosDirEntry *de;
113 
114           if (!(de = freede)) {
115                     if (!(de = (struct dosDirEntry *)malloc(sizeof *de)))
116                               return 0;
117           } else
118                     freede = de->next;
119           return de;
120 }
121 
122 static void
freeDosDirEntry(struct dosDirEntry * de)123 freeDosDirEntry(struct dosDirEntry *de)
124 {
125           de->next = freede;
126           freede = de;
127 }
128 
129 /*
130  * The same for dirTodoNode structures.
131  */
132 static struct dirTodoNode *freedt;
133 
134 static struct dirTodoNode *
newDirTodo(void)135 newDirTodo(void)
136 {
137           struct dirTodoNode *dt;
138 
139           if (!(dt = freedt)) {
140                     if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt)))
141                               return 0;
142           } else
143                     freedt = dt->next;
144           return dt;
145 }
146 
147 static void
freeDirTodo(struct dirTodoNode * dt)148 freeDirTodo(struct dirTodoNode *dt)
149 {
150           dt->next = freedt;
151           freedt = dt;
152 }
153 
154 /*
155  * The stack of unread directories
156  */
157 struct dirTodoNode *pendingDirectories = NULL;
158 
159 /*
160  * Return the full pathname for a directory entry.
161  */
162 static char *
fullpath(struct dosDirEntry * dir)163 fullpath(struct dosDirEntry *dir)
164 {
165           static char namebuf[MAXPATHLEN + 1];
166           char *cp, *np;
167           int nl;
168 
169           cp = namebuf + sizeof namebuf - 1;
170           *cp = '\0';
171           do {
172                     np = dir->lname[0] ? dir->lname : dir->name;
173                     nl = strlen(np);
174                     if ((cp -= nl) <= namebuf + 1)
175                               break;
176                     memcpy(cp, np, nl);
177                     *--cp = '/';
178           } while ((dir = dir->parent) != NULL);
179           if (dir)
180                     *--cp = '?';
181           else
182                     cp++;
183           return cp;
184 }
185 
186 /*
187  * Calculate a checksum over an 8.3 alias name
188  */
189 static u_char
calcShortSum(u_char * p)190 calcShortSum(u_char *p)
191 {
192           u_char sum = 0;
193           int i;
194 
195           for (i = 0; i < 11; i++) {
196                     sum = (sum << 7)|(sum >> 1);  /* rotate right */
197                     sum += p[i];
198           }
199 
200           return sum;
201 }
202 
203 /*
204  * Global variables temporarily used during a directory scan
205  */
206 static char longName[DOSLONGNAMELEN] = "";
207 static char *eLongName = longName + sizeof(longName);
208 static u_char *buffer = NULL;
209 static u_char *delbuf = NULL;
210 
211 struct dosDirEntry *rootDir;
212 static struct dosDirEntry *lostDir;
213 
214 /*
215  * Init internal state for a new directory scan.
216  */
217 int
resetDosDirSection(struct bootblock * boot,struct fatEntry * fat)218 resetDosDirSection(struct bootblock *boot, struct fatEntry *fat)
219 {
220           int b1, b2;
221           cl_t cl;
222           int ret = FSOK;
223           size_t len;
224 
225           b1 = boot->RootDirEnts * 32;
226           b2 = boot->SecPerClust * boot->BytesPerSec;
227 
228           if ((buffer = malloc(len = b1 > b2 ? b1 : b2)) == NULL) {
229                     perr("No space for directory buffer (%zu)", len);
230                     return FSFATAL;
231           }
232 
233           if ((delbuf = malloc(len = b2)) == NULL) {
234                     free(buffer);
235                     perr("No space for directory delbuf (%zu)", len);
236                     return FSFATAL;
237           }
238 
239           if ((rootDir = newDosDirEntry()) == NULL) {
240                     free(buffer);
241                     free(delbuf);
242                     perr("No space for directory entry");
243                     return FSFATAL;
244           }
245 
246           memset(rootDir, 0, sizeof *rootDir);
247           if (boot->flags & FAT32) {
248                     if (boot->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) {
249                               pfatal("Root directory starts with cluster out of range(%u)",
250                                      boot->RootCl);
251                               return FSFATAL;
252                     }
253                     cl = fat[boot->RootCl].next;
254                     if (cl < CLUST_FIRST
255                         || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
256                         || fat[boot->RootCl].head != boot->RootCl) {
257                               if (cl == CLUST_FREE)
258                                         pwarn("Root directory starts with free cluster\n");
259                               else if (cl >= CLUST_RSRVD)
260                                         pwarn("Root directory starts with cluster marked %s\n",
261                                               rsrvdcltype(cl));
262                               else {
263                                         pfatal("Root directory doesn't start a cluster chain");
264                                         return FSFATAL;
265                               }
266                               if (ask(1, "Fix")) {
267                                         fat[boot->RootCl].next = CLUST_FREE;
268                                         ret = FSFATMOD;
269                               } else
270                                         ret = FSFATAL;
271                     }
272 
273                     fat[boot->RootCl].flags |= FAT_USED;
274                     rootDir->head = boot->RootCl;
275           }
276 
277           return ret;
278 }
279 
280 /*
281  * Cleanup after a directory scan
282  */
283 void
finishDosDirSection(void)284 finishDosDirSection(void)
285 {
286           struct dirTodoNode *p, *np;
287           struct dosDirEntry *d, *nd;
288 
289           for (p = pendingDirectories; p; p = np) {
290                     np = p->next;
291                     freeDirTodo(p);
292           }
293           pendingDirectories = 0;
294           for (d = rootDir; d; d = nd) {
295                     if ((nd = d->child) != NULL) {
296                               d->child = 0;
297                               continue;
298                     }
299                     if (!(nd = d->next))
300                               nd = d->parent;
301                     freeDosDirEntry(d);
302           }
303           rootDir = lostDir = NULL;
304           free(buffer);
305           free(delbuf);
306           buffer = NULL;
307           delbuf = NULL;
308 }
309 
310 /*
311  * Delete directory entries between startcl, startoff and endcl, endoff.
312  */
313 static int
delete(int f,struct bootblock * boot,struct fatEntry * fat,cl_t startcl,int startoff,cl_t endcl,int endoff,int notlast)314 delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl,
315        int startoff, cl_t endcl, int endoff, int notlast)
316 {
317           u_char *s, *e;
318           off_t off;
319           int clsz = boot->SecPerClust * boot->BytesPerSec;
320 
321           s = delbuf + startoff;
322           e = delbuf + clsz;
323           while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
324                     if (startcl == endcl) {
325                               if (notlast)
326                                         break;
327                               e = delbuf + endoff;
328                     }
329                     off = (startcl - CLUST_FIRST) * boot->SecPerClust + boot->FirstCluster;
330                     off *= boot->BytesPerSec;
331                     if (lseek(f, off, SEEK_SET) != off
332                         || read(f, delbuf, clsz) != clsz) {
333                               perr("Unable to read directory");
334                               return FSFATAL;
335                     }
336                     while (s < e) {
337                               *s = SLOT_DELETED;
338                               s += 32;
339                     }
340                     if (lseek(f, off, SEEK_SET) != off
341                         || write(f, delbuf, clsz) != clsz) {
342                               perr("Unable to write directory");
343                               return FSFATAL;
344                     }
345                     if (startcl == endcl)
346                               break;
347                     startcl = fat[startcl].next;
348                     s = delbuf;
349           }
350           return FSOK;
351 }
352 
353 static int
removede(int f,struct bootblock * boot,struct fatEntry * fat,u_char * start,u_char * end,cl_t startcl,cl_t endcl,cl_t curcl,char * path,int type)354 removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start,
355          u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path,
356            int type)
357 {
358           switch (type) {
359           case 0:
360                     pwarn("Invalid long filename entry for %s\n", path);
361                     break;
362           case 1:
363                     pwarn("Invalid long filename entry at end of directory %s\n", path);
364                     break;
365           case 2:
366                     pwarn("Invalid long filename entry for volume label\n");
367                     break;
368           }
369           if (ask(0, "Remove")) {
370                     if (startcl != curcl) {
371                               if (delete(f, boot, fat,
372                                            startcl, start - buffer,
373                                            endcl, end - buffer,
374                                            endcl == curcl) == FSFATAL)
375                                         return FSFATAL;
376                               start = buffer;
377                     }
378                     /* startcl is < CLUST_FIRST for !fat32 root */
379                     if ((endcl == curcl) || (startcl < CLUST_FIRST))
380                               for (; start < end; start += 32)
381                                         *start = SLOT_DELETED;
382                     return FSDIRMOD;
383           }
384           return FSERROR;
385 }
386 
387 /*
388  * Check an in-memory file entry
389  */
390 static int
checksize(struct bootblock * boot,struct fatEntry * fat,u_char * p,struct dosDirEntry * dir)391 checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
392             struct dosDirEntry *dir)
393 {
394           /*
395            * Check size on ordinary files
396            */
397           u_int32_t physicalSize;
398 
399           if (dir->head == CLUST_FREE)
400                     physicalSize = 0;
401           else {
402                     if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
403                               return FSERROR;
404                     physicalSize = fat[dir->head].length * boot->ClusterSize;
405           }
406           if (physicalSize < dir->size) {
407                     pwarn("size of %s is %u, should at most be %u\n",
408                           fullpath(dir), dir->size, physicalSize);
409                     if (ask(1, "Truncate")) {
410                               dir->size = physicalSize;
411                               p[28] = (u_char)physicalSize;
412                               p[29] = (u_char)(physicalSize >> 8);
413                               p[30] = (u_char)(physicalSize >> 16);
414                               p[31] = (u_char)(physicalSize >> 24);
415                               return FSDIRMOD;
416                     } else
417                               return FSERROR;
418           } else if (physicalSize - dir->size >= boot->ClusterSize) {
419                     pwarn("%s has too many clusters allocated\n",
420                           fullpath(dir));
421                     if (ask(1, "Drop superfluous clusters")) {
422                               cl_t cl;
423                               u_int32_t sz, len;
424 
425                               for (cl = dir->head, len = sz = 0;
426                                   (sz += boot->ClusterSize) < dir->size; len++)
427                                         cl = fat[cl].next;
428                               clearchain(boot, fat, fat[cl].next);
429                               fat[cl].next = CLUST_EOF;
430                               fat[dir->head].length = len;
431                               return FSFATMOD;
432                     } else
433                               return FSERROR;
434           }
435           return FSOK;
436 }
437 
438 static int
procName(int from,int to,char ** dst,const u_char * src)439 procName(int from, int to, char **dst, const u_char *src)
440 {
441           int k;
442           char *t = *dst;
443 
444           for (k = from; k < to && t < eLongName; k += 2) {
445                     if (!src[k] && !src[k + 1])
446                               break;
447                     *t++ = src[k];
448                     /*
449                      * Warn about those unusable chars in msdosfs here?         XXX
450                      */
451                     if (src[k + 1])
452                         t[-1] = '?';
453           }
454           *dst = t;
455           return k;
456 }
457 
458 /*
459  * Read a directory and
460  *   - resolve long name records
461  *   - enter file and directory records into the parent's list
462  *   - push directories onto the todo-stack
463  */
464 static int
readDosDirSection(int f,struct bootblock * boot,struct fatEntry * fat,struct dosDirEntry * dir)465 readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
466                       struct dosDirEntry *dir)
467 {
468           struct dosDirEntry dirent, *d;
469           u_char *p, *vallfn, *invlfn, *empty;
470           off_t off;
471           int i, j, k, last;
472           cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
473           char *t;
474           u_int lidx = 0;
475           int shortSum;
476           int mod = FSOK;
477 #define   THISMOD   0x8000                        /* Only used within this routine */
478 
479           cl = dir->head;
480           if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
481                     /*
482                      * Already handled somewhere else.
483                      */
484                     return FSOK;
485           }
486           shortSum = -1;
487           vallfn = invlfn = empty = NULL;
488           do {
489                     if (!(boot->flags & FAT32) && !dir->parent) {
490                               last = boot->RootDirEnts * 32;
491                               off = boot->ResSectors + boot->FATs * boot->FATsecs;
492                     } else {
493                               last = boot->SecPerClust * boot->BytesPerSec;
494                               off = (cl - CLUST_FIRST) * boot->SecPerClust + boot->FirstCluster;
495                     }
496 
497                     off *= boot->BytesPerSec;
498                     if (lseek(f, off, SEEK_SET) != off
499                         || read(f, buffer, last) != last) {
500                               perr("Unable to read directory");
501                               return FSFATAL;
502                     }
503                     last /= 32;
504                     /*
505                      * Check `.' and `..' entries here?                         XXX
506                      */
507                     for (p = buffer, i = 0; i < last; i++, p += 32) {
508                               if (dir->fsckflags & DIREMPWARN) {
509                                         *p = SLOT_EMPTY;
510                                         continue;
511                               }
512 
513                               if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
514                                         if (*p == SLOT_EMPTY) {
515                                                   dir->fsckflags |= DIREMPTY;
516                                                   empty = p;
517                                                   empcl = cl;
518                                         }
519                                         continue;
520                               }
521 
522                               if (dir->fsckflags & DIREMPTY) {
523                                         if (!(dir->fsckflags & DIREMPWARN)) {
524                                                   pwarn("%s has entries after end of directory\n",
525                                                         fullpath(dir));
526                                                   if (ask(1, "Extend")) {
527                                                             u_char *q;
528 
529                                                             dir->fsckflags &= ~DIREMPTY;
530                                                             if (delete(f, boot, fat,
531                                                                          empcl, empty - buffer,
532                                                                          cl, p - buffer, 1) == FSFATAL)
533                                                                       return FSFATAL;
534                                                             q = empcl == cl ? empty : buffer;
535                                                             assert(q != NULL);
536                                                             for (; q < p; q += 32)
537                                                                       *q = SLOT_DELETED;
538                                                             mod |= THISMOD|FSDIRMOD;
539                                                   } else if (ask(0, "Truncate"))
540                                                             dir->fsckflags |= DIREMPWARN;
541                                         }
542                                         if (dir->fsckflags & DIREMPWARN) {
543                                                   *p = SLOT_DELETED;
544                                                   mod |= THISMOD|FSDIRMOD;
545                                                   continue;
546                                         } else if (dir->fsckflags & DIREMPTY)
547                                                   mod |= FSERROR;
548                                         empty = NULL;
549                               }
550 
551                               if (p[11] == ATTR_WIN95) {
552                                         u_int lrnomask = *p & LRNOMASK;
553                                         if (*p & LRFIRST) {
554                                                   if (shortSum != -1) {
555                                                             if (!invlfn) {
556                                                                       invlfn = vallfn;
557                                                                       invcl = valcl;
558                                                             }
559                                                   }
560                                                   memset(longName, 0, sizeof longName);
561                                                   shortSum = p[13];
562                                                   vallfn = p;
563                                                   valcl = cl;
564                                         } else if (shortSum != p[13]
565                                            || lidx != lrnomask) {
566                                                   if (!invlfn) {
567                                                             invlfn = vallfn;
568                                                             invcl = valcl;
569                                                   }
570                                                   if (!invlfn) {
571                                                             invlfn = p;
572                                                             invcl = cl;
573                                                   }
574                                                   vallfn = NULL;
575                                         }
576                                         lidx = lrnomask;
577                                         if (lidx != 0) {
578                                                   t = longName + --lidx * 13;
579                                                   k = procName(1, 11, &t, p);
580                                                   if (k >= 11)
581                                                             k = procName(14, 26, &t, p);
582                                                   if (k >= 26)
583                                                             k = procName(28, 32, &t, p);
584                                                   if (t >= eLongName) {
585                                                             pwarn(
586                                                                 "long filename too long\n");
587                                                             if (!invlfn) {
588                                                                       invlfn = vallfn;
589                                                                       invcl = valcl;
590                                                             }
591                                                             vallfn = NULL;
592                                                   }
593                                         }
594                                         if (p[26] | (p[27] << 8)) {
595                                                   pwarn("long filename record cluster "
596                                                       "start != 0\n");
597                                                   if (!invlfn) {
598                                                             invlfn = vallfn;
599                                                             invcl = cl;
600                                                   }
601                                                   vallfn = NULL;
602                                         }
603                                         continue;           /* long records don't carry
604                                                              * further information */
605                               }
606 
607                               /*
608                                * This is a standard msdosfs directory entry.
609                                */
610                               memset(&dirent, 0, sizeof dirent);
611 
612                               /*
613                                * it's a short name record, but we need to know
614                                * more, so get the flags first.
615                                */
616                               dirent.flags = p[11];
617 
618                               /*
619                                * Translate from 850 to ISO here                 XXX
620                                */
621                               for (j = 0; j < 8; j++)
622                                         dirent.name[j] = p[j];
623                               dirent.name[8] = '\0';
624                               for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
625                                         dirent.name[k] = '\0';
626                               if (k < 0 || dirent.name[k] != '\0')
627                                         k++;
628                               if (dirent.name[0] == SLOT_E5)
629                                         dirent.name[0] = 0xe5;
630 
631                               if (dirent.flags & ATTR_VOLUME) {
632                                         if (vallfn || invlfn) {
633                                                   mod |= removede(f, boot, fat,
634                                                                       invlfn ? invlfn : vallfn, p,
635                                                                       invlfn ? invcl : valcl, -1, 0,
636                                                                       fullpath(dir), 2);
637                                                   vallfn = NULL;
638                                                   invlfn = NULL;
639                                         }
640                                         continue;
641                               }
642 
643                               if (p[8] != ' ')
644                                         dirent.name[k++] = '.';
645                               for (j = 0; j < 3; j++)
646                                         dirent.name[k++] = p[j+8];
647                               dirent.name[k] = '\0';
648                               for (k--; k >= 0 && dirent.name[k] == ' '; k--)
649                                         dirent.name[k] = '\0';
650 
651                               if (vallfn && shortSum != calcShortSum(p)) {
652                                         if (!invlfn) {
653                                                   invlfn = vallfn;
654                                                   invcl = valcl;
655                                         }
656                                         vallfn = NULL;
657                               }
658                               dirent.head = p[26] | (p[27] << 8);
659                               if (boot->ClustMask == CLUST32_MASK)
660                                         dirent.head |= (p[20] << 16) | (p[21] << 24);
661                               dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
662                               if (vallfn) {
663                                         strlcpy(dirent.lname, longName,
664                                             sizeof(dirent.lname));
665                                         longName[0] = '\0';
666                                         shortSum = -1;
667                               }
668 
669                               dirent.parent = dir;
670                               dirent.next = dir->child;
671 
672                               if (invlfn) {
673                                         mod |= k = removede(f, boot, fat,
674                                                                 invlfn, vallfn ? vallfn : p,
675                                                                 invcl, vallfn ? valcl : cl, cl,
676                                                                 fullpath(&dirent), 0);
677                                         if (mod & FSFATAL)
678                                                   return FSFATAL;
679                                         if (vallfn
680                                             ? (valcl == cl && vallfn != buffer)
681                                             : p != buffer)
682                                                   if (k & FSDIRMOD)
683                                                             mod |= THISMOD;
684                               }
685 
686                               vallfn = NULL; /* not used any longer */
687                               invlfn = NULL;
688 
689                               if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
690                                         if (dirent.head != 0) {
691                                                   pwarn("%s has clusters, but size 0\n",
692                                                         fullpath(&dirent));
693                                                   if (ask(1, "Drop allocated clusters")) {
694                                                             p[26] = p[27] = 0;
695                                                             if (boot->ClustMask == CLUST32_MASK)
696                                                                       p[20] = p[21] = 0;
697                                                             clearchain(boot, fat, dirent.head);
698                                                             dirent.head = 0;
699                                                             mod |= THISMOD|FSDIRMOD|FSFATMOD;
700                                                   } else
701                                                             mod |= FSERROR;
702                                         }
703                               } else if (dirent.head == 0
704                                            && !strcmp(dirent.name, "..")
705                                            && dir->parent                       /* XXX */
706                                            && !dir->parent->parent) {
707                                         /*
708                                          *  Do nothing, the parent is the root
709                                          */
710                               } else if (dirent.head < CLUST_FIRST
711                                            || dirent.head >= boot->NumClusters
712                                            || fat[dirent.head].next == CLUST_FREE
713                                            || (fat[dirent.head].next >= CLUST_RSRVD
714                                                && fat[dirent.head].next < CLUST_EOFS)
715                                            || fat[dirent.head].head != dirent.head) {
716                                         if (dirent.head == 0)
717                                                   pwarn("%s has no clusters\n",
718                                                         fullpath(&dirent));
719                                         else if (dirent.head < CLUST_FIRST
720                                                    || dirent.head >= boot->NumClusters)
721                                                   pwarn("%s starts with cluster out of range(%u)\n",
722                                                         fullpath(&dirent),
723                                                         dirent.head);
724                                         else if (fat[dirent.head].next == CLUST_FREE)
725                                                   pwarn("%s starts with free cluster\n",
726                                                         fullpath(&dirent));
727                                         else if (fat[dirent.head].next >= CLUST_RSRVD)
728                                                   pwarn("%s starts with cluster marked %s\n",
729                                                         fullpath(&dirent),
730                                                         rsrvdcltype(fat[dirent.head].next));
731                                         else
732                                                   pwarn("%s doesn't start a cluster chain\n",
733                                                         fullpath(&dirent));
734                                         if (dirent.flags & ATTR_DIRECTORY) {
735                                                   if (ask(0, "Remove")) {
736                                                             *p = SLOT_DELETED;
737                                                             mod |= THISMOD|FSDIRMOD;
738                                                   } else
739                                                             mod |= FSERROR;
740                                                   continue;
741                                         } else {
742                                                   if (ask(1, "Truncate")) {
743                                                             p[28] = p[29] = p[30] = p[31] = 0;
744                                                             p[26] = p[27] = 0;
745                                                             if (boot->ClustMask == CLUST32_MASK)
746                                                                       p[20] = p[21] = 0;
747                                                             dirent.size = 0;
748                                                             mod |= THISMOD|FSDIRMOD;
749                                                   } else
750                                                             mod |= FSERROR;
751                                         }
752                               }
753 
754                               if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
755                                         fat[dirent.head].flags |= FAT_USED;
756 
757                               if (dirent.flags & ATTR_DIRECTORY) {
758                                         /*
759                                          * gather more info for directories
760                                          */
761                                         struct dirTodoNode *n;
762 
763                                         if (dirent.size) {
764                                                   pwarn("Directory %s has size != 0\n",
765                                                         fullpath(&dirent));
766                                                   if (ask(1, "Correct")) {
767                                                             p[28] = p[29] = p[30] = p[31] = 0;
768                                                             dirent.size = 0;
769                                                             mod |= THISMOD|FSDIRMOD;
770                                                   } else
771                                                             mod |= FSERROR;
772                                         }
773                                         /*
774                                          * handle `.' and `..' specially
775                                          */
776                                         if (strcmp(dirent.name, ".") == 0) {
777                                                   if (dirent.head != dir->head) {
778                                                             pwarn("`.' entry in %s has incorrect start cluster\n",
779                                                                   fullpath(dir));
780                                                             if (ask(1, "Correct")) {
781                                                                       dirent.head = dir->head;
782                                                                       p[26] = (u_char)dirent.head;
783                                                                       p[27] = (u_char)(dirent.head >> 8);
784                                                                       if (boot->ClustMask == CLUST32_MASK) {
785                                                                                 p[20] = (u_char)(dirent.head >> 16);
786                                                                                 p[21] = (u_char)(dirent.head >> 24);
787                                                                       }
788                                                                       mod |= THISMOD|FSDIRMOD;
789                                                             } else
790                                                                       mod |= FSERROR;
791                                                   }
792                                                   continue;
793                                         }
794                                         if (strcmp(dirent.name, "..") == 0) {
795                                                   if (dir->parent) {            /* XXX */
796                                                             if (!dir->parent->parent) {
797                                                                       if (dirent.head) {
798                                                                                 pwarn("`..' entry in %s has non-zero start cluster\n",
799                                                                                       fullpath(dir));
800                                                                                 if (ask(1, "Correct")) {
801                                                                                           dirent.head = 0;
802                                                                                           p[26] = p[27] = 0;
803                                                                                           if (boot->ClustMask == CLUST32_MASK)
804                                                                                                     p[20] = p[21] = 0;
805                                                                                           mod |= THISMOD|FSDIRMOD;
806                                                                                 } else
807                                                                                           mod |= FSERROR;
808                                                                       }
809                                                             } else if (dirent.head != dir->parent->head) {
810                                                                       pwarn("`..' entry in %s has incorrect start cluster\n",
811                                                                             fullpath(dir));
812                                                                       if (ask(1, "Correct")) {
813                                                                                 dirent.head = dir->parent->head;
814                                                                                 p[26] = (u_char)dirent.head;
815                                                                                 p[27] = (u_char)(dirent.head >> 8);
816                                                                                 if (boot->ClustMask == CLUST32_MASK) {
817                                                                                           p[20] = (u_char)(dirent.head >> 16);
818                                                                                           p[21] = (u_char)(dirent.head >> 24);
819                                                                                 }
820                                                                                 mod |= THISMOD|FSDIRMOD;
821                                                                       } else
822                                                                                 mod |= FSERROR;
823                                                             }
824                                                   }
825                                                   continue;
826                                         }
827 
828                                         /* create directory tree node */
829                                         if (!(d = newDosDirEntry())) {
830                                                   perr("No space for directory");
831                                                   return FSFATAL;
832                                         }
833                                         memcpy(d, &dirent, sizeof(struct dosDirEntry));
834                                         /* link it into the tree */
835                                         dir->child = d;
836 
837                                         /* Enter this directory into the todo list */
838                                         if (!(n = newDirTodo())) {
839                                                   perr("No space for todo list");
840                                                   return FSFATAL;
841                                         }
842                                         n->next = pendingDirectories;
843                                         n->dir = d;
844                                         pendingDirectories = n;
845                               } else {
846                                         mod |= k = checksize(boot, fat, p, &dirent);
847                                         if (k & FSDIRMOD)
848                                                   mod |= THISMOD;
849                               }
850                               boot->NumFiles++;
851                     }
852 
853                     if (!(boot->flags & FAT32) && !dir->parent)
854                               break;
855 
856                     if (mod & THISMOD) {
857                               last *= 32;
858                               if (lseek(f, off, SEEK_SET) != off
859                                   || write(f, buffer, last) != last) {
860                                         perr("Unable to write directory");
861                                         return FSFATAL;
862                               }
863                               mod &= ~THISMOD;
864                     }
865           } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
866           if (invlfn || vallfn)
867                     mod |= removede(f, boot, fat,
868                                         invlfn ? invlfn : vallfn, p,
869                                         invlfn ? invcl : valcl, -1, 0,
870                                         fullpath(dir), 1);
871 
872           /* The root directory of non fat32 filesystems is in a special
873            * area and may have been modified above without being written out.
874            */
875           if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) {
876                     last *= 32;
877                     if (lseek(f, off, SEEK_SET) != off
878                         || write(f, buffer, last) != last) {
879                               perr("Unable to write directory");
880                               return FSFATAL;
881                     }
882                     mod &= ~THISMOD;
883           }
884           return mod & ~THISMOD;
885 }
886 
887 int
handleDirTree(int dosfs,struct bootblock * boot,struct fatEntry * fat)888 handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
889 {
890           int mod;
891 
892           mod = readDosDirSection(dosfs, boot, fat, rootDir);
893           if (mod & FSFATAL)
894                     return FSFATAL;
895 
896           /*
897            * process the directory todo list
898            */
899           while (pendingDirectories) {
900                     struct dosDirEntry *dir = pendingDirectories->dir;
901                     struct dirTodoNode *n = pendingDirectories->next;
902 
903                     /*
904                      * remove TODO entry now, the list might change during
905                      * directory reads
906                      */
907                     freeDirTodo(pendingDirectories);
908                     pendingDirectories = n;
909 
910                     /*
911                      * handle subdirectory
912                      */
913                     mod |= readDosDirSection(dosfs, boot, fat, dir);
914                     if (mod & FSFATAL)
915                               return FSFATAL;
916           }
917 
918           return mod;
919 }
920 
921 /*
922  * Try to reconnect a FAT chain into dir
923  */
924 static u_char *lfbuf;
925 static cl_t lfcl;
926 static off_t lfoff;
927 
928 int
reconnect(int dosfs,struct bootblock * boot,struct fatEntry * fat,cl_t head)929 reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
930 {
931           struct dosDirEntry d;
932           int len;
933           u_char *p;
934 
935           if (!ask(1, "Reconnect"))
936                     return FSERROR;
937 
938           if (!lostDir) {
939                     for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
940                               if (!strcmp(lostDir->name, LOSTDIR))
941                                         break;
942                     }
943                     if (!lostDir) {               /* Create LOSTDIR?            XXX */
944                               pwarn("No %s directory\n", LOSTDIR);
945                               return FSERROR;
946                     }
947           }
948           if (!lfbuf) {
949                     lfbuf = malloc(boot->ClusterSize);
950                     if (!lfbuf) {
951                               perr("No space for buffer");
952                               return FSFATAL;
953                     }
954                     p = NULL;
955           } else
956                     p = lfbuf;
957           while (1) {
958                     if (p)
959                               for (; p < lfbuf + boot->ClusterSize; p += 32)
960                                         if (*p == SLOT_EMPTY
961                                             || *p == SLOT_DELETED)
962                                                   break;
963                     if (p && p < lfbuf + boot->ClusterSize)
964                               break;
965                     lfcl = p ? fat[lfcl].next : lostDir->head;
966                     if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
967                               /* Extend LOSTDIR?                                XXX */
968                               pwarn("No space in %s\n", LOSTDIR);
969                               return FSERROR;
970                     }
971                     lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
972                         + boot->FirstCluster * boot->BytesPerSec;
973                     if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
974                         || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
975                               perr("could not read LOST.DIR");
976                               return FSFATAL;
977                     }
978                     p = lfbuf;
979           }
980 
981           boot->NumFiles++;
982           /* Ensure uniqueness of entry here!                                   XXX */
983           memset(&d, 0, sizeof d);
984           /* worst case -1 = 4294967295, 10 digits */
985           len = snprintf(d.name, sizeof(d.name), "%u", head);
986           d.flags = 0;
987           d.head = head;
988           d.size = fat[head].length * boot->ClusterSize;
989 
990           memcpy(p, d.name, len);
991           memset(p + len, ' ', 11 - len);
992           memset(p + 11, 0, 32 - 11);
993           p[26] = (u_char)d.head;
994           p[27] = (u_char)(d.head >> 8);
995           if (boot->ClustMask == CLUST32_MASK) {
996                     p[20] = (u_char)(d.head >> 16);
997                     p[21] = (u_char)(d.head >> 24);
998           }
999           p[28] = (u_char)d.size;
1000           p[29] = (u_char)(d.size >> 8);
1001           p[30] = (u_char)(d.size >> 16);
1002           p[31] = (u_char)(d.size >> 24);
1003           fat[head].flags |= FAT_USED;
1004           if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1005               || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1006                     perr("could not write LOST.DIR");
1007                     return FSFATAL;
1008           }
1009           return FSDIRMOD;
1010 }
1011 
1012 void
finishlf(void)1013 finishlf(void)
1014 {
1015           if (lfbuf)
1016                     free(lfbuf);
1017           lfbuf = NULL;
1018 }
1019