diff options
Diffstat (limited to 'src/check.c')
-rw-r--r-- | src/check.c | 936 |
1 files changed, 592 insertions, 344 deletions
diff --git a/src/check.c b/src/check.c index 417eb55..1097d57 100644 --- a/src/check.c +++ b/src/check.c @@ -4,6 +4,7 @@ Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch> Copyright (C) 2015 Andreas Bombe <aeb@debian.org> + Copyright (C) 2017-2021 Pali Rohár <pali.rohar@gmail.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,6 +30,9 @@ #include <stdlib.h> #include <string.h> #include <time.h> +#include <errno.h> +#include <ctype.h> +#include <wctype.h> #include "common.h" #include "fsck.fat.h" @@ -37,6 +41,8 @@ #include "file.h" #include "lfn.h" #include "check.h" +#include "boot.h" +#include "charconv.h" /* the longest path on the filesystem that can be handled by path_name() */ @@ -47,7 +53,7 @@ static DOS_FILE *root; /* get start field of a dir entry */ #define FSTART(p,fs) \ ((uint32_t)le16toh(p->dir_ent.start) | \ - (fs->fat_bits == 32 ? le16toh(p->dir_ent.starthi) << 16 : 0)) + (fs->fat_bits == 32 ? (uint32_t)le16toh(p->dir_ent.starthi) << 16 : 0)) #define MODIFY(p,i,v) \ do { \ @@ -79,132 +85,6 @@ static DOS_FILE *root; } \ } while(0) -off_t alloc_rootdir_entry(DOS_FS * fs, DIR_ENT * de, const char *pattern, int gen_name) -{ - static int curr_num = 0; - off_t offset; - - if (fs->root_cluster) { - DIR_ENT d2; - int i = 0, got = 0; - uint32_t clu_num, prev = 0; - off_t offset2; - - clu_num = fs->root_cluster; - offset = cluster_start(fs, clu_num); - while (clu_num > 0 && clu_num != -1) { - fs_read(offset, sizeof(DIR_ENT), &d2); - if (IS_FREE(d2.name) && d2.attr != VFAT_LN_ATTR) { - got = 1; - break; - } - i += sizeof(DIR_ENT); - offset += sizeof(DIR_ENT); - if ((i % fs->cluster_size) == 0) { - prev = clu_num; - if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1) - break; - offset = cluster_start(fs, clu_num); - } - } - if (!got) { - /* no free slot, need to extend root dir: alloc next free cluster - * after previous one */ - if (!prev) - die("Root directory has no cluster allocated!"); - for (clu_num = prev + 1; clu_num != prev; clu_num++) { - FAT_ENTRY entry; - - if (clu_num >= fs->data_clusters + 2) - clu_num = 2; - get_fat(&entry, fs->fat, clu_num, fs); - if (!entry.value) - break; - } - if (clu_num == prev) - die("Root directory full and no free cluster"); - set_fat(fs, prev, clu_num); - set_fat(fs, clu_num, -1); - set_owner(fs, clu_num, get_owner(fs, fs->root_cluster)); - /* clear new cluster */ - memset(&d2, 0, sizeof(d2)); - offset = cluster_start(fs, clu_num); - for (i = 0; i < fs->cluster_size; i += sizeof(DIR_ENT)) - fs_write(offset + i, sizeof(d2), &d2); - } - memset(de, 0, sizeof(DIR_ENT)); - if (gen_name) { - while (1) { - char expanded[12]; - sprintf(expanded, pattern, curr_num); - memcpy(de->name, expanded, MSDOS_NAME); - clu_num = fs->root_cluster; - i = 0; - offset2 = cluster_start(fs, clu_num); - while (clu_num > 0 && clu_num != -1) { - fs_read(offset2, sizeof(DIR_ENT), &d2); - if (offset2 != offset && - !strncmp((const char *)d2.name, (const char *)de->name, - MSDOS_NAME)) - break; - i += sizeof(DIR_ENT); - offset2 += sizeof(DIR_ENT); - if ((i % fs->cluster_size) == 0) { - if ((clu_num = next_cluster(fs, clu_num)) == 0 || - clu_num == -1) - break; - offset2 = cluster_start(fs, clu_num); - } - } - if (clu_num == 0 || clu_num == -1) - break; - if (++curr_num >= 10000) - die("Unable to create unique name"); - } - } else { - memcpy(de->name, pattern, MSDOS_NAME); - } - } else { - DIR_ENT *root; - int next_free = 0, scan; - - root = alloc(fs->root_entries * sizeof(DIR_ENT)); - fs_read(fs->root_start, fs->root_entries * sizeof(DIR_ENT), root); - - while (next_free < fs->root_entries) - if (IS_FREE(root[next_free].name) && - root[next_free].attr != VFAT_LN_ATTR) - break; - else - next_free++; - if (next_free == fs->root_entries) - die("Root directory is full."); - offset = fs->root_start + next_free * sizeof(DIR_ENT); - memset(de, 0, sizeof(DIR_ENT)); - if (gen_name) { - while (1) { - char expanded[12]; - sprintf(expanded, pattern, curr_num); - memcpy(de->name, expanded, MSDOS_NAME); - for (scan = 0; scan < fs->root_entries; scan++) - if (scan != next_free && - !strncmp((const char *)root[scan].name, - (const char *)de->name, MSDOS_NAME)) - break; - if (scan == fs->root_entries) - break; - if (++curr_num >= 10000) - die("Unable to create unique name"); - } - } else { - memcpy(de->name, pattern, MSDOS_NAME); - } - free(root); - } - ++n_files; - return offset; -} - /** * Construct a full path (starting with '/') for the specified dentry, * relative to the partition. All components are "long" names where possible. @@ -234,57 +114,43 @@ static char *path_name(DOS_FILE * file) return path; } -static const int day_n[] = - { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0 }; -/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */ - -/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */ - -static time_t date_dos2unix(unsigned short time, unsigned short date) -{ - int month, year; - time_t secs; - - month = ((date >> 5) & 15) - 1; - if (month < 0) { - /* make sure that nothing bad happens if the month bits were zero */ - month = 0; - } - year = date >> 9; - secs = - (time & 31) * 2 + 60 * ((time >> 5) & 63) + (time >> 11) * 3600 + - 86400 * ((date & 31) - 1 + day_n[month] + (year / 4) + year * 365 - - ((year & 3) == 0 && month < 2 ? 1 : 0) + 3653); - /* days since 1.1.70 plus 80's leap day */ - return secs; -} +static const char *month_str[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char *file_stat(DOS_FILE * file) { static char temp[100]; - struct tm *tm; - char tmp[100]; - time_t date; - - date = - date_dos2unix(le16toh(file->dir_ent.time), le16toh(file->dir_ent.date)); - tm = localtime(&date); - strftime(tmp, 99, "%H:%M:%S %b %d %Y", tm); - sprintf(temp, " Size %u bytes, date %s", le32toh(file->dir_ent.size), tmp); + unsigned int hours, minutes, secs, day, month, year; + unsigned short time, date; + + time = le16toh(file->dir_ent.time); + date = le16toh(file->dir_ent.date); + year = 1980 + (date >> 9); + month = ((date >> 5) & 15); + if (month < 1) month = 1; + else if (month > 12) month = 12; + day = (date & 31); + if (day < 1) day = 1; + hours = (time >> 11); + if (hours > 23) hours = 23; + minutes = ((time >> 5) & 63); + if (minutes > 59) minutes = 59; + secs = (time & 31) * 2; + if (secs > 59) secs = 59; + sprintf(temp, " Size %u bytes, date %02u:%02u:%02u %s %02u %4u", + le32toh(file->dir_ent.size), hours, minutes, secs, month_str[month-1], day, year); return temp; } static int bad_name(DOS_FILE * file) { int i, spc, suspicious = 0; - const char *bad_chars = atari_format ? "*?\\/:" : "*?<>|\"\\/:"; + const char *bad_chars = atari_format ? "*?\\/:" : "*?<>|\"\\/:."; const unsigned char *name = file->dir_ent.name; const unsigned char *ext = name + 8; - /* Do not complain about (and auto-correct) the extended attribute files - * of OS/2. */ - if (strncmp((const char *)name, "EA DATA SF", 11) == 0 || - strncmp((const char *)name, "WP ROOT SF", 11) == 0) + /* do not check synthetic FAT32 root entry */ + if (!file->offset) return 0; /* check if we have neither a long filename nor a short name */ @@ -298,7 +164,7 @@ static int bad_name(DOS_FILE * file) return 0; for (i = 0; i < MSDOS_NAME; i++) { - if (name[i] < ' ' || name[i] == 0x7f) + if ((name[i] < ' ' && !(i == 0 && name[0] == 0x05)) || name[i] == 0x7f) return 1; if (name[i] > 0x7f) ++suspicious; @@ -306,24 +172,29 @@ static int bad_name(DOS_FILE * file) return 1; } - spc = 0; - for (i = 0; i < 8; i++) { - if (name[i] == ' ') - spc = 1; - else if (spc) - /* non-space after a space not allowed, space terminates the name - * part */ - return 1; - } + if (name[0] == ' ') + return 1; - spc = 0; - for (i = 0; i < 3; i++) { - if (ext[i] == ' ') - spc = 1; - else if (spc) - /* non-space after a space not allowed, space terminates the ext - * part */ - return 1; + if (no_spaces_in_sfns) { + spc = 0; + for (i = 0; i < 8; i++) { + if (name[i] == ' ') + spc = 1; + else if (spc) + /* non-space after a space not allowed, space terminates the name + * part */ + return 1; + } + + spc = 0; + for (i = 0; i < 3; i++) { + if (ext[i] == ' ') + spc = 1; + else if (spc) + /* non-space after a space not allowed, space terminates the ext + * part */ + return 1; + } } /* Under GEMDOS, chars >= 128 are never allowed. */ @@ -355,14 +226,11 @@ static void lfn_remove(off_t from, off_t to) static void drop_file(DOS_FS * fs, DOS_FILE * file) { - uint32_t cluster; + (void) fs; MODIFY(file, name[0], DELETED_FLAG); if (file->lfn) lfn_remove(file->lfn_offset, file->offset); - for (cluster = FSTART(file, fs); cluster > 0 && cluster < - fs->data_clusters + 2; cluster = next_cluster(fs, cluster)) - set_owner(fs, cluster, NULL); --n_files; } @@ -389,8 +257,10 @@ static void auto_rename(DOS_FILE * file) DOS_FILE *first, *walk; uint32_t number; - if (!file->offset) + if (!file->offset) { + printf("Cannot rename FAT32 root dir\n"); return; /* cannot rename FAT32 root dir */ + } first = file->parent ? file->parent->first : root; number = 0; while (1) { @@ -414,9 +284,10 @@ static void auto_rename(DOS_FILE * file) } else { fs_write(file->offset, MSDOS_NAME, file->dir_ent.name); } - if (file->lfn) - lfn_fix_checksum(file->lfn_offset, file->offset, - (const char *)file->dir_ent.name); + if (file->lfn) { + lfn_remove(file->lfn_offset, file->offset); + file->lfn = NULL; + } return; } number++; @@ -437,9 +308,7 @@ static void rename_file(DOS_FILE * file) return; /* cannot rename FAT32 root dir */ } while (1) { - printf("New name: "); - fflush(stdout); - if (fgets((char *)name, 45, stdin)) { + if (get_line("New name", (char *)name, 45)) { if ((here = (unsigned char *)strchr((const char *)name, '\n'))) *here = 0; for (walk = (unsigned char *)strrchr((const char *)name, 0); @@ -457,51 +326,124 @@ static void rename_file(DOS_FILE * file) } else { fs_write(file->offset, MSDOS_NAME, file->dir_ent.name); } - if (file->lfn) - lfn_fix_checksum(file->lfn_offset, file->offset, - (const char *)file->dir_ent.name); + if (file->lfn) { + lfn_remove(file->lfn_offset, file->offset); + file->lfn = NULL; + } return; } } } } -static int handle_dot(DOS_FS * fs, DOS_FILE * file, int dots) +static uint32_t scan_free_entry(DOS_FS * fs, DOS_FILE * this) { - const char *name; - - name = - strncmp((const char *)file->dir_ent.name, MSDOS_DOT, - MSDOS_NAME) ? ".." : "."; - if (!(file->dir_ent.attr & ATTR_DIR)) { - printf("%s\n Is a non-directory.\n", path_name(file)); - if (interactive) - printf("1) Drop it\n2) Auto-rename\n3) Rename\n" - "4) Convert to directory\n"); - else - printf(" Auto-renaming it.\n"); - switch (interactive ? get_key("1234", "?") : '2') { - case '1': - drop_file(fs, file); - return 1; - case '2': - auto_rename(file); - printf(" Renamed to %s\n", file_name(file->dir_ent.name)); - return 0; - case '3': - rename_file(file); - return 0; - case '4': - MODIFY(file, size, htole32(0)); - MODIFY(file, attr, file->dir_ent.attr | ATTR_DIR); - break; + uint32_t clu_num, offset; + int i; + DIR_ENT de; + + i = 2 * sizeof(DIR_ENT); /* Skip '.' and '..' slots */ + clu_num = FSTART(this, fs); + while (clu_num > 0 && clu_num != -1) { + offset = cluster_start(fs, clu_num) + (i % fs->cluster_size); + fs_read(offset, sizeof(DIR_ENT), &de); + if (IS_FREE(de.name)) + return offset; + i += sizeof(DIR_ENT); + if (!(i % fs->cluster_size)) + if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1) + break; + } + + return 0; +} + +static int handle_dot(DOS_FS * fs, DOS_FILE * file, int dotdot) +{ + const char *name, *ent; + uint32_t new_offset, start; + + if (dotdot) { + name = ".."; + ent = MSDOS_DOTDOT; + if (!file->parent->parent) { + start = 0; + } else { + start = FSTART(file->parent->parent, fs); + if (start == fs->root_cluster) + start = 0; } + } else { + name = "."; + ent = MSDOS_DOT; + start = FSTART(file->parent, fs); } - if (!dots) { - printf("Root contains directory \"%s\". Dropping it.\n", name); - drop_file(fs, file); - return 1; + + if (!(file->dir_ent.attr & ATTR_DIR) || (FSTART(file, fs) != start) || + strncmp((const char *)(file->dir_ent.name), ent, MSDOS_NAME)) { + + if (IS_FREE(file->dir_ent.name)) { + printf("%s\n Expected a valid '%s' entry in the %s slot, found free entry.\n", + path_name(file->parent), name, dotdot ? "second" : "first"); + switch (get_choice(1, " Creating.", + 2, + 1, "Create entry", + 2, "Drop parent")) { + case 1: + goto conjure; + case 2: + drop_file(fs, file->parent); + return 1; + } + } + + if (!strncmp((const char *)(file->dir_ent.name), ent, MSDOS_NAME)) { + printf("%s\n Invalid '%s' entry in the %s slot. Fixing.\n", + path_name(file->parent), name, dotdot ? "second" : "first"); + MODIFY_START(file, start, fs); + MODIFY(file, attr, ATTR_DIR); + } else { + printf("%s\n Expected a valid '%s' entry in this slot.\n", + path_name(file), name); + switch (get_choice(3, " Moving entry down.", + 3, + 1, "Drop entry", + 2, "Drop parent", + 3, "Move entry down")) { + case 1: + drop_file(fs, file); + goto conjure; + case 2: + drop_file(fs, file->parent); + return 1; + case 3: + new_offset = scan_free_entry(fs, file->parent); + if (!new_offset) { + printf("No free entry found.\n"); + return 0; + } + + fs_write(new_offset, sizeof(file->dir_ent), &file->dir_ent); + goto conjure; + } + } } + if (file->dir_ent.lcase & FAT_NO_83NAME) { + /* Some versions of mtools write these directory entries with random data in + this field. */ + printf("%s\n Is a dot with no 8.3 name flag set, clearing.\n", path_name(file)); + file->dir_ent.lcase &= ~FAT_NO_83NAME; + MODIFY(file, lcase, file->dir_ent.lcase); + } + + return 0; + +conjure: + memset(&file->dir_ent, 0, sizeof(DIR_ENT)); + memcpy(file->dir_ent.name, ent, MSDOS_NAME); + fs_write(file->offset, sizeof(file->dir_ent), &file->dir_ent); + MODIFY_START(file, start, fs); + MODIFY(file, attr, ATTR_DIR); return 0; } @@ -509,7 +451,10 @@ static int check_file(DOS_FS * fs, DOS_FILE * file) { DOS_FILE *owner; int restart; - uint32_t expect, curr, this, clusters, prev, walk, clusters2; + uint32_t parent, grandp, curr, this, clusters, prev, walk, clusters2; + + if (IS_FREE(file->dir_ent.name)) + return 0; if (file->dir_ent.attr & ATTR_DIR) { if (le32toh(file->dir_ent.size)) { @@ -517,37 +462,33 @@ static int check_file(DOS_FS * fs, DOS_FILE * file) path_name(file)); MODIFY(file, size, htole32(0)); } - if (file->parent - && !strncmp((const char *)file->dir_ent.name, MSDOS_DOT, - MSDOS_NAME)) { - expect = FSTART(file->parent, fs); - if (FSTART(file, fs) != expect) { - printf("%s\n Start (%lu) does not point to parent (%lu)\n", - path_name(file), (unsigned long)FSTART(file, fs), (long)expect); - MODIFY_START(file, expect, fs); - } - return 0; - } - if (file->parent - && !strncmp((const char *)file->dir_ent.name, MSDOS_DOTDOT, - MSDOS_NAME)) { - expect = - file->parent->parent ? FSTART(file->parent->parent, fs) : 0; - if (fs->root_cluster && expect == fs->root_cluster) - expect = 0; - if (FSTART(file, fs) != expect) { - printf("%s\n Start (%lu) does not point to .. (%lu)\n", - path_name(file), (unsigned long)FSTART(file, fs), (unsigned long)expect); - MODIFY_START(file, expect, fs); - } - return 0; - } if (FSTART(file, fs) == 0) { printf("%s\n Start does point to root directory. Deleting dir. \n", path_name(file)); MODIFY(file, name[0], DELETED_FLAG); return 0; } + if (file->parent) { + parent = FSTART(file->parent, fs); + grandp = file->parent->parent ? FSTART(file->parent->parent, fs) : 0; + if (fs->root_cluster && grandp == fs->root_cluster) + grandp = 0; + + if (FSTART(file, fs) == parent) { + printf("%s\n Start does point to containing directory. Deleting entry.\n", + path_name(file)); + MODIFY(file, name[0], DELETED_FLAG); + MODIFY_START(file, 0, fs); + return 0; + } + if (FSTART(file, fs) == grandp) { + printf("%s\n Start does point to containing directory's parent. Deleting entry.\n", + path_name(file)); + MODIFY(file, name[0], DELETED_FLAG); + MODIFY_START(file, 0, fs); + return 0; + } + } } if (FSTART(file, fs) == 1) { printf("%s\n Bad start cluster 1. Truncating file.\n", @@ -585,12 +526,12 @@ static int check_file(DOS_FS * fs, DOS_FILE * file) break; } if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) <= - (uint64_t)clusters * fs->cluster_size) { + clusters * fs->cluster_size) { printf - ("%s\n File size is %u bytes, cluster chain length is > %llu " + ("%s\n File size is %u bytes, cluster chain length is > %u " "bytes.\n Truncating file to %u bytes.\n", path_name(file), le32toh(file->dir_ent.size), - (unsigned long long)clusters * fs->cluster_size, + (unsigned)clusters * fs->cluster_size, le32toh(file->dir_ent.size)); truncate_file(fs, file, clusters); break; @@ -604,31 +545,49 @@ static int check_file(DOS_FS * fs, DOS_FILE * file) next_cluster(fs, walk)) if (walk == curr) break; - else + else { + if ((unsigned long long)clusters2 * fs->cluster_size >= UINT32_MAX) + die("Internal error: File size is larger than 2^32-1"); clusters2++; + } restart = file->dir_ent.attr & ATTR_DIR; if (!owner->offset) { - printf(" Truncating second to %llu bytes because first " + printf(" Truncating second to %u bytes because first " "is FAT32 root dir.\n", - (unsigned long long)clusters * fs->cluster_size); + (unsigned)clusters * fs->cluster_size); do_trunc = 2; } else if (!file->offset) { - printf(" Truncating first to %llu bytes because second " + printf(" Truncating first to %u bytes because second " "is FAT32 root dir.\n", - (unsigned long long)clusters2 * fs->cluster_size); + (unsigned)clusters2 * fs->cluster_size); do_trunc = 1; - } else if (interactive) - printf("1) Truncate first to %llu bytes%s\n" - "2) Truncate second to %llu bytes\n", - (unsigned long long)clusters2 * fs->cluster_size, - restart ? " and restart" : "", - (unsigned long long)clusters * fs->cluster_size); - else - printf(" Truncating second to %llu bytes.\n", - (unsigned long long)clusters * fs->cluster_size); - if (do_trunc != 2 - && (do_trunc == 1 - || (interactive && get_key("12", "?") == '1'))) { + } else { + char *trunc_first_string; + char *trunc_second_string; + char *noninteractive_string; + + xasprintf(&trunc_first_string, + "Truncate first to %u bytes%s", + (unsigned)clusters2 * fs->cluster_size, + restart ? " and restart" : ""); + xasprintf(&trunc_second_string, + "Truncate second to %u bytes", + (unsigned)clusters * fs->cluster_size); + xasprintf(&noninteractive_string, + " Truncating second to %u bytes.", + (unsigned)clusters * fs->cluster_size); + + do_trunc = get_choice(2, noninteractive_string, + 2, + 1, trunc_first_string, + 2, trunc_second_string); + + free(trunc_first_string); + free(trunc_second_string); + free(noninteractive_string); + } + + if (do_trunc == 1) { prev = 0; clusters = 0; for (this = FSTART(owner, fs); this > 0 && this != -1; this = @@ -638,9 +597,7 @@ static int check_file(DOS_FS * fs, DOS_FILE * file) set_fat(fs, prev, -1); else MODIFY_START(owner, 0, fs); - MODIFY(owner, size, - htole32((uint64_t)clusters * - fs->cluster_size)); + MODIFY(owner, size, htole32(clusters * fs->cluster_size)); if (restart) return 1; while (this > 0 && this != -1) { @@ -650,6 +607,8 @@ static int check_file(DOS_FS * fs, DOS_FILE * file) this = curr; break; } + if ((unsigned long long)clusters * fs->cluster_size >= UINT32_MAX) + die("Internal error: File size is larger than 2^32-1"); clusters++; prev = this; } @@ -665,19 +624,21 @@ static int check_file(DOS_FS * fs, DOS_FILE * file) } } set_owner(fs, curr, file); + if ((unsigned long long)clusters * fs->cluster_size >= UINT32_MAX) + die("Internal error: File size is larger than 2^32-1"); clusters++; prev = curr; } if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) > - (uint64_t)clusters * fs->cluster_size) { + clusters * fs->cluster_size) { printf - ("%s\n File size is %u bytes, cluster chain length is %llu bytes." - "\n Truncating file to %llu bytes.\n", path_name(file), + ("%s\n File size is %u bytes, cluster chain length is %u bytes." + "\n Truncating file to %u bytes.\n", path_name(file), le32toh(file->dir_ent.size), - (unsigned long long)clusters * fs->cluster_size, - (unsigned long long)clusters * fs->cluster_size); + (unsigned)clusters * fs->cluster_size, + (unsigned)clusters * fs->cluster_size); MODIFY(file, size, - htole32((uint64_t)clusters * fs->cluster_size)); + htole32(clusters * fs->cluster_size)); } return 0; } @@ -695,7 +656,7 @@ static int check_files(DOS_FS * fs, DOS_FILE * start) static int check_dir(DOS_FS * fs, DOS_FILE ** root, int dots) { DOS_FILE *parent, **walk, **scan; - int dot, dotdot, skip, redo; + int skip, redo; int good, bad; if (!*root) @@ -712,55 +673,42 @@ static int check_dir(DOS_FS * fs, DOS_FILE ** root, int dots) path_name(parent), bad, good + bad); if (!dots) printf(" Not dropping root directory.\n"); - else if (!interactive) - printf(" Not dropping it in auto-mode.\n"); - else if (get_key("yn", "Drop directory ? (y/n)") == 'y') { + else if (get_choice(2, " Not dropping it in auto-mode.", + 2, + 1, "Drop directory", + 2, "Keep directory") == 1) { truncate_file(fs, parent, 0); MODIFY(parent, name[0], DELETED_FLAG); /* buglet: deleted directory stays in the list. */ return 1; } } - dot = dotdot = redo = 0; + redo = 0; walk = root; while (*walk) { - if (!strncmp - ((const char *)((*walk)->dir_ent.name), MSDOS_DOT, MSDOS_NAME) - || !strncmp((const char *)((*walk)->dir_ent.name), MSDOS_DOTDOT, - MSDOS_NAME)) { - if (handle_dot(fs, *walk, dots)) { - *walk = (*walk)->next; - continue; - } - if (!strncmp - ((const char *)((*walk)->dir_ent.name), MSDOS_DOT, MSDOS_NAME)) - dot++; - else - dotdot++; - } if (!((*walk)->dir_ent.attr & ATTR_VOLUME) && bad_name(*walk)) { puts(path_name(*walk)); printf(" Bad short file name (%s).\n", file_name((*walk)->dir_ent.name)); - if (interactive) - printf("1) Drop file\n2) Rename file\n3) Auto-rename\n" - "4) Keep it\n"); - else - printf(" Auto-renaming it.\n"); - switch (interactive ? get_key("1234", "?") : '3') { - case '1': + switch (get_choice(3, " Auto-renaming it.", + 4, + 1, "Drop file", + 2, "Rename file", + 3, "Auto-rename", + 4, "Keep it")) { + case 1: drop_file(fs, *walk); walk = &(*walk)->next; continue; - case '2': + case 2: rename_file(*walk); redo = 1; break; - case '3': + case 3: auto_rename(*walk); printf(" Renamed to %s\n", file_name((*walk)->dir_ent.name)); break; - case '4': + case 4: break; } } @@ -775,39 +723,39 @@ static int check_dir(DOS_FS * fs, DOS_FILE ** root, int dots) printf("%s\n Duplicate directory entry.\n First %s\n", path_name(*walk), file_stat(*walk)); printf(" Second %s\n", file_stat(*scan)); - if (interactive) - printf - ("1) Drop first\n2) Drop second\n3) Rename first\n" - "4) Rename second\n5) Auto-rename first\n" - "6) Auto-rename second\n"); - else - printf(" Auto-renaming second.\n"); - switch (interactive ? get_key("123456", "?") : '6') { - case '1': + switch (get_choice(6, " Auto-renaming second.", + 6, + 1, "Drop first", + 2, "Drop second", + 3, "Rename first", + 4, "Rename second", + 5, "Auto-rename first", + 6, "Auto-rename second")) { + case 1: drop_file(fs, *walk); *walk = (*walk)->next; skip = 1; break; - case '2': + case 2: drop_file(fs, *scan); *scan = (*scan)->next; continue; - case '3': + case 3: rename_file(*walk); printf(" Renamed to %s\n", path_name(*walk)); redo = 1; break; - case '4': + case 4: rename_file(*scan); printf(" Renamed to %s\n", path_name(*walk)); redo = 1; break; - case '5': + case 5: auto_rename(*walk); printf(" Renamed to %s\n", file_name((*walk)->dir_ent.name)); break; - case '6': + case 6: auto_rename(*scan); printf(" Renamed to %s\n", file_name((*scan)->dir_ent.name)); @@ -823,15 +771,9 @@ static int check_dir(DOS_FS * fs, DOS_FILE ** root, int dots) walk = &(*walk)->next; else { walk = root; - dot = dotdot = redo = 0; + redo = 0; } } - if (dots && !dot) - printf("%s\n \".\" is missing. Can't fix this yet.\n", - path_name(parent)); - if (dots && !dotdot) - printf("%s\n \"..\" is missing. Can't fix this yet.\n", - path_name(parent)); return 0; } @@ -1003,10 +945,8 @@ static void add_file(DOS_FS * fs, DOS_FILE *** chain, DOS_FILE * parent, printf(" (%s)", file_name(new->dir_ent.name)); /* (8.3) */ printf("\n"); } - /* Don't include root directory, '.', or '..' in the total file count */ - if (offset && - strncmp((const char *)de.name, MSDOS_DOT, MSDOS_NAME) != 0 && - strncmp((const char *)de.name, MSDOS_DOTDOT, MSDOS_NAME) != 0) + /* Don't include root directory in the total file count */ + if (offset) ++n_files; test_file(fs, new, test); /* Bad cluster check */ } @@ -1023,6 +963,27 @@ static int scan_dir(DOS_FS * fs, DOS_FILE * this, FDSC ** cp) i = 0; clu_num = FSTART(this, fs); new_dir(); + if (clu_num != 0 && clu_num != -1 && this->offset) { + DOS_FILE file; + + file.lfn = NULL; + file.lfn_offset = 0; + file.next = NULL; + file.parent = this; + file.first = NULL; + + file.offset = cluster_start(fs, clu_num) + (i % fs->cluster_size); + fs_read(file.offset, sizeof(DIR_ENT), &file.dir_ent); + if (handle_dot(fs, &file, 0)) + return 1; + i += sizeof(DIR_ENT); + + file.offset = cluster_start(fs, clu_num) + (i % fs->cluster_size); + fs_read(file.offset, sizeof(DIR_ENT), &file.dir_ent); + if (handle_dot(fs, &file, 1)) + return 1; + i += sizeof(DIR_ENT); + } while (clu_num > 0 && clu_num != -1) { add_file(fs, &chain, this, cluster_start(fs, clu_num) + (i % fs->cluster_size), cp); @@ -1054,12 +1015,9 @@ static int subdirs(DOS_FS * fs, DOS_FILE * parent, FDSC ** cp) DOS_FILE *walk; for (walk = parent ? parent->first : root; walk; walk = walk->next) - if (walk->dir_ent.attr & ATTR_DIR) - if (strncmp((const char *)walk->dir_ent.name, MSDOS_DOT, MSDOS_NAME) - && strncmp((const char *)walk->dir_ent.name, MSDOS_DOTDOT, - MSDOS_NAME)) - if (scan_dir(fs, walk, file_cd(cp, (char *)walk->dir_ent.name))) - return 1; + if (!IS_FREE(walk->dir_ent.name) && (walk->dir_ent.attr & ATTR_DIR)) + if (scan_dir(fs, walk, file_cd(cp, (char *)walk->dir_ent.name))) + return 1; return 0; } @@ -1092,3 +1050,293 @@ int scan_root(DOS_FS * fs) return 1; return subdirs(fs, NULL, &fp_root); } + +static char print_fat_dirty_state(void) +{ + printf("Dirty bit is set. Fs was not properly unmounted and" + " some data may be corrupt.\n"); + + return get_choice(1, " Automatically removing dirty bit.", + 2, + 1, "Remove dirty bit", + 2, "No action"); +} + +void check_dirty_bits(DOS_FS * fs) +{ + if (fs->fat_bits == 32) { + struct boot_sector b32; + FAT_ENTRY fat32_flags; + + get_fat(&fat32_flags, fs->fat, 1, fs); + fs_read(0, sizeof(b32), &b32); + + if ((b32.boot_flags & FAT_STATE_DIRTY) || !(fat32_flags.value & FAT32_FLAG_CLEAN_SHUTDOWN)) { + if (print_fat_dirty_state() == 1) { + if (b32.boot_flags & FAT_STATE_DIRTY) { + b32.boot_flags &= ~FAT_STATE_DIRTY; + fs_write(0, sizeof(b32), &b32); + } + if (!(fat32_flags.value & FAT32_FLAG_CLEAN_SHUTDOWN)) { + uint32_t *new_flags_ptr = (uint32_t *)(fs->fat + 4); + *new_flags_ptr = htole32(fat32_flags.value | FAT32_FLAG_CLEAN_SHUTDOWN | (fat32_flags.reserved << 28)); + fs_write(fs->fat_start + 4, 4, new_flags_ptr); + if (fs->nfats > 1) + fs_write(fs->fat_start + 4 + fs->fat_size, 4, new_flags_ptr); + } + } + } + } else { + struct boot_sector_16 b16; + FAT_ENTRY fat16_flags; + int fat16_is_dirty = 0; + + fs_read(0, sizeof(b16), &b16); + + if (fs->fat_bits == 16) { + get_fat(&fat16_flags, fs->fat, 1, fs); + fat16_is_dirty = !(fat16_flags.value & FAT16_FLAG_CLEAN_SHUTDOWN); + } + + if ((b16.boot_flags & FAT_STATE_DIRTY) || fat16_is_dirty) { + if (print_fat_dirty_state() == 1) { + if (b16.boot_flags & FAT_STATE_DIRTY) { + b16.boot_flags &= ~FAT_STATE_DIRTY; + fs_write(0, sizeof(b16), &b16); + } + if (fat16_is_dirty) { + uint16_t *new_flags_ptr = (uint16_t *)(fs->fat + 2); + *new_flags_ptr = htole16(fat16_flags.value | FAT16_FLAG_CLEAN_SHUTDOWN); + fs_write(fs->fat_start + 2, 2, new_flags_ptr); + if (fs->nfats > 1) + fs_write(fs->fat_start + 2 + fs->fat_size, 2, new_flags_ptr); + } + } + } + } +} + +static void get_new_label(char doslabel[12]) +{ + char newlabel[256]; + size_t len; + char *p; + int ret; + int i; + + while (1) { + if (get_line("New label", newlabel, sizeof(newlabel))) { + if ((p = strchr(newlabel, '\n'))) + *p = 0; + + len = mbstowcs(NULL, newlabel, 0); + if (len != (size_t)-1 && len > 11) { + printf("Label can be no longer than 11 characters\n"); + continue; + } + + if (!local_string_to_dos_string(doslabel, newlabel, 12)) { + printf("Error when processing label\n"); + continue; + } + + for (i = strlen(doslabel); i < 11; ++i) + doslabel[i] = ' '; + doslabel[11] = 0; + + ret = validate_volume_label(doslabel); + if ((ret && only_uppercase_label) || (ret & ~0x1)) { + printf("New label is invalid\n"); + continue; + } else if (ret & 0x1) { + printf("Warning: lowercase labels might not work properly on some systems\n"); + } + + break; + } + } +} + +static int check_boot_label(DOS_FS *fs) +{ + char doslabel[12]; + wchar_t wlabel[12]; + int ret; + int i; + + ret = validate_volume_label(fs->label); + if (ret & ~0x1) { + printf("Label '%s' stored in boot sector is not valid.\n", pretty_label(fs->label)); + switch (get_choice(1, " Auto-removing label from boot sector.", + 2, + 1, "Remove invalid label from boot sector", + 2, "Enter new label")) { + case 1: + write_boot_label(fs, "NO NAME "); + memcpy(fs->label, "NO NAME ", 11); + return 1; + case 2: + get_new_label(doslabel); + write_boot_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + return 1; + } + } else if ((ret & 0x1) && only_uppercase_label) { + printf("Label '%s' stored in boot sector contains lowercase characters.\n", pretty_label(fs->label)); + switch (get_choice(1, " Auto-changing lowercase characters to uppercase", + 3, + 1, "Change lowercase characters to uppercase", + 2, "Remove invalid label", + 2, "Set new label")) { + case 1: + if (!dos_string_to_wchar_string(wlabel, fs->label, sizeof(wlabel))) + die("Cannot change lowercase characters to uppercase."); + for (i = 0; i < 11; ++i) + wlabel[i] = towupper(wlabel[i]); + if (!wchar_string_to_dos_string(doslabel, wlabel, sizeof(doslabel))) + die("Cannot change lowercase characters to uppercase."); + write_boot_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + return 1; + case 2: + write_boot_label(fs, "NO NAME "); + memcpy(fs->label, "NO NAME ", 11); + return 1; + case 3: + get_new_label(doslabel); + write_boot_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + return 1; + } + } + + return 0; +} + +void check_label(DOS_FS *fs) +{ + DIR_ENT de; + off_t offset; + char buffer[256]; + char doslabel[12]; + wchar_t wlabel[12]; + int ret; + int i; + + offset = find_volume_de(fs, &de); + + if (offset == 0 && memcmp(fs->label, "NO NAME ", 11) != 0) + check_boot_label(fs); + + if (offset == 0 && memcmp(fs->label, "NO NAME ", 11) != 0) { + printf("Label in boot sector is '%s', but there is no volume label in root directory.\n", pretty_label(fs->label)); + switch (get_choice(1, " Auto-removing label from boot sector.", + 2, + 1, "Remove label from boot sector", + 2, "Copy label from boot sector to root directory")) { + case 1: + write_boot_label(fs, "NO NAME "); + memcpy(fs->label, "NO NAME ", 11); + break; + case 2: + write_volume_label(fs, fs->label); + offset = find_volume_de(fs, &de); + break; + } + } + + if (offset != 0) { + memcpy(doslabel, de.name, 11); + if (doslabel[0] == 0x05) + doslabel[0] = 0xe5; + ret = validate_volume_label(doslabel); + if (ret & ~0x1) { + printf("Volume label '%s' stored in root directory is not valid.\n", pretty_label(doslabel)); + switch (get_choice(1, " Auto-removing label.", + 2, + 1, "Remove invalid label", + 2, "Set new label")) { + case 1: + remove_label(fs); + memcpy(fs->label, "NO NAME ", 11); + offset = 0; + break; + case 2: + get_new_label(doslabel); + write_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + break; + } + } else if ((ret & 0x1) && only_uppercase_label) { + printf("Volume label '%s' stored in root directory contains lowercase characters.\n", pretty_label(doslabel)); + switch (get_choice(1, " Auto-changing lowercase characters to uppercase", + 3, + 1, "Change lowercase characters to uppercase", + 2, "Remove invalid label", + 2, "Set new label")) { + case 1: + if (!dos_string_to_wchar_string(wlabel, doslabel, sizeof(wlabel))) + die("Cannot change lowercase characters to uppercase."); + for (i = 0; i < 11; ++i) + wlabel[i] = towupper(wlabel[i]); + if (!wchar_string_to_dos_string(doslabel, wlabel, sizeof(doslabel))) + die("Cannot change lowercase characters to uppercase."); + write_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + break; + case 2: + remove_label(fs); + memcpy(fs->label, "NO NAME ", 11); + offset = 0; + break; + case 3: + get_new_label(doslabel); + write_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + break; + } + } + } + +again: + + if (offset != 0 && memcmp(fs->label, "NO NAME ", 11) == 0 && memcmp(doslabel, "NO NAME ", 11) != 0) { + printf("There is no label in boot sector, but there is volume label '%s' stored in root directory\n", pretty_label(doslabel)); + switch (get_choice(1, " Auto-copying volume label from root directory to boot sector.", + 2, + 1, "Copy volume label from root directory to boot sector", + 2, "Remove volume label from root directory")) { + case 1: + write_boot_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + break; + case 2: + remove_label(fs); + offset = 0; + break; + } + } + + if (offset != 0 && memcmp(fs->label, "NO NAME ", 11) != 0 && memcmp(fs->label, doslabel, 11) != 0) { + strncpy(buffer, pretty_label(doslabel), sizeof(buffer)-1); + buffer[sizeof(buffer)-1] = 0; + printf("Volume label '%s' stored in root directory and label '%s' stored in boot sector and different.\n", buffer, pretty_label(fs->label)); + switch (get_choice(1, " Auto-copying volume label from root directory to boot sector.", + 2, + 1, "Copy volume label from root directory to boot sector", + 2, "Copy label from boot sector to root directory")) { + case 1: + write_boot_label(fs, doslabel); + memcpy(fs->label, doslabel, 11); + break; + case 2: + ret = check_boot_label(fs); + if (ret) + goto again; + write_volume_label(fs, fs->label); + offset = find_volume_de(fs, &de); + /* NOTE: doslabel is not updated */ + break; + } + } +} |