blob: add8222935411eb4aa21954928cbdf361f2d5b60 [file] [log] [blame]
Matt Mower18794c82015-11-11 16:22:45 -06001/* check.c - Check and repair a PC/MS-DOS filesystem
bigbiff bigbiff9c754052013-01-09 09:09:08 -05002
3 Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
4 Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
Matt Mower18794c82015-11-11 16:22:45 -06005 Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
bigbiff bigbiff9c754052013-01-09 09:09:08 -05006
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19
Matt Mower18794c82015-11-11 16:22:45 -060020 The complete text of the GNU General Public License
bigbiff bigbiff9c754052013-01-09 09:09:08 -050021 can be found in /usr/share/common-licenses/GPL-3 file.
22*/
23
24/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
25 * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <limits.h>
31#include <time.h>
32
33#include "common.h"
Matt Mower18794c82015-11-11 16:22:45 -060034#include "fsck.fat.h"
bigbiff bigbiff9c754052013-01-09 09:09:08 -050035#include "io.h"
36#include "fat.h"
37#include "file.h"
38#include "lfn.h"
39#include "check.h"
40
41static DOS_FILE *root;
42
43/* get start field of a dir entry */
44#define FSTART(p,fs) \
Matt Mower18794c82015-11-11 16:22:45 -060045 ((uint32_t)le16toh(p->dir_ent.start) | \
46 (fs->fat_bits == 32 ? le16toh(p->dir_ent.starthi) << 16 : 0))
bigbiff bigbiff9c754052013-01-09 09:09:08 -050047
48#define MODIFY(p,i,v) \
49 do { \
50 if (p->offset) { \
51 p->dir_ent.i = v; \
52 fs_write(p->offset+offsetof(DIR_ENT,i), \
53 sizeof(p->dir_ent.i),&p->dir_ent.i); \
54 } \
55 } while(0)
56
57#define MODIFY_START(p,v,fs) \
58 do { \
Matt Mower18794c82015-11-11 16:22:45 -060059 uint32_t __v = (v); \
bigbiff bigbiff9c754052013-01-09 09:09:08 -050060 if (!p->offset) { \
61 /* writing to fake entry for FAT32 root dir */ \
62 if (!__v) die("Oops, deleting FAT32 root dir!"); \
63 fs->root_cluster = __v; \
Matt Mower18794c82015-11-11 16:22:45 -060064 p->dir_ent.start = htole16(__v&0xffff); \
65 p->dir_ent.starthi = htole16(__v>>16); \
66 __v = htole32(__v); \
bigbiff bigbiff9c754052013-01-09 09:09:08 -050067 fs_write((loff_t)offsetof(struct boot_sector,root_cluster), \
68 sizeof(((struct boot_sector *)0)->root_cluster), \
69 &__v); \
70 } \
71 else { \
Matt Mower18794c82015-11-11 16:22:45 -060072 MODIFY(p,start,htole16((__v)&0xffff)); \
bigbiff bigbiff9c754052013-01-09 09:09:08 -050073 if (fs->fat_bits == 32) \
Matt Mower18794c82015-11-11 16:22:45 -060074 MODIFY(p,starthi,htole16((__v)>>16)); \
bigbiff bigbiff9c754052013-01-09 09:09:08 -050075 } \
76 } while(0)
77
78loff_t alloc_rootdir_entry(DOS_FS * fs, DIR_ENT * de, const char *pattern)
79{
80 static int curr_num = 0;
81 loff_t offset;
82
83 if (fs->root_cluster) {
84 DIR_ENT d2;
85 int i = 0, got = 0;
Matt Mower18794c82015-11-11 16:22:45 -060086 uint32_t clu_num, prev = 0;
bigbiff bigbiff9c754052013-01-09 09:09:08 -050087 loff_t offset2;
88
89 clu_num = fs->root_cluster;
90 offset = cluster_start(fs, clu_num);
91 while (clu_num > 0 && clu_num != -1) {
92 fs_read(offset, sizeof(DIR_ENT), &d2);
93 if (IS_FREE(d2.name) && d2.attr != VFAT_LN_ATTR) {
94 got = 1;
95 break;
96 }
97 i += sizeof(DIR_ENT);
98 offset += sizeof(DIR_ENT);
99 if ((i % fs->cluster_size) == 0) {
100 prev = clu_num;
101 if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1)
102 break;
103 offset = cluster_start(fs, clu_num);
104 }
105 }
106 if (!got) {
107 /* no free slot, need to extend root dir: alloc next free cluster
108 * after previous one */
109 if (!prev)
110 die("Root directory has no cluster allocated!");
111 for (clu_num = prev + 1; clu_num != prev; clu_num++) {
112 FAT_ENTRY entry;
113
114 if (clu_num >= fs->clusters + 2)
115 clu_num = 2;
116 get_fat(&entry, fs->fat, clu_num, fs);
117 if (!entry.value)
118 break;
119 }
120 if (clu_num == prev)
121 die("Root directory full and no free cluster");
122 set_fat(fs, prev, clu_num);
123 set_fat(fs, clu_num, -1);
124 set_owner(fs, clu_num, get_owner(fs, fs->root_cluster));
125 /* clear new cluster */
126 memset(&d2, 0, sizeof(d2));
127 offset = cluster_start(fs, clu_num);
128 for (i = 0; i < fs->cluster_size; i += sizeof(DIR_ENT))
129 fs_write(offset + i, sizeof(d2), &d2);
130 }
131 memset(de, 0, sizeof(DIR_ENT));
132 while (1) {
133 char expanded[12];
134 sprintf(expanded, pattern, curr_num);
Matt Mower18794c82015-11-11 16:22:45 -0600135 memcpy(de->name, expanded, 8);
136 memcpy(de->ext, expanded + 8, 3);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500137 clu_num = fs->root_cluster;
138 i = 0;
139 offset2 = cluster_start(fs, clu_num);
140 while (clu_num > 0 && clu_num != -1) {
141 fs_read(offset2, sizeof(DIR_ENT), &d2);
142 if (offset2 != offset &&
143 !strncmp((const char *)d2.name, (const char *)de->name,
144 MSDOS_NAME))
145 break;
146 i += sizeof(DIR_ENT);
147 offset2 += sizeof(DIR_ENT);
148 if ((i % fs->cluster_size) == 0) {
149 if ((clu_num = next_cluster(fs, clu_num)) == 0 ||
150 clu_num == -1)
151 break;
152 offset2 = cluster_start(fs, clu_num);
153 }
154 }
155 if (clu_num == 0 || clu_num == -1)
156 break;
157 if (++curr_num >= 10000)
158 die("Unable to create unique name");
159 }
160 } else {
161 DIR_ENT *root;
162 int next_free = 0, scan;
163
164 root = alloc(fs->root_entries * sizeof(DIR_ENT));
165 fs_read(fs->root_start, fs->root_entries * sizeof(DIR_ENT), root);
166
167 while (next_free < fs->root_entries)
168 if (IS_FREE(root[next_free].name) &&
169 root[next_free].attr != VFAT_LN_ATTR)
170 break;
171 else
172 next_free++;
173 if (next_free == fs->root_entries)
174 die("Root directory is full.");
175 offset = fs->root_start + next_free * sizeof(DIR_ENT);
176 memset(de, 0, sizeof(DIR_ENT));
177 while (1) {
Matt Mower18794c82015-11-11 16:22:45 -0600178 char expanded[12];
179 sprintf(expanded, pattern, curr_num);
180 memcpy(de->name, expanded, 8);
181 memcpy(de->ext, expanded + 8, 3);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500182 for (scan = 0; scan < fs->root_entries; scan++)
183 if (scan != next_free &&
184 !strncmp((const char *)root[scan].name,
185 (const char *)de->name, MSDOS_NAME))
186 break;
187 if (scan == fs->root_entries)
188 break;
189 if (++curr_num >= 10000)
190 die("Unable to create unique name");
191 }
192 free(root);
193 }
194 ++n_files;
195 return offset;
196}
197
198/**
199 * Construct a full path (starting with '/') for the specified dentry,
200 * relative to the partition. All components are "long" names where possible.
201 *
202 * @param[in] file Information about dentry (file or directory) of interest
203 *
204 * return Pointer to static string containing file's full path
205 */
206static char *path_name(DOS_FILE * file)
207{
208 static char path[PATH_MAX * 2];
209
210 if (!file)
211 *path = 0; /* Reached the root directory */
212 else {
213 if (strlen(path_name(file->parent)) > PATH_MAX)
214 die("Path name too long.");
215 if (strcmp(path, "/") != 0)
216 strcat(path, "/");
217
218 /* Append the long name to the path,
219 * or the short name if there isn't a long one
220 */
221 strcpy(strrchr(path, 0),
222 file->lfn ? file->lfn : file_name(file->dir_ent.name));
223 }
224 return path;
225}
226
Matt Mower18794c82015-11-11 16:22:45 -0600227static const int day_n[] =
228 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0 };
229/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500230
231/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */
232
Matt Mower18794c82015-11-11 16:22:45 -0600233static time_t date_dos2unix(unsigned short time, unsigned short date)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500234{
235 int month, year;
236 time_t secs;
237
238 month = ((date >> 5) & 15) - 1;
Matt Mower18794c82015-11-11 16:22:45 -0600239 if (month < 0) {
240 /* make sure that nothing bad happens if the month bits were zero */
241 month = 0;
242 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500243 year = date >> 9;
244 secs =
245 (time & 31) * 2 + 60 * ((time >> 5) & 63) + (time >> 11) * 3600 +
246 86400 * ((date & 31) - 1 + day_n[month] + (year / 4) + year * 365 -
247 ((year & 3) == 0 && month < 2 ? 1 : 0) + 3653);
248 /* days since 1.1.70 plus 80's leap day */
249 return secs;
250}
251
252static char *file_stat(DOS_FILE * file)
253{
254 static char temp[100];
255 struct tm *tm;
256 char tmp[100];
257 time_t date;
258
259 date =
Matt Mower18794c82015-11-11 16:22:45 -0600260 date_dos2unix(le16toh(file->dir_ent.time), le16toh(file->dir_ent.date));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500261 tm = localtime(&date);
262 strftime(tmp, 99, "%H:%M:%S %b %d %Y", tm);
Matt Mower18794c82015-11-11 16:22:45 -0600263 sprintf(temp, " Size %u bytes, date %s", le32toh(file->dir_ent.size), tmp);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500264 return temp;
265}
266
267static int bad_name(DOS_FILE * file)
268{
269 int i, spc, suspicious = 0;
Matt Mower18794c82015-11-11 16:22:45 -0600270 const char *bad_chars = atari_format ? "*?\\/:" : "*?<>|\"\\/:";
271 const unsigned char *name = file->dir_ent.name;
272 const unsigned char *ext = file->dir_ent.ext;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500273
274 /* Do not complain about (and auto-correct) the extended attribute files
275 * of OS/2. */
276 if (strncmp((const char *)name, "EA DATA SF", 11) == 0 ||
277 strncmp((const char *)name, "WP ROOT SF", 11) == 0)
278 return 0;
279
Matt Mower18794c82015-11-11 16:22:45 -0600280 /* check if we have neither a long filename nor a short name */
281 if ((file->lfn == NULL) && (file->dir_ent.lcase & FAT_NO_83NAME)) {
282 return 1;
283 }
284
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500285 /* don't complain about the dummy 11 bytes used by patched Linux
286 kernels */
287 if (file->dir_ent.lcase & FAT_NO_83NAME)
288 return 0;
289
290 for (i = 0; i < 8; i++) {
291 if (name[i] < ' ' || name[i] == 0x7f)
292 return 1;
293 if (name[i] > 0x7f)
294 ++suspicious;
295 if (strchr(bad_chars, name[i]))
296 return 1;
297 }
298
Matt Mower18794c82015-11-11 16:22:45 -0600299 for (i = 0; i < 3; i++) {
300 if (ext[i] < ' ' || ext[i] == 0x7f)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500301 return 1;
Matt Mower18794c82015-11-11 16:22:45 -0600302 if (ext[i] > 0x7f)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500303 ++suspicious;
Matt Mower18794c82015-11-11 16:22:45 -0600304 if (strchr(bad_chars, ext[i]))
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500305 return 1;
306 }
307
308 spc = 0;
309 for (i = 0; i < 8; i++) {
310 if (name[i] == ' ')
311 spc = 1;
312 else if (spc)
313 /* non-space after a space not allowed, space terminates the name
314 * part */
315 return 1;
316 }
317
318 spc = 0;
Matt Mower18794c82015-11-11 16:22:45 -0600319 for (i = 0; i < 3; i++) {
320 if (ext[i] == ' ')
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500321 spc = 1;
322 else if (spc)
Matt Mower18794c82015-11-11 16:22:45 -0600323 /* non-space after a space not allowed, space terminates the ext
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500324 * part */
325 return 1;
326 }
327
328 /* Under GEMDOS, chars >= 128 are never allowed. */
329 if (atari_format && suspicious)
330 return 1;
331
332 /* Under MS-DOS and Windows, chars >= 128 in short names are valid
333 * (but these characters can be visualised differently depending on
334 * local codepage: CP437, CP866, etc). The chars are all basically ok,
335 * so we shouldn't auto-correct such names. */
336 return 0;
337}
338
339static void lfn_remove(loff_t from, loff_t to)
340{
341 DIR_ENT empty;
342
343 /* New dir entry is zeroed except first byte, which is set to 0xe5.
344 * This is to avoid that some FAT-reading OSes (not Linux! ;) stop reading
345 * a directory at the first zero entry...
346 */
347 memset(&empty, 0, sizeof(empty));
348 empty.name[0] = DELETED_FLAG;
349
350 for (; from < to; from += sizeof(empty)) {
351 fs_write(from, sizeof(DIR_ENT), &empty);
352 }
353}
354
355static void drop_file(DOS_FS * fs, DOS_FILE * file)
356{
Matt Mower18794c82015-11-11 16:22:45 -0600357 uint32_t cluster;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500358
359 MODIFY(file, name[0], DELETED_FLAG);
360 if (file->lfn)
361 lfn_remove(file->lfn_offset, file->offset);
362 for (cluster = FSTART(file, fs); cluster > 0 && cluster <
363 fs->clusters + 2; cluster = next_cluster(fs, cluster))
364 set_owner(fs, cluster, NULL);
365 --n_files;
366}
367
Matt Mower18794c82015-11-11 16:22:45 -0600368static void truncate_file(DOS_FS * fs, DOS_FILE * file, uint32_t clusters)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500369{
370 int deleting;
Matt Mower18794c82015-11-11 16:22:45 -0600371 uint32_t walk, next;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500372
373 walk = FSTART(file, fs);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500374 if ((deleting = !clusters))
375 MODIFY_START(file, 0, fs);
376 while (walk > 0 && walk != -1) {
377 next = next_cluster(fs, walk);
378 if (deleting)
379 set_fat(fs, walk, 0);
380 else if ((deleting = !--clusters))
381 set_fat(fs, walk, -1);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500382 walk = next;
383 }
384}
385
386static void auto_rename(DOS_FILE * file)
387{
388 DOS_FILE *first, *walk;
Matt Mower18794c82015-11-11 16:22:45 -0600389 uint32_t number;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500390
391 if (!file->offset)
392 return; /* cannot rename FAT32 root dir */
393 first = file->parent ? file->parent->first : root;
394 number = 0;
395 while (1) {
396 char num[8];
Matt Mower18794c82015-11-11 16:22:45 -0600397 sprintf(num, "%07lu", (unsigned long)number);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500398 memcpy(file->dir_ent.name, "FSCK", 4);
399 memcpy(file->dir_ent.name + 4, num, 4);
400 memcpy(file->dir_ent.ext, num + 4, 3);
401 for (walk = first; walk; walk = walk->next)
402 if (walk != file
403 && !strncmp((const char *)walk->dir_ent.name,
404 (const char *)file->dir_ent.name, MSDOS_NAME))
405 break;
406 if (!walk) {
Matt Mower18794c82015-11-11 16:22:45 -0600407 if (file->dir_ent.lcase & FAT_NO_83NAME) {
408 /* as we only assign a new 8.3 filename, reset flag that 8.3 name is not
409 present */
410 file->dir_ent.lcase &= ~FAT_NO_83NAME;
411 /* reset the attributes, only keep DIR and VOLUME */
412 file->dir_ent.attr &= ~(ATTR_DIR | ATTR_VOLUME);
413 fs_write(file->offset, MSDOS_NAME + 2, file->dir_ent.name);
414 } else {
415 fs_write(file->offset, MSDOS_NAME, file->dir_ent.name);
416 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500417 if (file->lfn)
418 lfn_fix_checksum(file->lfn_offset, file->offset,
419 (const char *)file->dir_ent.name);
420 return;
421 }
422 number++;
423 if (number > 9999999) {
424 die("Too many files need repair.");
425 }
426 }
427 die("Can't generate a unique name.");
428}
429
430static void rename_file(DOS_FILE * file)
431{
432 unsigned char name[46];
433 unsigned char *walk, *here;
434
435 if (!file->offset) {
436 printf("Cannot rename FAT32 root dir\n");
437 return; /* cannot rename FAT32 root dir */
438 }
439 while (1) {
440 printf("New name: ");
441 fflush(stdout);
442 if (fgets((char *)name, 45, stdin)) {
443 if ((here = (unsigned char *)strchr((const char *)name, '\n')))
444 *here = 0;
445 for (walk = (unsigned char *)strrchr((const char *)name, 0);
446 walk >= name && (*walk == ' ' || *walk == '\t'); walk--) ;
447 walk[1] = 0;
448 for (walk = name; *walk == ' ' || *walk == '\t'; walk++) ;
449 if (file_cvt(walk, file->dir_ent.name)) {
Matt Mower18794c82015-11-11 16:22:45 -0600450 if (file->dir_ent.lcase & FAT_NO_83NAME) {
451 /* as we only assign a new 8.3 filename, reset flag that 8.3 name is not
452 present */
453 file->dir_ent.lcase &= ~FAT_NO_83NAME;
454 /* reset the attributes, only keep DIR and VOLUME */
455 file->dir_ent.attr &= ~(ATTR_DIR | ATTR_VOLUME);
456 fs_write(file->offset, MSDOS_NAME + 2, file->dir_ent.name);
457 } else {
458 fs_write(file->offset, MSDOS_NAME, file->dir_ent.name);
459 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500460 if (file->lfn)
461 lfn_fix_checksum(file->lfn_offset, file->offset,
462 (const char *)file->dir_ent.name);
463 return;
464 }
465 }
466 }
467}
468
469static int handle_dot(DOS_FS * fs, DOS_FILE * file, int dots)
470{
Matt Mower18794c82015-11-11 16:22:45 -0600471 const char *name;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500472
473 name =
474 strncmp((const char *)file->dir_ent.name, MSDOS_DOT,
475 MSDOS_NAME) ? ".." : ".";
476 if (!(file->dir_ent.attr & ATTR_DIR)) {
477 printf("%s\n Is a non-directory.\n", path_name(file));
478 if (interactive)
479 printf("1) Drop it\n2) Auto-rename\n3) Rename\n"
480 "4) Convert to directory\n");
481 else
482 printf(" Auto-renaming it.\n");
483 switch (interactive ? get_key("1234", "?") : '2') {
484 case '1':
485 drop_file(fs, file);
486 return 1;
487 case '2':
488 auto_rename(file);
489 printf(" Renamed to %s\n", file_name(file->dir_ent.name));
490 return 0;
491 case '3':
492 rename_file(file);
493 return 0;
494 case '4':
Matt Mower18794c82015-11-11 16:22:45 -0600495 MODIFY(file, size, htole32(0));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500496 MODIFY(file, attr, file->dir_ent.attr | ATTR_DIR);
497 break;
498 }
499 }
500 if (!dots) {
501 printf("Root contains directory \"%s\". Dropping it.\n", name);
502 drop_file(fs, file);
503 return 1;
504 }
505 return 0;
506}
507
508static int check_file(DOS_FS * fs, DOS_FILE * file)
509{
510 DOS_FILE *owner;
511 int restart;
Matt Mower18794c82015-11-11 16:22:45 -0600512 uint32_t expect, curr, this, clusters, prev, walk, clusters2;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500513
514 if (file->dir_ent.attr & ATTR_DIR) {
Matt Mower18794c82015-11-11 16:22:45 -0600515 if (le32toh(file->dir_ent.size)) {
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500516 printf("%s\n Directory has non-zero size. Fixing it.\n",
517 path_name(file));
Matt Mower18794c82015-11-11 16:22:45 -0600518 MODIFY(file, size, htole32(0));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500519 }
520 if (file->parent
521 && !strncmp((const char *)file->dir_ent.name, MSDOS_DOT,
522 MSDOS_NAME)) {
523 expect = FSTART(file->parent, fs);
524 if (FSTART(file, fs) != expect) {
Matt Mower18794c82015-11-11 16:22:45 -0600525 printf("%s\n Start (%lu) does not point to parent (%lu)\n",
526 path_name(file), (unsigned long)FSTART(file, fs), (long)expect);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500527 MODIFY_START(file, expect, fs);
528 }
529 return 0;
530 }
531 if (file->parent
532 && !strncmp((const char *)file->dir_ent.name, MSDOS_DOTDOT,
533 MSDOS_NAME)) {
534 expect =
535 file->parent->parent ? FSTART(file->parent->parent, fs) : 0;
536 if (fs->root_cluster && expect == fs->root_cluster)
537 expect = 0;
538 if (FSTART(file, fs) != expect) {
539 printf("%s\n Start (%lu) does not point to .. (%lu)\n",
Matt Mower18794c82015-11-11 16:22:45 -0600540 path_name(file), (unsigned long)FSTART(file, fs), (unsigned long)expect);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500541 MODIFY_START(file, expect, fs);
542 }
543 return 0;
544 }
545 if (FSTART(file, fs) == 0) {
546 printf("%s\n Start does point to root directory. Deleting dir. \n",
547 path_name(file));
548 MODIFY(file, name[0], DELETED_FLAG);
549 return 0;
550 }
551 }
Matt Mower18794c82015-11-11 16:22:45 -0600552 if (FSTART(file, fs) == 1) {
553 printf("%s\n Bad start cluster 1. Truncating file.\n",
554 path_name(file));
555 if (!file->offset)
556 die("Bad FAT32 root directory! (bad start cluster 1)\n");
557 MODIFY_START(file, 0, fs);
558 }
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500559 if (FSTART(file, fs) >= fs->clusters + 2) {
560 printf
561 ("%s\n Start cluster beyond limit (%lu > %lu). Truncating file.\n",
Matt Mower18794c82015-11-11 16:22:45 -0600562 path_name(file), (unsigned long)FSTART(file, fs), (unsigned long)(fs->clusters + 1));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500563 if (!file->offset)
Matt Mower18794c82015-11-11 16:22:45 -0600564 die("Bad FAT32 root directory! (start cluster beyond limit: %lu > %lu)\n",
565 (unsigned long)FSTART(file, fs), (unsigned long)(fs->clusters + 1));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500566 MODIFY_START(file, 0, fs);
567 }
568 clusters = prev = 0;
569 for (curr = FSTART(file, fs) ? FSTART(file, fs) :
570 -1; curr != -1; curr = next_cluster(fs, curr)) {
571 FAT_ENTRY curEntry;
572 get_fat(&curEntry, fs->fat, curr, fs);
573
574 if (!curEntry.value || bad_cluster(fs, curr)) {
575 printf("%s\n Contains a %s cluster (%lu). Assuming EOF.\n",
Matt Mower18794c82015-11-11 16:22:45 -0600576 path_name(file), curEntry.value ? "bad" : "free", (unsigned long)curr);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500577 if (prev)
578 set_fat(fs, prev, -1);
579 else if (!file->offset)
580 die("FAT32 root dir starts with a bad cluster!");
581 else
582 MODIFY_START(file, 0, fs);
583 break;
584 }
Matt Mower18794c82015-11-11 16:22:45 -0600585 if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) <=
586 (uint64_t)clusters * fs->cluster_size) {
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500587 printf
lambdadroida0a16352018-10-09 20:42:22 +0200588 ("%s\n File size is %u bytes, cluster chain length is > %llu "
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500589 "bytes.\n Truncating file to %u bytes.\n", path_name(file),
Matt Mower18794c82015-11-11 16:22:45 -0600590 le32toh(file->dir_ent.size),
lambdadroida0a16352018-10-09 20:42:22 +0200591 (unsigned long long)clusters * fs->cluster_size,
Matt Mower18794c82015-11-11 16:22:45 -0600592 le32toh(file->dir_ent.size));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500593 truncate_file(fs, file, clusters);
594 break;
595 }
596 if ((owner = get_owner(fs, curr))) {
597 int do_trunc = 0;
598 printf("%s and\n", path_name(owner));
599 printf("%s\n share clusters.\n", path_name(file));
600 clusters2 = 0;
601 for (walk = FSTART(owner, fs); walk > 0 && walk != -1; walk =
602 next_cluster(fs, walk))
603 if (walk == curr)
604 break;
605 else
606 clusters2++;
607 restart = file->dir_ent.attr & ATTR_DIR;
608 if (!owner->offset) {
609 printf(" Truncating second to %llu bytes because first "
610 "is FAT32 root dir.\n",
611 (unsigned long long)clusters2 * fs->cluster_size);
612 do_trunc = 2;
613 } else if (!file->offset) {
614 printf(" Truncating first to %llu bytes because second "
615 "is FAT32 root dir.\n",
616 (unsigned long long)clusters * fs->cluster_size);
617 do_trunc = 1;
618 } else if (interactive)
619 printf("1) Truncate first to %llu bytes%s\n"
620 "2) Truncate second to %llu bytes\n",
621 (unsigned long long)clusters * fs->cluster_size,
622 restart ? " and restart" : "",
623 (unsigned long long)clusters2 * fs->cluster_size);
624 else
625 printf(" Truncating second to %llu bytes.\n",
626 (unsigned long long)clusters2 * fs->cluster_size);
627 if (do_trunc != 2
628 && (do_trunc == 1
629 || (interactive && get_key("12", "?") == '1'))) {
630 prev = 0;
631 clusters = 0;
632 for (this = FSTART(owner, fs); this > 0 && this != -1; this =
633 next_cluster(fs, this)) {
634 if (this == curr) {
635 if (prev)
636 set_fat(fs, prev, -1);
637 else
638 MODIFY_START(owner, 0, fs);
639 MODIFY(owner, size,
Matt Mower18794c82015-11-11 16:22:45 -0600640 htole32((uint64_t)clusters *
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500641 fs->cluster_size));
642 if (restart)
643 return 1;
644 while (this > 0 && this != -1) {
645 set_owner(fs, this, NULL);
646 this = next_cluster(fs, this);
647 }
648 this = curr;
649 break;
650 }
651 clusters++;
652 prev = this;
653 }
654 if (this != curr)
655 die("Internal error: didn't find cluster %d in chain"
656 " starting at %d", curr, FSTART(owner, fs));
657 } else {
658 if (prev)
659 set_fat(fs, prev, -1);
660 else
661 MODIFY_START(file, 0, fs);
662 break;
663 }
664 }
665 set_owner(fs, curr, file);
666 clusters++;
667 prev = curr;
668 }
Matt Mower18794c82015-11-11 16:22:45 -0600669 if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) >
670 (uint64_t)clusters * fs->cluster_size) {
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500671 printf
672 ("%s\n File size is %u bytes, cluster chain length is %llu bytes."
673 "\n Truncating file to %llu bytes.\n", path_name(file),
Matt Mower18794c82015-11-11 16:22:45 -0600674 le32toh(file->dir_ent.size),
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500675 (unsigned long long)clusters * fs->cluster_size,
676 (unsigned long long)clusters * fs->cluster_size);
677 MODIFY(file, size,
Matt Mower18794c82015-11-11 16:22:45 -0600678 htole32((uint64_t)clusters * fs->cluster_size));
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500679 }
680 return 0;
681}
682
683static int check_files(DOS_FS * fs, DOS_FILE * start)
684{
685 while (start) {
686 if (check_file(fs, start))
687 return 1;
688 start = start->next;
689 }
690 return 0;
691}
692
693static int check_dir(DOS_FS * fs, DOS_FILE ** root, int dots)
694{
695 DOS_FILE *parent, **walk, **scan;
696 int dot, dotdot, skip, redo;
697 int good, bad;
698
699 if (!*root)
700 return 0;
701 parent = (*root)->parent;
702 good = bad = 0;
703 for (walk = root; *walk; walk = &(*walk)->next)
704 if (bad_name(*walk))
705 bad++;
706 else
707 good++;
708 if (*root && parent && good + bad > 4 && bad > good / 2) {
709 printf("%s\n Has a large number of bad entries. (%d/%d)\n",
710 path_name(parent), bad, good + bad);
711 if (!dots)
712 printf(" Not dropping root directory.\n");
713 else if (!interactive)
714 printf(" Not dropping it in auto-mode.\n");
715 else if (get_key("yn", "Drop directory ? (y/n)") == 'y') {
716 truncate_file(fs, parent, 0);
717 MODIFY(parent, name[0], DELETED_FLAG);
718 /* buglet: deleted directory stays in the list. */
719 return 1;
720 }
721 }
722 dot = dotdot = redo = 0;
723 walk = root;
724 while (*walk) {
725 if (!strncmp
726 ((const char *)((*walk)->dir_ent.name), MSDOS_DOT, MSDOS_NAME)
727 || !strncmp((const char *)((*walk)->dir_ent.name), MSDOS_DOTDOT,
728 MSDOS_NAME)) {
729 if (handle_dot(fs, *walk, dots)) {
730 *walk = (*walk)->next;
731 continue;
732 }
733 if (!strncmp
734 ((const char *)((*walk)->dir_ent.name), MSDOS_DOT, MSDOS_NAME))
735 dot++;
736 else
737 dotdot++;
738 }
739 if (!((*walk)->dir_ent.attr & ATTR_VOLUME) && bad_name(*walk)) {
740 puts(path_name(*walk));
741 printf(" Bad short file name (%s).\n",
742 file_name((*walk)->dir_ent.name));
743 if (interactive)
744 printf("1) Drop file\n2) Rename file\n3) Auto-rename\n"
745 "4) Keep it\n");
746 else
747 printf(" Auto-renaming it.\n");
748 switch (interactive ? get_key("1234", "?") : '3') {
749 case '1':
750 drop_file(fs, *walk);
751 walk = &(*walk)->next;
752 continue;
753 case '2':
754 rename_file(*walk);
755 redo = 1;
756 break;
757 case '3':
758 auto_rename(*walk);
759 printf(" Renamed to %s\n", file_name((*walk)->dir_ent.name));
760 break;
761 case '4':
762 break;
763 }
764 }
765 /* don't check for duplicates of the volume label */
766 if (!((*walk)->dir_ent.attr & ATTR_VOLUME)) {
767 scan = &(*walk)->next;
768 skip = 0;
769 while (*scan && !skip) {
770 if (!((*scan)->dir_ent.attr & ATTR_VOLUME) &&
771 !memcmp((*walk)->dir_ent.name, (*scan)->dir_ent.name,
772 MSDOS_NAME)) {
773 printf("%s\n Duplicate directory entry.\n First %s\n",
774 path_name(*walk), file_stat(*walk));
775 printf(" Second %s\n", file_stat(*scan));
776 if (interactive)
777 printf
778 ("1) Drop first\n2) Drop second\n3) Rename first\n"
779 "4) Rename second\n5) Auto-rename first\n"
780 "6) Auto-rename second\n");
781 else
782 printf(" Auto-renaming second.\n");
783 switch (interactive ? get_key("123456", "?") : '6') {
784 case '1':
785 drop_file(fs, *walk);
786 *walk = (*walk)->next;
787 skip = 1;
788 break;
789 case '2':
790 drop_file(fs, *scan);
791 *scan = (*scan)->next;
792 continue;
793 case '3':
794 rename_file(*walk);
795 printf(" Renamed to %s\n", path_name(*walk));
796 redo = 1;
797 break;
798 case '4':
799 rename_file(*scan);
800 printf(" Renamed to %s\n", path_name(*walk));
801 redo = 1;
802 break;
803 case '5':
804 auto_rename(*walk);
805 printf(" Renamed to %s\n",
806 file_name((*walk)->dir_ent.name));
807 break;
808 case '6':
809 auto_rename(*scan);
810 printf(" Renamed to %s\n",
811 file_name((*scan)->dir_ent.name));
812 break;
813 }
814 }
815 scan = &(*scan)->next;
816 }
817 if (skip)
818 continue;
819 }
820 if (!redo)
821 walk = &(*walk)->next;
822 else {
823 walk = root;
824 dot = dotdot = redo = 0;
825 }
826 }
827 if (dots && !dot)
828 printf("%s\n \".\" is missing. Can't fix this yet.\n",
829 path_name(parent));
830 if (dots && !dotdot)
831 printf("%s\n \"..\" is missing. Can't fix this yet.\n",
832 path_name(parent));
833 return 0;
834}
835
836/**
837 * Check a dentry's cluster chain for bad clusters.
838 * If requested, we verify readability and mark unreadable clusters as bad.
839 *
840 * @param[inout] fs Information about the filesystem
841 * @param[in] file dentry to check
842 * @param[in] read_test Nonzero == verify that dentry's clusters can
843 * be read
844 */
845static void test_file(DOS_FS * fs, DOS_FILE * file, int read_test)
846{
847 DOS_FILE *owner;
Matt Mower18794c82015-11-11 16:22:45 -0600848 uint32_t walk, prev, clusters, next_clu;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500849
850 prev = clusters = 0;
Matt Mower18794c82015-11-11 16:22:45 -0600851 for (walk = FSTART(file, fs); walk > 1 && walk < fs->clusters + 2;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500852 walk = next_clu) {
853 next_clu = next_cluster(fs, walk);
854
855 /* In this stage we are checking only for a loop within our own
856 * cluster chain.
857 * Cross-linking of clusters is handled in check_file()
858 */
859 if ((owner = get_owner(fs, walk))) {
860 if (owner == file) {
861 printf("%s\n Circular cluster chain. Truncating to %lu "
Matt Mower18794c82015-11-11 16:22:45 -0600862 "cluster%s.\n", path_name(file), (unsigned long)clusters,
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500863 clusters == 1 ? "" : "s");
864 if (prev)
865 set_fat(fs, prev, -1);
866 else if (!file->offset)
867 die("Bad FAT32 root directory! (bad start cluster)\n");
868 else
869 MODIFY_START(file, 0, fs);
870 }
871 break;
872 }
873 if (bad_cluster(fs, walk))
874 break;
875 if (read_test) {
876 if (fs_test(cluster_start(fs, walk), fs->cluster_size)) {
877 prev = walk;
878 clusters++;
879 } else {
880 printf("%s\n Cluster %lu (%lu) is unreadable. Skipping it.\n",
Matt Mower18794c82015-11-11 16:22:45 -0600881 path_name(file), (unsigned long)clusters, (unsigned long)walk);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500882 if (prev)
883 set_fat(fs, prev, next_cluster(fs, walk));
884 else
885 MODIFY_START(file, next_cluster(fs, walk), fs);
886 set_fat(fs, walk, -2);
887 }
888 }
889 set_owner(fs, walk, file);
890 }
891 /* Revert ownership (for now) */
Matt Mower18794c82015-11-11 16:22:45 -0600892 for (walk = FSTART(file, fs); walk > 1 && walk < fs->clusters + 2;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500893 walk = next_cluster(fs, walk))
894 if (bad_cluster(fs, walk))
895 break;
896 else if (get_owner(fs, walk) == file)
897 set_owner(fs, walk, NULL);
898 else
899 break;
900}
901
902static void undelete(DOS_FS * fs, DOS_FILE * file)
903{
Matt Mower18794c82015-11-11 16:22:45 -0600904 uint32_t clusters, left, prev, walk;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500905
Matt Mower18794c82015-11-11 16:22:45 -0600906 clusters = left = (le32toh(file->dir_ent.size) + fs->cluster_size - 1) /
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500907 fs->cluster_size;
908 prev = 0;
909
910 walk = FSTART(file, fs);
911
912 while (left && (walk >= 2) && (walk < fs->clusters + 2)) {
913
914 FAT_ENTRY curEntry;
915 get_fat(&curEntry, fs->fat, walk, fs);
916
917 if (!curEntry.value)
918 break;
919
920 left--;
921 if (prev)
922 set_fat(fs, prev, walk);
923 prev = walk;
924 walk++;
925 }
926 if (prev)
927 set_fat(fs, prev, -1);
928 else
929 MODIFY_START(file, 0, fs);
930 if (left)
931 printf("Warning: Did only undelete %lu of %lu cluster%s.\n",
Matt Mower18794c82015-11-11 16:22:45 -0600932 (unsigned long)clusters - left, (unsigned long)clusters, clusters == 1 ? "" : "s");
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500933
934}
935
936static void new_dir(void)
937{
938 lfn_reset();
939}
940
941/**
942 * Create a description for a referenced dentry and insert it in our dentry
943 * tree. Then, go check the dentry's cluster chain for bad clusters and
944 * cluster loops.
945 *
946 * @param[inout] fs Information about the filesystem
947 * @param[out] chain
948 * @param[in] parent Information about parent directory of this file
949 * NULL == no parent ('file' is root directory)
950 * @param[in] offset Partition-relative byte offset of directory entry of interest
951 * 0 == Root directory
952 * @param cp
953 */
954static void add_file(DOS_FS * fs, DOS_FILE *** chain, DOS_FILE * parent,
955 loff_t offset, FDSC ** cp)
956{
957 DOS_FILE *new;
958 DIR_ENT de;
959 FD_TYPE type;
960
961 if (offset)
962 fs_read(offset, sizeof(DIR_ENT), &de);
963 else {
964 /* Construct a DIR_ENT for the root directory */
Matt Mower18794c82015-11-11 16:22:45 -0600965 memset(&de, 0, sizeof de);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500966 memcpy(de.name, " ", MSDOS_NAME);
967 de.attr = ATTR_DIR;
Matt Mower18794c82015-11-11 16:22:45 -0600968 de.start = htole16(fs->root_cluster & 0xffff);
969 de.starthi = htole16((fs->root_cluster >> 16) & 0xffff);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500970 }
971 if ((type = file_type(cp, (char *)de.name)) != fdt_none) {
972 if (type == fdt_undelete && (de.attr & ATTR_DIR))
973 die("Can't undelete directories.");
974 file_modify(cp, (char *)de.name);
975 fs_write(offset, 1, &de);
976 }
977 if (IS_FREE(de.name)) {
978 lfn_check_orphaned();
979 return;
980 }
981 if (de.attr == VFAT_LN_ATTR) {
982 lfn_add_slot(&de, offset);
983 return;
984 }
985 new = qalloc(&mem_queue, sizeof(DOS_FILE));
986 new->lfn = lfn_get(&de, &new->lfn_offset);
987 new->offset = offset;
988 memcpy(&new->dir_ent, &de, sizeof(de));
989 new->next = new->first = NULL;
990 new->parent = parent;
991 if (type == fdt_undelete)
992 undelete(fs, new);
993 **chain = new;
994 *chain = &new->next;
995 if (list) {
996 printf("Checking file %s", path_name(new));
997 if (new->lfn)
998 printf(" (%s)", file_name(new->dir_ent.name)); /* (8.3) */
999 printf("\n");
1000 }
1001 /* Don't include root directory, '.', or '..' in the total file count */
1002 if (offset &&
1003 strncmp((const char *)de.name, MSDOS_DOT, MSDOS_NAME) != 0 &&
1004 strncmp((const char *)de.name, MSDOS_DOTDOT, MSDOS_NAME) != 0)
1005 ++n_files;
1006 test_file(fs, new, test); /* Bad cluster check */
1007}
1008
1009static int subdirs(DOS_FS * fs, DOS_FILE * parent, FDSC ** cp);
1010
1011static int scan_dir(DOS_FS * fs, DOS_FILE * this, FDSC ** cp)
1012{
1013 DOS_FILE **chain;
1014 int i;
Matt Mower18794c82015-11-11 16:22:45 -06001015 uint32_t clu_num;
bigbiff bigbiff9c754052013-01-09 09:09:08 -05001016
1017 chain = &this->first;
1018 i = 0;
1019 clu_num = FSTART(this, fs);
1020 new_dir();
1021 while (clu_num > 0 && clu_num != -1) {
1022 add_file(fs, &chain, this,
1023 cluster_start(fs, clu_num) + (i % fs->cluster_size), cp);
1024 i += sizeof(DIR_ENT);
1025 if (!(i % fs->cluster_size))
1026 if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1)
1027 break;
1028 }
1029 lfn_check_orphaned();
1030 if (check_dir(fs, &this->first, this->offset))
1031 return 0;
1032 if (check_files(fs, this->first))
1033 return 1;
1034 return subdirs(fs, this, cp);
1035}
1036
1037/**
1038 * Recursively scan subdirectories of the specified parent directory.
1039 *
1040 * @param[inout] fs Information about the filesystem
1041 * @param[in] parent Identifies the directory to scan
1042 * @param[in] cp
1043 *
1044 * @return 0 Success
1045 * @return 1 Error
1046 */
1047static int subdirs(DOS_FS * fs, DOS_FILE * parent, FDSC ** cp)
1048{
1049 DOS_FILE *walk;
1050
1051 for (walk = parent ? parent->first : root; walk; walk = walk->next)
1052 if (walk->dir_ent.attr & ATTR_DIR)
1053 if (strncmp((const char *)walk->dir_ent.name, MSDOS_DOT, MSDOS_NAME)
1054 && strncmp((const char *)walk->dir_ent.name, MSDOS_DOTDOT,
1055 MSDOS_NAME))
1056 if (scan_dir(fs, walk, file_cd(cp, (char *)walk->dir_ent.name)))
1057 return 1;
1058 return 0;
1059}
1060
1061/**
1062 * Scan all directory and file information for errors.
1063 *
1064 * @param[inout] fs Information about the filesystem
1065 *
1066 * @return 0 Success
1067 * @return 1 Error
1068 */
1069int scan_root(DOS_FS * fs)
1070{
1071 DOS_FILE **chain;
1072 int i;
1073
1074 root = NULL;
1075 chain = &root;
1076 new_dir();
1077 if (fs->root_cluster) {
1078 add_file(fs, &chain, NULL, 0, &fp_root);
1079 } else {
1080 for (i = 0; i < fs->root_entries; i++)
1081 add_file(fs, &chain, NULL, fs->root_start + i * sizeof(DIR_ENT),
1082 &fp_root);
1083 }
1084 lfn_check_orphaned();
1085 (void)check_dir(fs, &root, 0);
1086 if (check_files(fs, root))
1087 return 1;
1088 return subdirs(fs, NULL, &fp_root);
1089}