blob: 6bdc3b4d0f94723257b0d6004cb726ac24d9b172 [file] [log] [blame]
Xin Li1e1dad62019-04-10 13:38:23 -07001/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
Xin Li21386e02020-01-03 09:22:03 -08004 * Copyright (c) 2019 Google LLC
Xin Li1e1dad62019-04-10 13:38:23 -07005 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
6 * Copyright (c) 1995 Martin Husemann
7 * Some structure declaration borrowed from Paul Popelka
8 * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32#include <sys/cdefs.h>
33#ifndef lint
34__RCSID("$NetBSD: dir.c,v 1.20 2006/06/05 16:51:18 christos Exp $");
35static const char rcsid[] =
36 "$FreeBSD$";
37#endif /* not lint */
38
Xin Li760f5402019-04-10 13:39:57 -070039#include <assert.h>
40#include <inttypes.h>
Xin Li1e1dad62019-04-10 13:38:23 -070041#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <ctype.h>
45#include <unistd.h>
46#include <time.h>
47
48#include <sys/param.h>
49
50#include "ext.h"
51#include "fsutil.h"
52
53#define SLOT_EMPTY 0x00 /* slot has never been used */
54#define SLOT_E5 0x05 /* the real value is 0xe5 */
55#define SLOT_DELETED 0xe5 /* file in this slot deleted */
56
57#define ATTR_NORMAL 0x00 /* normal file */
58#define ATTR_READONLY 0x01 /* file is readonly */
59#define ATTR_HIDDEN 0x02 /* file is hidden */
60#define ATTR_SYSTEM 0x04 /* file is a system file */
61#define ATTR_VOLUME 0x08 /* entry is a volume label */
62#define ATTR_DIRECTORY 0x10 /* entry is a directory name */
63#define ATTR_ARCHIVE 0x20 /* file is new or modified */
64
65#define ATTR_WIN95 0x0f /* long name record */
66
67/*
68 * This is the format of the contents of the deTime field in the direntry
69 * structure.
70 * We don't use bitfields because we don't know how compilers for
71 * arbitrary machines will lay them out.
72 */
73#define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */
74#define DT_2SECONDS_SHIFT 0
75#define DT_MINUTES_MASK 0x7E0 /* minutes */
76#define DT_MINUTES_SHIFT 5
77#define DT_HOURS_MASK 0xF800 /* hours */
78#define DT_HOURS_SHIFT 11
79
80/*
81 * This is the format of the contents of the deDate field in the direntry
82 * structure.
83 */
84#define DD_DAY_MASK 0x1F /* day of month */
85#define DD_DAY_SHIFT 0
86#define DD_MONTH_MASK 0x1E0 /* month */
87#define DD_MONTH_SHIFT 5
88#define DD_YEAR_MASK 0xFE00 /* year - 1980 */
89#define DD_YEAR_SHIFT 9
90
91
92/* dir.c */
93static struct dosDirEntry *newDosDirEntry(void);
94static void freeDosDirEntry(struct dosDirEntry *);
95static struct dirTodoNode *newDirTodo(void);
96static void freeDirTodo(struct dirTodoNode *);
97static char *fullpath(struct dosDirEntry *);
98static u_char calcShortSum(u_char *);
Xin Li21386e02020-01-03 09:22:03 -080099static int delete(struct fat_descriptor *, cl_t, int, cl_t, int, int);
100static int removede(struct fat_descriptor *, u_char *, u_char *,
101 cl_t, cl_t, cl_t, char *, int);
102static int checksize(struct fat_descriptor *, u_char *, struct dosDirEntry *);
103static int readDosDirSection(struct fat_descriptor *, struct dosDirEntry *);
Xin Li1e1dad62019-04-10 13:38:23 -0700104
105/*
106 * Manage free dosDirEntry structures.
107 */
108static struct dosDirEntry *freede;
109
110static struct dosDirEntry *
111newDosDirEntry(void)
112{
113 struct dosDirEntry *de;
114
115 if (!(de = freede)) {
Xin Li8b5aba42019-06-04 08:55:20 -0700116 if (!(de = malloc(sizeof *de)))
Xin Li21386e02020-01-03 09:22:03 -0800117 return (NULL);
Xin Li1e1dad62019-04-10 13:38:23 -0700118 } else
119 freede = de->next;
120 return de;
121}
122
123static void
124freeDosDirEntry(struct dosDirEntry *de)
125{
126 de->next = freede;
127 freede = de;
128}
129
130/*
131 * The same for dirTodoNode structures.
132 */
133static struct dirTodoNode *freedt;
134
135static struct dirTodoNode *
136newDirTodo(void)
137{
138 struct dirTodoNode *dt;
139
140 if (!(dt = freedt)) {
Xin Li8b5aba42019-06-04 08:55:20 -0700141 if (!(dt = malloc(sizeof *dt)))
Xin Li1e1dad62019-04-10 13:38:23 -0700142 return 0;
143 } else
144 freedt = dt->next;
145 return dt;
146}
147
148static void
149freeDirTodo(struct dirTodoNode *dt)
150{
151 dt->next = freedt;
152 freedt = dt;
153}
154
155/*
156 * The stack of unread directories
157 */
158static struct dirTodoNode *pendingDirectories = NULL;
159
160/*
161 * Return the full pathname for a directory entry.
162 */
163static char *
164fullpath(struct dosDirEntry *dir)
165{
166 static char namebuf[MAXPATHLEN + 1];
167 char *cp, *np;
168 int nl;
169
Xin Lia4cbc882019-10-01 15:09:31 -0700170 cp = namebuf + sizeof namebuf;
171 *--cp = '\0';
172
173 for(;;) {
Xin Li1e1dad62019-04-10 13:38:23 -0700174 np = dir->lname[0] ? dir->lname : dir->name;
175 nl = strlen(np);
Xin Lia4cbc882019-10-01 15:09:31 -0700176 if (cp <= namebuf + 1 + nl) {
177 *--cp = '?';
Xin Li1e1dad62019-04-10 13:38:23 -0700178 break;
Xin Lia4cbc882019-10-01 15:09:31 -0700179 }
180 cp -= nl;
Xin Li1e1dad62019-04-10 13:38:23 -0700181 memcpy(cp, np, nl);
Xin Lia4cbc882019-10-01 15:09:31 -0700182 dir = dir->parent;
183 if (!dir)
184 break;
Xin Li1e1dad62019-04-10 13:38:23 -0700185 *--cp = '/';
Xin Lia4cbc882019-10-01 15:09:31 -0700186 }
187
Xin Li1e1dad62019-04-10 13:38:23 -0700188 return cp;
189}
190
191/*
192 * Calculate a checksum over an 8.3 alias name
193 */
Xin Li21386e02020-01-03 09:22:03 -0800194static inline u_char
Xin Li1e1dad62019-04-10 13:38:23 -0700195calcShortSum(u_char *p)
196{
197 u_char sum = 0;
198 int i;
199
200 for (i = 0; i < 11; i++) {
201 sum = (sum << 7)|(sum >> 1); /* rotate right */
202 sum += p[i];
203 }
204
205 return sum;
206}
207
208/*
209 * Global variables temporarily used during a directory scan
210 */
211static char longName[DOSLONGNAMELEN] = "";
212static u_char *buffer = NULL;
213static u_char *delbuf = NULL;
214
215static struct dosDirEntry *rootDir;
216static struct dosDirEntry *lostDir;
217
218/*
219 * Init internal state for a new directory scan.
220 */
221int
Xin Li21386e02020-01-03 09:22:03 -0800222resetDosDirSection(struct fat_descriptor *fat)
Xin Li1e1dad62019-04-10 13:38:23 -0700223{
Xin Li21386e02020-01-03 09:22:03 -0800224 int rootdir_size, cluster_size;
Xin Li1e1dad62019-04-10 13:38:23 -0700225 int ret = FSOK;
226 size_t len;
Xin Li21386e02020-01-03 09:22:03 -0800227 struct bootblock *boot;
Xin Li1e1dad62019-04-10 13:38:23 -0700228
Xin Li21386e02020-01-03 09:22:03 -0800229 boot = fat_get_boot(fat);
Xin Li1e1dad62019-04-10 13:38:23 -0700230
Xin Li21386e02020-01-03 09:22:03 -0800231 rootdir_size = boot->bpbRootDirEnts * 32;
232 cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec;
233
234 if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) {
Xin Li1e1dad62019-04-10 13:38:23 -0700235 perr("No space for directory buffer (%zu)", len);
236 return FSFATAL;
237 }
238
Xin Li21386e02020-01-03 09:22:03 -0800239 if ((delbuf = malloc(len = cluster_size)) == NULL) {
Xin Li1e1dad62019-04-10 13:38:23 -0700240 free(buffer);
241 perr("No space for directory delbuf (%zu)", len);
242 return FSFATAL;
243 }
244
245 if ((rootDir = newDosDirEntry()) == NULL) {
246 free(buffer);
247 free(delbuf);
248 perr("No space for directory entry");
249 return FSFATAL;
250 }
251
252 memset(rootDir, 0, sizeof *rootDir);
253 if (boot->flags & FAT32) {
Xin Li21386e02020-01-03 09:22:03 -0800254 if (!fat_is_cl_head(fat, boot->bpbRootClust)) {
Xin Lia4cbc882019-10-01 15:09:31 -0700255 pfatal("Root directory doesn't start a cluster chain");
256 return FSFATAL;
Xin Li1e1dad62019-04-10 13:38:23 -0700257 }
Xin Li1e1dad62019-04-10 13:38:23 -0700258 rootDir->head = boot->bpbRootClust;
259 }
260
261 return ret;
262}
263
264/*
265 * Cleanup after a directory scan
266 */
267void
268finishDosDirSection(void)
269{
270 struct dirTodoNode *p, *np;
271 struct dosDirEntry *d, *nd;
272
273 for (p = pendingDirectories; p; p = np) {
274 np = p->next;
275 freeDirTodo(p);
276 }
277 pendingDirectories = NULL;
278 for (d = rootDir; d; d = nd) {
279 if ((nd = d->child) != NULL) {
280 d->child = 0;
281 continue;
282 }
283 if (!(nd = d->next))
284 nd = d->parent;
285 freeDosDirEntry(d);
286 }
287 rootDir = lostDir = NULL;
288 free(buffer);
289 free(delbuf);
290 buffer = NULL;
291 delbuf = NULL;
292}
293
294/*
295 * Delete directory entries between startcl, startoff and endcl, endoff.
296 */
297static int
Xin Li21386e02020-01-03 09:22:03 -0800298delete(struct fat_descriptor *fat, cl_t startcl,
Xin Li1e1dad62019-04-10 13:38:23 -0700299 int startoff, cl_t endcl, int endoff, int notlast)
300{
301 u_char *s, *e;
302 off_t off;
Xin Li21386e02020-01-03 09:22:03 -0800303 int clsz, fd;
304 struct bootblock *boot;
305
306 boot = fat_get_boot(fat);
307 fd = fat_get_fd(fat);
308 clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
Xin Li1e1dad62019-04-10 13:38:23 -0700309
310 s = delbuf + startoff;
311 e = delbuf + clsz;
Xin Li21386e02020-01-03 09:22:03 -0800312 while (fat_is_valid_cl(fat, startcl)) {
Xin Li1e1dad62019-04-10 13:38:23 -0700313 if (startcl == endcl) {
314 if (notlast)
315 break;
316 e = delbuf + endoff;
317 }
Xin Lia4cbc882019-10-01 15:09:31 -0700318 off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
319
Xin Li1e1dad62019-04-10 13:38:23 -0700320 off *= boot->bpbBytesPerSec;
Xin Li21386e02020-01-03 09:22:03 -0800321 if (lseek(fd, off, SEEK_SET) != off) {
Xin Li760f5402019-04-10 13:39:57 -0700322 perr("Unable to lseek to %" PRId64, off);
323 return FSFATAL;
324 }
Xin Li21386e02020-01-03 09:22:03 -0800325 if (read(fd, delbuf, clsz) != clsz) {
Xin Li1e1dad62019-04-10 13:38:23 -0700326 perr("Unable to read directory");
327 return FSFATAL;
328 }
329 while (s < e) {
330 *s = SLOT_DELETED;
331 s += 32;
332 }
Xin Li21386e02020-01-03 09:22:03 -0800333 if (lseek(fd, off, SEEK_SET) != off) {
Xin Li760f5402019-04-10 13:39:57 -0700334 perr("Unable to lseek to %" PRId64, off);
335 return FSFATAL;
336 }
Xin Li21386e02020-01-03 09:22:03 -0800337 if (write(fd, delbuf, clsz) != clsz) {
Xin Li1e1dad62019-04-10 13:38:23 -0700338 perr("Unable to write directory");
339 return FSFATAL;
340 }
341 if (startcl == endcl)
342 break;
Xin Li21386e02020-01-03 09:22:03 -0800343 startcl = fat_get_cl_next(fat, startcl);
Xin Li1e1dad62019-04-10 13:38:23 -0700344 s = delbuf;
345 }
346 return FSOK;
347}
348
349static int
Xin Li21386e02020-01-03 09:22:03 -0800350removede(struct fat_descriptor *fat, u_char *start,
351 u_char *end, cl_t startcl, cl_t endcl, cl_t curcl,
352 char *path, int type)
Xin Li1e1dad62019-04-10 13:38:23 -0700353{
354 switch (type) {
355 case 0:
356 pwarn("Invalid long filename entry for %s\n", path);
357 break;
358 case 1:
359 pwarn("Invalid long filename entry at end of directory %s\n",
360 path);
361 break;
362 case 2:
363 pwarn("Invalid long filename entry for volume label\n");
364 break;
365 }
366 if (ask(0, "Remove")) {
367 if (startcl != curcl) {
Xin Li21386e02020-01-03 09:22:03 -0800368 if (delete(fat,
Xin Li1e1dad62019-04-10 13:38:23 -0700369 startcl, start - buffer,
370 endcl, end - buffer,
371 endcl == curcl) == FSFATAL)
372 return FSFATAL;
373 start = buffer;
374 }
Xin Li21386e02020-01-03 09:22:03 -0800375 /* startcl is < CLUST_FIRST for !FAT32 root */
Xin Li1e1dad62019-04-10 13:38:23 -0700376 if ((endcl == curcl) || (startcl < CLUST_FIRST))
377 for (; start < end; start += 32)
378 *start = SLOT_DELETED;
379 return FSDIRMOD;
380 }
381 return FSERROR;
382}
383
384/*
385 * Check an in-memory file entry
386 */
387static int
Xin Li21386e02020-01-03 09:22:03 -0800388checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir)
Xin Li1e1dad62019-04-10 13:38:23 -0700389{
Xin Li21386e02020-01-03 09:22:03 -0800390 int ret = FSOK;
391 size_t physicalSize;
392 struct bootblock *boot;
393
394 boot = fat_get_boot(fat);
395
Xin Li1e1dad62019-04-10 13:38:23 -0700396 /*
397 * Check size on ordinary files
398 */
Xin Li21386e02020-01-03 09:22:03 -0800399 if (dir->head == CLUST_FREE) {
Xin Li1e1dad62019-04-10 13:38:23 -0700400 physicalSize = 0;
Xin Li21386e02020-01-03 09:22:03 -0800401 } else {
402 if (!fat_is_valid_cl(fat, dir->head))
Xin Li1e1dad62019-04-10 13:38:23 -0700403 return FSERROR;
Xin Li21386e02020-01-03 09:22:03 -0800404 ret = checkchain(fat, dir->head, &physicalSize);
405 /*
406 * Upon return, physicalSize would hold the chain length
407 * that checkchain() was able to validate, but if the user
408 * refused the proposed repair, it would be unsafe to
409 * proceed with directory entry fix, so bail out in that
410 * case.
411 */
412 if (ret == FSERROR) {
413 return (FSERROR);
414 }
415 physicalSize *= boot->ClusterSize;
Xin Li1e1dad62019-04-10 13:38:23 -0700416 }
417 if (physicalSize < dir->size) {
Xin Li21386e02020-01-03 09:22:03 -0800418 pwarn("size of %s is %u, should at most be %zu\n",
Xin Li1e1dad62019-04-10 13:38:23 -0700419 fullpath(dir), dir->size, physicalSize);
420 if (ask(1, "Truncate")) {
421 dir->size = physicalSize;
422 p[28] = (u_char)physicalSize;
423 p[29] = (u_char)(physicalSize >> 8);
424 p[30] = (u_char)(physicalSize >> 16);
425 p[31] = (u_char)(physicalSize >> 24);
426 return FSDIRMOD;
427 } else
428 return FSERROR;
429 } else if (physicalSize - dir->size >= boot->ClusterSize) {
430 pwarn("%s has too many clusters allocated\n",
431 fullpath(dir));
432 if (ask(1, "Drop superfluous clusters")) {
433 cl_t cl;
434 u_int32_t sz, len;
435
436 for (cl = dir->head, len = sz = 0;
437 (sz += boot->ClusterSize) < dir->size; len++)
Xin Li21386e02020-01-03 09:22:03 -0800438 cl = fat_get_cl_next(fat, cl);
439 clearchain(fat, fat_get_cl_next(fat, cl));
440 ret = fat_set_cl_next(fat, cl, CLUST_EOF);
441 return (FSFATMOD | ret);
Xin Li1e1dad62019-04-10 13:38:23 -0700442 } else
443 return FSERROR;
444 }
445 return FSOK;
446}
447
Xin Li760f5402019-04-10 13:39:57 -0700448static const u_char dot_name[11] = ". ";
449static const u_char dotdot_name[11] = ".. ";
450
451/*
452 * Basic sanity check if the subdirectory have good '.' and '..' entries,
453 * and they are directory entries. Further sanity checks are performed
454 * when we traverse into it.
455 */
456static int
Xin Li21386e02020-01-03 09:22:03 -0800457check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)
Xin Li760f5402019-04-10 13:39:57 -0700458{
459 u_char *buf, *cp;
460 off_t off;
461 cl_t cl;
462 int retval = FSOK;
Xin Li21386e02020-01-03 09:22:03 -0800463 int fd;
464 struct bootblock *boot;
465
466 boot = fat_get_boot(fat);
467 fd = fat_get_fd(fat);
Xin Li760f5402019-04-10 13:39:57 -0700468
469 cl = dir->head;
Xin Li21386e02020-01-03 09:22:03 -0800470 if (dir->parent && !fat_is_valid_cl(fat, cl)) {
Xin Li760f5402019-04-10 13:39:57 -0700471 return FSERROR;
472 }
473
474 if (!(boot->flags & FAT32) && !dir->parent) {
475 off = boot->bpbResSectors + boot->bpbFATs *
476 boot->FATsecs;
477 } else {
Xin Lia4cbc882019-10-01 15:09:31 -0700478 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
Xin Li760f5402019-04-10 13:39:57 -0700479 }
480
481 /*
482 * We only need to check the first two entries of the directory,
483 * which is found in the first sector of the directory entry,
484 * so read in only the first sector.
485 */
486 buf = malloc(boot->bpbBytesPerSec);
487 if (buf == NULL) {
488 perr("No space for directory buffer (%u)",
489 boot->bpbBytesPerSec);
490 return FSFATAL;
491 }
492
493 off *= boot->bpbBytesPerSec;
Xin Li21386e02020-01-03 09:22:03 -0800494 if (lseek(fd, off, SEEK_SET) != off ||
495 read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) {
Xin Li760f5402019-04-10 13:39:57 -0700496 perr("Unable to read directory");
497 free(buf);
498 return FSFATAL;
499 }
500
501 /*
502 * Both `.' and `..' must be present and be the first two entries
503 * and be ATTR_DIRECTORY of a valid subdirectory.
504 */
505 cp = buf;
506 if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
507 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
508 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
509 retval |= FSERROR;
510 }
511 cp += 32;
512 if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
513 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
514 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
515 retval |= FSERROR;
516 }
517
518 free(buf);
519 return retval;
520}
521
Xin Li1e1dad62019-04-10 13:38:23 -0700522/*
523 * Read a directory and
524 * - resolve long name records
525 * - enter file and directory records into the parent's list
526 * - push directories onto the todo-stack
527 */
528static int
Xin Li21386e02020-01-03 09:22:03 -0800529readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir)
Xin Li1e1dad62019-04-10 13:38:23 -0700530{
Xin Li21386e02020-01-03 09:22:03 -0800531 struct bootblock *boot;
Xin Li1e1dad62019-04-10 13:38:23 -0700532 struct dosDirEntry dirent, *d;
533 u_char *p, *vallfn, *invlfn, *empty;
534 off_t off;
Xin Li21386e02020-01-03 09:22:03 -0800535 int fd, i, j, k, iosize, entries;
536 bool is_legacyroot;
Xin Li1e1dad62019-04-10 13:38:23 -0700537 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
538 char *t;
539 u_int lidx = 0;
540 int shortSum;
541 int mod = FSOK;
Xin Li21386e02020-01-03 09:22:03 -0800542 size_t dirclusters;
Xin Li1e1dad62019-04-10 13:38:23 -0700543#define THISMOD 0x8000 /* Only used within this routine */
544
Xin Li21386e02020-01-03 09:22:03 -0800545 boot = fat_get_boot(fat);
546 fd = fat_get_fd(fat);
547
Xin Li1e1dad62019-04-10 13:38:23 -0700548 cl = dir->head;
Xin Li21386e02020-01-03 09:22:03 -0800549 if (dir->parent && (!fat_is_valid_cl(fat, cl))) {
Xin Li1e1dad62019-04-10 13:38:23 -0700550 /*
551 * Already handled somewhere else.
552 */
553 return FSOK;
554 }
555 shortSum = -1;
556 vallfn = invlfn = empty = NULL;
Xin Li21386e02020-01-03 09:22:03 -0800557
558 /*
559 * If we are checking the legacy root (for FAT12/FAT16),
560 * we will operate on the whole directory; otherwise, we
561 * will operate on one cluster at a time, and also take
562 * this opportunity to examine the chain.
563 *
564 * Derive how many entries we are going to encounter from
565 * the I/O size.
566 */
567 is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32));
568 if (is_legacyroot) {
569 iosize = boot->bpbRootDirEnts * 32;
570 entries = boot->bpbRootDirEnts;
571 } else {
572 iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec;
573 entries = iosize / 32;
574 mod |= checkchain(fat, dir->head, &dirclusters);
575 }
576
Xin Li1e1dad62019-04-10 13:38:23 -0700577 do {
Xin Li21386e02020-01-03 09:22:03 -0800578 if (is_legacyroot) {
579 /*
580 * Special case for FAT12/FAT16 root -- read
581 * in the whole root directory.
582 */
Xin Li1e1dad62019-04-10 13:38:23 -0700583 off = boot->bpbResSectors + boot->bpbFATs *
584 boot->FATsecs;
585 } else {
Xin Li21386e02020-01-03 09:22:03 -0800586 /*
587 * Otherwise, read in a cluster of the
588 * directory.
589 */
Xin Lia4cbc882019-10-01 15:09:31 -0700590 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
Xin Li1e1dad62019-04-10 13:38:23 -0700591 }
592
593 off *= boot->bpbBytesPerSec;
Xin Li21386e02020-01-03 09:22:03 -0800594 if (lseek(fd, off, SEEK_SET) != off ||
595 read(fd, buffer, iosize) != iosize) {
Xin Li1e1dad62019-04-10 13:38:23 -0700596 perr("Unable to read directory");
597 return FSFATAL;
598 }
Xin Li21386e02020-01-03 09:22:03 -0800599
600 for (p = buffer, i = 0; i < entries; i++, p += 32) {
Xin Li1e1dad62019-04-10 13:38:23 -0700601 if (dir->fsckflags & DIREMPWARN) {
602 *p = SLOT_EMPTY;
603 continue;
604 }
605
606 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
607 if (*p == SLOT_EMPTY) {
608 dir->fsckflags |= DIREMPTY;
609 empty = p;
610 empcl = cl;
611 }
612 continue;
613 }
614
615 if (dir->fsckflags & DIREMPTY) {
616 if (!(dir->fsckflags & DIREMPWARN)) {
617 pwarn("%s has entries after end of directory\n",
618 fullpath(dir));
619 if (ask(1, "Extend")) {
620 u_char *q;
621
622 dir->fsckflags &= ~DIREMPTY;
Xin Li21386e02020-01-03 09:22:03 -0800623 if (delete(fat,
Xin Li1e1dad62019-04-10 13:38:23 -0700624 empcl, empty - buffer,
625 cl, p - buffer, 1) == FSFATAL)
626 return FSFATAL;
Xin Li760f5402019-04-10 13:39:57 -0700627 q = ((empcl == cl) ? empty : buffer);
628 assert(q != NULL);
Xin Li1e1dad62019-04-10 13:38:23 -0700629 for (; q < p; q += 32)
630 *q = SLOT_DELETED;
631 mod |= THISMOD|FSDIRMOD;
632 } else if (ask(0, "Truncate"))
633 dir->fsckflags |= DIREMPWARN;
634 }
635 if (dir->fsckflags & DIREMPWARN) {
636 *p = SLOT_DELETED;
637 mod |= THISMOD|FSDIRMOD;
638 continue;
639 } else if (dir->fsckflags & DIREMPTY)
640 mod |= FSERROR;
641 empty = NULL;
642 }
643
644 if (p[11] == ATTR_WIN95) {
645 if (*p & LRFIRST) {
646 if (shortSum != -1) {
647 if (!invlfn) {
648 invlfn = vallfn;
649 invcl = valcl;
650 }
651 }
652 memset(longName, 0, sizeof longName);
653 shortSum = p[13];
654 vallfn = p;
655 valcl = cl;
656 } else if (shortSum != p[13]
657 || lidx != (*p & LRNOMASK)) {
658 if (!invlfn) {
659 invlfn = vallfn;
660 invcl = valcl;
661 }
662 if (!invlfn) {
663 invlfn = p;
664 invcl = cl;
665 }
666 vallfn = NULL;
667 }
668 lidx = *p & LRNOMASK;
Xin Li47045d82019-06-12 10:02:31 -0700669 if (lidx == 0) {
670 pwarn("invalid long name\n");
671 if (!invlfn) {
672 invlfn = vallfn;
673 invcl = valcl;
674 }
675 vallfn = NULL;
676 continue;
677 }
Xin Li1e1dad62019-04-10 13:38:23 -0700678 t = longName + --lidx * 13;
679 for (k = 1; k < 11 && t < longName +
680 sizeof(longName); k += 2) {
681 if (!p[k] && !p[k + 1])
682 break;
683 *t++ = p[k];
684 /*
685 * Warn about those unusable chars in msdosfs here? XXX
686 */
687 if (p[k + 1])
688 t[-1] = '?';
689 }
690 if (k >= 11)
691 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
692 if (!p[k] && !p[k + 1])
693 break;
694 *t++ = p[k];
695 if (p[k + 1])
696 t[-1] = '?';
697 }
698 if (k >= 26)
699 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
700 if (!p[k] && !p[k + 1])
701 break;
702 *t++ = p[k];
703 if (p[k + 1])
704 t[-1] = '?';
705 }
706 if (t >= longName + sizeof(longName)) {
707 pwarn("long filename too long\n");
708 if (!invlfn) {
709 invlfn = vallfn;
710 invcl = valcl;
711 }
712 vallfn = NULL;
713 }
714 if (p[26] | (p[27] << 8)) {
715 pwarn("long filename record cluster start != 0\n");
716 if (!invlfn) {
717 invlfn = vallfn;
718 invcl = cl;
719 }
720 vallfn = NULL;
721 }
722 continue; /* long records don't carry further
723 * information */
724 }
725
726 /*
727 * This is a standard msdosfs directory entry.
728 */
729 memset(&dirent, 0, sizeof dirent);
730
731 /*
732 * it's a short name record, but we need to know
733 * more, so get the flags first.
734 */
735 dirent.flags = p[11];
736
737 /*
738 * Translate from 850 to ISO here XXX
739 */
740 for (j = 0; j < 8; j++)
741 dirent.name[j] = p[j];
742 dirent.name[8] = '\0';
743 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
744 dirent.name[k] = '\0';
745 if (k < 0 || dirent.name[k] != '\0')
746 k++;
747 if (dirent.name[0] == SLOT_E5)
748 dirent.name[0] = 0xe5;
749
750 if (dirent.flags & ATTR_VOLUME) {
751 if (vallfn || invlfn) {
Xin Li21386e02020-01-03 09:22:03 -0800752 mod |= removede(fat,
Xin Li1e1dad62019-04-10 13:38:23 -0700753 invlfn ? invlfn : vallfn, p,
754 invlfn ? invcl : valcl, -1, 0,
755 fullpath(dir), 2);
756 vallfn = NULL;
757 invlfn = NULL;
758 }
759 continue;
760 }
761
762 if (p[8] != ' ')
763 dirent.name[k++] = '.';
764 for (j = 0; j < 3; j++)
765 dirent.name[k++] = p[j+8];
766 dirent.name[k] = '\0';
767 for (k--; k >= 0 && dirent.name[k] == ' '; k--)
768 dirent.name[k] = '\0';
769
770 if (vallfn && shortSum != calcShortSum(p)) {
771 if (!invlfn) {
772 invlfn = vallfn;
773 invcl = valcl;
774 }
775 vallfn = NULL;
776 }
777 dirent.head = p[26] | (p[27] << 8);
778 if (boot->ClustMask == CLUST32_MASK)
779 dirent.head |= (p[20] << 16) | (p[21] << 24);
780 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
781 if (vallfn) {
782 strlcpy(dirent.lname, longName,
783 sizeof(dirent.lname));
784 longName[0] = '\0';
785 shortSum = -1;
786 }
787
788 dirent.parent = dir;
789 dirent.next = dir->child;
790
791 if (invlfn) {
Xin Li21386e02020-01-03 09:22:03 -0800792 mod |= k = removede(fat,
Xin Li1e1dad62019-04-10 13:38:23 -0700793 invlfn, vallfn ? vallfn : p,
794 invcl, vallfn ? valcl : cl, cl,
795 fullpath(&dirent), 0);
796 if (mod & FSFATAL)
797 return FSFATAL;
798 if (vallfn
799 ? (valcl == cl && vallfn != buffer)
800 : p != buffer)
801 if (k & FSDIRMOD)
802 mod |= THISMOD;
803 }
804
805 vallfn = NULL; /* not used any longer */
806 invlfn = NULL;
807
Xin Li21386e02020-01-03 09:22:03 -0800808 /*
809 * Check if the directory entry is sane.
810 *
811 * '.' and '..' are skipped, their sanity is
812 * checked somewhere else.
813 *
814 * For everything else, check if we have a new,
815 * valid cluster chain (beginning of a file or
816 * directory that was never previously claimed
817 * by another file) when it's a non-empty file
818 * or a directory. The sanity of the cluster
819 * chain is checked at a later time when we
820 * traverse into the directory, or examine the
821 * file's directory entry.
822 *
823 * The only possible fix is to delete the entry
824 * if it's a directory; for file, we have to
825 * truncate the size to 0.
826 */
827 if (!(dirent.flags & ATTR_DIRECTORY) ||
828 (strcmp(dirent.name, ".") != 0 &&
829 strcmp(dirent.name, "..") != 0)) {
830 if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) &&
831 ((!fat_is_valid_cl(fat, dirent.head) ||
832 !fat_is_cl_head(fat, dirent.head)))) {
833 if (!fat_is_valid_cl(fat, dirent.head)) {
834 pwarn("%s starts with cluster out of range(%u)\n",
835 fullpath(&dirent),
836 dirent.head);
837 } else {
838 pwarn("%s doesn't start a new cluster chain\n",
839 fullpath(&dirent));
840 }
841
842 if (dirent.flags & ATTR_DIRECTORY) {
843 if (ask(0, "Remove")) {
844 *p = SLOT_DELETED;
845 mod |= THISMOD|FSDIRMOD;
846 } else
847 mod |= FSERROR;
848 continue;
849 } else {
850 if (ask(1, "Truncate")) {
851 p[28] = p[29] = p[30] = p[31] = 0;
852 p[26] = p[27] = 0;
853 if (boot->ClustMask == CLUST32_MASK)
854 p[20] = p[21] = 0;
855 dirent.size = 0;
856 dirent.head = 0;
857 mod |= THISMOD|FSDIRMOD;
858 } else
859 mod |= FSERROR;
860 }
Xin Li1e1dad62019-04-10 13:38:23 -0700861 }
862 }
Xin Li1e1dad62019-04-10 13:38:23 -0700863 if (dirent.flags & ATTR_DIRECTORY) {
864 /*
865 * gather more info for directories
866 */
867 struct dirTodoNode *n;
868
869 if (dirent.size) {
870 pwarn("Directory %s has size != 0\n",
871 fullpath(&dirent));
872 if (ask(1, "Correct")) {
873 p[28] = p[29] = p[30] = p[31] = 0;
874 dirent.size = 0;
875 mod |= THISMOD|FSDIRMOD;
876 } else
877 mod |= FSERROR;
878 }
879 /*
880 * handle `.' and `..' specially
881 */
882 if (strcmp(dirent.name, ".") == 0) {
883 if (dirent.head != dir->head) {
884 pwarn("`.' entry in %s has incorrect start cluster\n",
885 fullpath(dir));
886 if (ask(1, "Correct")) {
887 dirent.head = dir->head;
888 p[26] = (u_char)dirent.head;
889 p[27] = (u_char)(dirent.head >> 8);
890 if (boot->ClustMask == CLUST32_MASK) {
891 p[20] = (u_char)(dirent.head >> 16);
892 p[21] = (u_char)(dirent.head >> 24);
893 }
894 mod |= THISMOD|FSDIRMOD;
895 } else
896 mod |= FSERROR;
897 }
898 continue;
Xin Li21386e02020-01-03 09:22:03 -0800899 } else if (strcmp(dirent.name, "..") == 0) {
Xin Li1e1dad62019-04-10 13:38:23 -0700900 if (dir->parent) { /* XXX */
901 if (!dir->parent->parent) {
902 if (dirent.head) {
903 pwarn("`..' entry in %s has non-zero start cluster\n",
904 fullpath(dir));
905 if (ask(1, "Correct")) {
906 dirent.head = 0;
907 p[26] = p[27] = 0;
908 if (boot->ClustMask == CLUST32_MASK)
909 p[20] = p[21] = 0;
910 mod |= THISMOD|FSDIRMOD;
911 } else
912 mod |= FSERROR;
913 }
914 } else if (dirent.head != dir->parent->head) {
915 pwarn("`..' entry in %s has incorrect start cluster\n",
916 fullpath(dir));
917 if (ask(1, "Correct")) {
918 dirent.head = dir->parent->head;
919 p[26] = (u_char)dirent.head;
920 p[27] = (u_char)(dirent.head >> 8);
921 if (boot->ClustMask == CLUST32_MASK) {
922 p[20] = (u_char)(dirent.head >> 16);
923 p[21] = (u_char)(dirent.head >> 24);
924 }
925 mod |= THISMOD|FSDIRMOD;
926 } else
927 mod |= FSERROR;
928 }
929 }
930 continue;
Xin Li760f5402019-04-10 13:39:57 -0700931 } else {
932 /*
933 * Only one directory entry can point
934 * to dir->head, it's '.'.
935 */
936 if (dirent.head == dir->head) {
937 pwarn("%s entry in %s has incorrect start cluster\n",
938 dirent.name, fullpath(dir));
939 if (ask(1, "Remove")) {
940 *p = SLOT_DELETED;
941 mod |= THISMOD|FSDIRMOD;
942 } else
943 mod |= FSERROR;
944 continue;
Xin Li21386e02020-01-03 09:22:03 -0800945 } else if ((check_subdirectory(fat,
Xin Li760f5402019-04-10 13:39:57 -0700946 &dirent) & FSERROR) == FSERROR) {
947 /*
948 * A subdirectory should have
949 * a dot (.) entry and a dot-dot
950 * (..) entry of ATTR_DIRECTORY,
951 * we will inspect further when
952 * traversing into it.
953 */
954 if (ask(1, "Remove")) {
955 *p = SLOT_DELETED;
956 mod |= THISMOD|FSDIRMOD;
957 } else
958 mod |= FSERROR;
959 continue;
960 }
Xin Li1e1dad62019-04-10 13:38:23 -0700961 }
962
963 /* create directory tree node */
964 if (!(d = newDosDirEntry())) {
965 perr("No space for directory");
966 return FSFATAL;
967 }
968 memcpy(d, &dirent, sizeof(struct dosDirEntry));
969 /* link it into the tree */
970 dir->child = d;
971
972 /* Enter this directory into the todo list */
973 if (!(n = newDirTodo())) {
974 perr("No space for todo list");
975 return FSFATAL;
976 }
977 n->next = pendingDirectories;
978 n->dir = d;
979 pendingDirectories = n;
980 } else {
Xin Li21386e02020-01-03 09:22:03 -0800981 mod |= k = checksize(fat, p, &dirent);
Xin Li1e1dad62019-04-10 13:38:23 -0700982 if (k & FSDIRMOD)
983 mod |= THISMOD;
984 }
985 boot->NumFiles++;
986 }
987
Xin Li21386e02020-01-03 09:22:03 -0800988 if (is_legacyroot) {
989 /*
990 * Don't bother to write back right now because
991 * we may continue to make modification to the
992 * non-FAT32 root directory below.
993 */
Xin Li1e1dad62019-04-10 13:38:23 -0700994 break;
Xin Li21386e02020-01-03 09:22:03 -0800995 } else if (mod & THISMOD) {
996 if (lseek(fd, off, SEEK_SET) != off
997 || write(fd, buffer, iosize) != iosize) {
Xin Li1e1dad62019-04-10 13:38:23 -0700998 perr("Unable to write directory");
999 return FSFATAL;
1000 }
1001 mod &= ~THISMOD;
1002 }
Xin Li21386e02020-01-03 09:22:03 -08001003 } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl))));
Xin Li1e1dad62019-04-10 13:38:23 -07001004 if (invlfn || vallfn)
Xin Li21386e02020-01-03 09:22:03 -08001005 mod |= removede(fat,
Xin Li1e1dad62019-04-10 13:38:23 -07001006 invlfn ? invlfn : vallfn, p,
1007 invlfn ? invcl : valcl, -1, 0,
1008 fullpath(dir), 1);
1009
Xin Li21386e02020-01-03 09:22:03 -08001010 /*
1011 * The root directory of non-FAT32 filesystems is in a special
1012 * area and may have been modified above removede() without
1013 * being written out.
Xin Li1e1dad62019-04-10 13:38:23 -07001014 */
Xin Li21386e02020-01-03 09:22:03 -08001015 if ((mod & FSDIRMOD) && is_legacyroot) {
1016 if (lseek(fd, off, SEEK_SET) != off
1017 || write(fd, buffer, iosize) != iosize) {
Xin Li1e1dad62019-04-10 13:38:23 -07001018 perr("Unable to write directory");
1019 return FSFATAL;
1020 }
1021 mod &= ~THISMOD;
1022 }
1023 return mod & ~THISMOD;
1024}
1025
1026int
Xin Li21386e02020-01-03 09:22:03 -08001027handleDirTree(struct fat_descriptor *fat)
Xin Li1e1dad62019-04-10 13:38:23 -07001028{
1029 int mod;
1030
Xin Li21386e02020-01-03 09:22:03 -08001031 mod = readDosDirSection(fat, rootDir);
Xin Li1e1dad62019-04-10 13:38:23 -07001032 if (mod & FSFATAL)
1033 return FSFATAL;
1034
1035 /*
1036 * process the directory todo list
1037 */
1038 while (pendingDirectories) {
1039 struct dosDirEntry *dir = pendingDirectories->dir;
1040 struct dirTodoNode *n = pendingDirectories->next;
1041
1042 /*
1043 * remove TODO entry now, the list might change during
1044 * directory reads
1045 */
1046 freeDirTodo(pendingDirectories);
1047 pendingDirectories = n;
1048
1049 /*
1050 * handle subdirectory
1051 */
Xin Li21386e02020-01-03 09:22:03 -08001052 mod |= readDosDirSection(fat, dir);
Xin Li1e1dad62019-04-10 13:38:23 -07001053 if (mod & FSFATAL)
1054 return FSFATAL;
1055 }
1056
1057 return mod;
1058}
1059
1060/*
1061 * Try to reconnect a FAT chain into dir
1062 */
1063static u_char *lfbuf;
1064static cl_t lfcl;
1065static off_t lfoff;
1066
1067int
Xin Li21386e02020-01-03 09:22:03 -08001068reconnect(struct fat_descriptor *fat, cl_t head, size_t length)
Xin Li1e1dad62019-04-10 13:38:23 -07001069{
Xin Li21386e02020-01-03 09:22:03 -08001070 struct bootblock *boot = fat_get_boot(fat);
Xin Li1e1dad62019-04-10 13:38:23 -07001071 struct dosDirEntry d;
Xin Li21386e02020-01-03 09:22:03 -08001072 int len, dosfs;
Xin Li1e1dad62019-04-10 13:38:23 -07001073 u_char *p;
1074
Xin Li21386e02020-01-03 09:22:03 -08001075 dosfs = fat_get_fd(fat);
1076
Xin Li1e1dad62019-04-10 13:38:23 -07001077 if (!ask(1, "Reconnect"))
1078 return FSERROR;
1079
1080 if (!lostDir) {
1081 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
1082 if (!strcmp(lostDir->name, LOSTDIR))
1083 break;
1084 }
1085 if (!lostDir) { /* Create LOSTDIR? XXX */
1086 pwarn("No %s directory\n", LOSTDIR);
1087 return FSERROR;
1088 }
1089 }
1090 if (!lfbuf) {
1091 lfbuf = malloc(boot->ClusterSize);
1092 if (!lfbuf) {
1093 perr("No space for buffer");
1094 return FSFATAL;
1095 }
1096 p = NULL;
1097 } else
1098 p = lfbuf;
1099 while (1) {
1100 if (p)
1101 for (; p < lfbuf + boot->ClusterSize; p += 32)
1102 if (*p == SLOT_EMPTY
1103 || *p == SLOT_DELETED)
1104 break;
1105 if (p && p < lfbuf + boot->ClusterSize)
1106 break;
Xin Li21386e02020-01-03 09:22:03 -08001107 lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head;
Xin Li1e1dad62019-04-10 13:38:23 -07001108 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
1109 /* Extend LOSTDIR? XXX */
1110 pwarn("No space in %s\n", LOSTDIR);
Xin Li760f5402019-04-10 13:39:57 -07001111 lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
Xin Li1e1dad62019-04-10 13:38:23 -07001112 return FSERROR;
1113 }
Xin Lia4cbc882019-10-01 15:09:31 -07001114 lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
1115 + boot->FirstCluster * boot->bpbBytesPerSec;
1116
Xin Li1e1dad62019-04-10 13:38:23 -07001117 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1118 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1119 perr("could not read LOST.DIR");
1120 return FSFATAL;
1121 }
1122 p = lfbuf;
1123 }
1124
1125 boot->NumFiles++;
1126 /* Ensure uniqueness of entry here! XXX */
1127 memset(&d, 0, sizeof d);
1128 /* worst case -1 = 4294967295, 10 digits */
1129 len = snprintf(d.name, sizeof(d.name), "%u", head);
1130 d.flags = 0;
1131 d.head = head;
Xin Li21386e02020-01-03 09:22:03 -08001132 d.size = length * boot->ClusterSize;
Xin Li1e1dad62019-04-10 13:38:23 -07001133
1134 memcpy(p, d.name, len);
1135 memset(p + len, ' ', 11 - len);
1136 memset(p + 11, 0, 32 - 11);
1137 p[26] = (u_char)d.head;
1138 p[27] = (u_char)(d.head >> 8);
1139 if (boot->ClustMask == CLUST32_MASK) {
1140 p[20] = (u_char)(d.head >> 16);
1141 p[21] = (u_char)(d.head >> 24);
1142 }
1143 p[28] = (u_char)d.size;
1144 p[29] = (u_char)(d.size >> 8);
1145 p[30] = (u_char)(d.size >> 16);
1146 p[31] = (u_char)(d.size >> 24);
Xin Li1e1dad62019-04-10 13:38:23 -07001147 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
1148 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
1149 perr("could not write LOST.DIR");
1150 return FSFATAL;
1151 }
1152 return FSDIRMOD;
1153}
1154
1155void
1156finishlf(void)
1157{
1158 if (lfbuf)
1159 free(lfbuf);
1160 lfbuf = NULL;
1161}