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