diff options
Diffstat (limited to 'src/boot.c')
-rw-r--r-- | src/boot.c | 404 |
1 files changed, 297 insertions, 107 deletions
@@ -3,7 +3,8 @@ Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch> 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) 2015-2017 Andreas Bombe <aeb@debian.org> + Copyright (C) 2018-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 @@ -25,6 +26,7 @@ /* FAT32, VFAT, Atari format support, and various fixes additions May 1998 * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */ +#include <limits.h> #include <stdio.h> #include <stdint.h> #include <string.h> @@ -38,6 +40,7 @@ #include "io.h" #include "boot.h" #include "check.h" +#include "charconv.h" #define ROUND_TO_MULTIPLE(n,m) ((n) && (m) ? (n)+(m)-1-((n)-1)%(m) : 0) /* don't divide by zero */ @@ -46,6 +49,132 @@ #define FAT12_THRESHOLD 4085 #define FAT16_THRESHOLD 65525 +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; +} + static struct { uint8_t media; const char *descr; @@ -103,8 +232,8 @@ static void dump_boot(DOS_FS * fs, struct boot_sector *b, unsigned lss) (unsigned long long)fs->fat_start, (unsigned long long)fs->fat_start / lss); printf("%10d FATs, %d bit entries\n", b->fats, fs->fat_bits); - printf("%10lld bytes per FAT (= %llu sectors)\n", (long long)fs->fat_size, - (long long)fs->fat_size / lss); + printf("%10u bytes per FAT (= %u sectors)\n", fs->fat_size, + fs->fat_size / lss); if (!fs->root_cluster) { printf("Root directory starts at byte %llu (sector %llu)\n", (unsigned long long)fs->root_start, @@ -140,11 +269,10 @@ static void check_backup_boot(DOS_FS * fs, struct boot_sector *b, unsigned int l printf("And there is no space for creating one!\n"); return; } - if (interactive) - printf("1) Create one\n2) Do without a backup\n"); - else - printf(" Auto-creating backup boot block.\n"); - if (!interactive || get_key("12", "?") == '1') { + if (get_choice(1, " Auto-creating backup boot block.", + 2, + 1, "Create one", + 2, "Do without a backup") == 1) { unsigned int bbs; /* The usual place for the backup boot sector is sector 6. Choose * that or the last reserved sector. */ @@ -171,7 +299,7 @@ static void check_backup_boot(DOS_FS * fs, struct boot_sector *b, unsigned int l /* there are any differences */ uint8_t *p, *q; int i, pos, first = 1; - char buf[20]; + char buf[32]; printf("There are differences between boot sector and its backup.\n"); printf("This is mostly harmless. Differences: (offset:original/backup)\n "); @@ -190,16 +318,15 @@ static void check_backup_boot(DOS_FS * fs, struct boot_sector *b, unsigned int l } printf("\n"); - if (interactive) - printf("1) Copy original to backup\n" - "2) Copy backup to original\n" "3) No action\n"); - else - printf(" Not automatically fixing this.\n"); - switch (interactive ? get_key("123", "?") : '3') { - case '1': + switch (get_choice(3, " Not automatically fixing this.", + 3, + 1, "Copy original to backup", + 2, "Copy backup to original", + 3, "No action")) { + case 1: fs_write(fs->backupboot_start, sizeof(*b), b); break; - case '2': + case 2: fs_write(0, sizeof(b2), &b2); break; default: @@ -208,9 +335,8 @@ static void check_backup_boot(DOS_FS * fs, struct boot_sector *b, unsigned int l } } -static void init_fsinfo(struct info_sector *i) +static void init_fsinfo_except_reserved(struct info_sector *i) { - memset(i, 0, sizeof (struct info_sector)); i->magic = htole32(0x41615252); i->signature = htole32(0x61417272); i->free_clusters = htole32(-1); @@ -224,11 +350,10 @@ static void read_fsinfo(DOS_FS * fs, struct boot_sector *b, unsigned int lss) if (!b->info_sector) { printf("No FSINFO sector\n"); - if (interactive) - printf("1) Create one\n2) Do without FSINFO\n"); - else - printf(" Not automatically creating it.\n"); - if (interactive && get_key("12", "?") == '1') { + if (get_choice(2, " Not automatically creating it.", + 2, + 1, "Create one", + 2, "Do without FSINFO") == 1) { /* search for a free reserved sector (not boot sector and not * backup boot sector) */ uint32_t s; @@ -236,7 +361,8 @@ static void read_fsinfo(DOS_FS * fs, struct boot_sector *b, unsigned int lss) if (s != le16toh(b->backup_boot)) break; if (s > 0 && s < le16toh(b->reserved)) { - init_fsinfo(&i); + memset(&i, 0, sizeof (struct info_sector)); + init_fsinfo_except_reserved(&i); fs_write((off_t)s * lss, sizeof(i), &i); b->info_sector = htole16(s); fs_write(offsetof(struct boot_sector, info_sector), @@ -272,12 +398,11 @@ static void read_fsinfo(DOS_FS * fs, struct boot_sector *b, unsigned int lss) printf(" Offset %llu: 0x%08x != expected 0x%08x\n", (unsigned long long)offsetof(struct info_sector, boot_sign), le32toh(i.boot_sign), 0xaa550000); - if (interactive) - printf("1) Correct\n2) Don't correct (FSINFO invalid then)\n"); - else - printf(" Auto-correcting it.\n"); - if (!interactive || get_key("12", "?") == '1') { - init_fsinfo(&i); + if (get_choice(1, " Auto-correcting it.", + 2, + 1, "Correct", + 2, "Don't correct (FSINFO invalid then)") == 1) { + init_fsinfo_except_reserved(&i); fs_write(fs->fsinfo_start, sizeof(i), &i); } else fs->fsinfo_start = 0; @@ -287,52 +412,15 @@ static void read_fsinfo(DOS_FS * fs, struct boot_sector *b, unsigned int lss) fs->free_clusters = le32toh(i.free_clusters); } -static char print_fat_dirty_state(void) -{ - printf("Dirty bit is set. Fs was not properly unmounted and" - " some data may be corrupt.\n"); - - if (interactive) { - printf("1) Remove dirty bit\n" "2) No action\n"); - return get_key("12", "?"); - } else - printf(" Automatically removing dirty bit.\n"); - return '1'; -} - -static void check_fat_state_bit(DOS_FS * fs, void *b) -{ - if (fs->fat_bits == 32) { - struct boot_sector *b32 = b; - - if (b32->reserved3 & FAT_STATE_DIRTY) { - printf("0x41: "); - if (print_fat_dirty_state() == '1') { - b32->reserved3 &= ~FAT_STATE_DIRTY; - fs_write(0, sizeof(*b32), b32); - } - } - } else { - struct boot_sector_16 *b16 = b; - - if (b16->reserved2 & FAT_STATE_DIRTY) { - printf("0x25: "); - if (print_fat_dirty_state() == '1') { - b16->reserved2 &= ~FAT_STATE_DIRTY; - fs_write(0, sizeof(*b16), b16); - } - } - } -} - void read_boot(DOS_FS * fs) { struct boot_sector b; unsigned total_sectors; unsigned int logical_sector_size, sectors; - off_t fat_length; + long long fat_length; unsigned total_fat_entries; off_t data_size; + long long position; fs_read(0, sizeof(b), &b); logical_sector_size = GET_UNALIGNED_W(b.sector_size); @@ -343,7 +431,7 @@ void read_boot(DOS_FS * fs) /* if the platform needs special handling of unaligned multibyte accesses */ /* but such handling isn't being provided. See GET_UNALIGNED_W() above. */ if (logical_sector_size & (SECTOR_SIZE - 1)) - die("Logical sector size (%d bytes) is not a multiple of the physical " + die("Logical sector size (%u bytes) is not a multiple of the physical " "sector size.", logical_sector_size); fs->cluster_size = b.cluster_size * logical_sector_size; @@ -357,8 +445,11 @@ void read_boot(DOS_FS * fs) if (verbose) printf("Checking we can access the last sector of the filesystem\n"); /* Can't access last odd sector anyway, so round down */ - fs_test((off_t)((total_sectors & ~1) - 1) * logical_sector_size, - logical_sector_size); + position = (long long)((total_sectors & ~1) - 1) * logical_sector_size; + if (position > OFF_MAX) + die("Filesystem is too large."); + if (!fs_test(position, logical_sector_size)) + die("Failed to read sector %u.", (total_sectors & ~1) - 1); fat_length = le16toh(b.fat_length) ? le16toh(b.fat_length) : le32toh(b.fat32_length); @@ -366,14 +457,22 @@ void read_boot(DOS_FS * fs) die("FAT size is zero."); fs->fat_start = (off_t)le16toh(b.reserved) * logical_sector_size; - fs->root_start = ((off_t)le16toh(b.reserved) + b.fats * fat_length) * + position = (le16toh(b.reserved) + b.fats * fat_length) * logical_sector_size; + if (position > OFF_MAX) + die("Filesystem is too large."); + fs->root_start = position; fs->root_entries = GET_UNALIGNED_W(b.dir_entries); - fs->data_start = fs->root_start + ROUND_TO_MULTIPLE(fs->root_entries << - MSDOS_DIR_BITS, - logical_sector_size); - - data_size = (off_t)total_sectors * logical_sector_size - fs->data_start; + position = (long long)fs->root_start + + ROUND_TO_MULTIPLE(fs->root_entries << MSDOS_DIR_BITS, + logical_sector_size); + if (position > OFF_MAX) + die("Filesystem is too large."); + fs->data_start = position; + position = (long long)total_sectors * logical_sector_size - fs->data_start; + if (position > OFF_MAX) + die("Filesystem is too large."); + data_size = position; if (data_size < fs->cluster_size) die("Filesystem has no space for any data clusters"); @@ -390,23 +489,22 @@ void read_boot(DOS_FS * fs) * (root_entries != 0), we handle the root dir the old way. Give a * warning, but convertig to a root dir in a cluster chain seems * to complex for now... */ - printf("Warning: FAT32 root dir not in cluster chain! " + fprintf(stderr, "Warning: FAT32 root dir not in cluster chain! " "Compatibility mode...\n"); else if (!fs->root_cluster && !fs->root_entries) die("No root directory!"); else if (fs->root_cluster && fs->root_entries) - printf("Warning: FAT32 root dir is in a cluster chain, but " + fprintf(stderr, "Warning: FAT32 root dir is in a cluster chain, but " "a separate root dir\n" " area is defined. Cannot fix this easily.\n"); if (fs->data_clusters < FAT16_THRESHOLD) - printf("Warning: Filesystem is FAT32 according to fat_length " + fprintf(stderr, "Warning: Filesystem is FAT32 according to fat_length " "and fat32_length fields,\n" " but has only %lu clusters, less than the required " "minimum of %d.\n" " This may lead to problems on some systems.\n", (unsigned long)fs->data_clusters, FAT16_THRESHOLD); - check_fat_state_bit(fs, &b); fs->backupboot_start = le16toh(b.backup_boot) * logical_sector_size; check_backup_boot(fs, &b, logical_sector_size); @@ -418,7 +516,6 @@ void read_boot(DOS_FS * fs) if (fs->data_clusters >= FAT16_THRESHOLD) die("Too many clusters (%lu) for FAT16 filesystem.", (unsigned long)fs->data_clusters); - check_fat_state_bit(fs, &b); } else { /* On Atari, things are more difficult: GEMDOS always uses 12bit FATs * on floppies, and always 16 bit on harddisks. */ @@ -433,23 +530,29 @@ void read_boot(DOS_FS * fs) } /* On FAT32, the high 4 bits of a FAT entry are reserved */ fs->eff_fat_bits = (fs->fat_bits == 32) ? 28 : fs->fat_bits; - fs->fat_size = fat_length * logical_sector_size; + position = fat_length * logical_sector_size; + if (position > OFF_MAX) + die("Filesystem is too large."); + fs->fat_size = position; - fs->label = calloc(12, sizeof(uint8_t)); + fs->label[0] = 0; if (fs->fat_bits == 12 || fs->fat_bits == 16) { struct boot_sector_16 *b16 = (struct boot_sector_16 *)&b; - if (b16->extended_sig == 0x29) + if (b16->extended_sig == 0x29) { memmove(fs->label, b16->label, 11); - else - fs->label = NULL; + fs->serial = b16->serial; + } } else if (fs->fat_bits == 32) { - if (b.extended_sig == 0x29) + if (b.extended_sig == 0x29) { memmove(fs->label, &b.label, 11); - else - fs->label = NULL; + fs->serial = b.serial; + } } - total_fat_entries = (uint64_t)fs->fat_size * 8 / fs->fat_bits; + position = (long long)fs->fat_size * 8 / fs->fat_bits; + if (position > UINT_MAX) + die("FAT has space for too many entries (%lld).", (long long)position); + total_fat_entries = position; if (fs->data_clusters > total_fat_entries - 2) die("Filesystem has %u clusters but only space for %u FAT entries.", fs->data_clusters, total_fat_entries - 2); @@ -470,7 +573,8 @@ void read_boot(DOS_FS * fs) dump_boot(fs, &b, logical_sector_size); } -static void write_boot_label(DOS_FS * fs, char *label) +static void write_boot_label_or_serial(int label_mode, DOS_FS * fs, + const char *label, uint32_t serial) { if (fs->fat_bits == 12 || fs->fat_bits == 16) { struct boot_sector_16 b16; @@ -479,10 +583,16 @@ static void write_boot_label(DOS_FS * fs, char *label) if (b16.extended_sig != 0x29) { b16.extended_sig = 0x29; b16.serial = 0; + memmove(b16.label, "NO NAME ", 11); memmove(b16.fs_type, fs->fat_bits == 12 ? "FAT12 " : "FAT16 ", 8); } - memmove(b16.label, label, 11); + + if (label_mode) + memmove(b16.label, label, 11); + else + b16.serial = serial; + fs_write(0, sizeof(b16), &b16); } else if (fs->fat_bits == 32) { struct boot_sector b; @@ -491,15 +601,31 @@ static void write_boot_label(DOS_FS * fs, char *label) if (b.extended_sig != 0x29) { b.extended_sig = 0x29; b.serial = 0; + memmove(b.label, "NO NAME ", 11); memmove(b.fs_type, "FAT32 ", 8); } - memmove(b.label, label, 11); + + if (label_mode) + memmove(b.label, label, 11); + else + b.serial = serial; + fs_write(0, sizeof(b), &b); if (fs->backupboot_start) fs_write(fs->backupboot_start, sizeof(b), &b); } } +void write_boot_label(DOS_FS * fs, const char *label) +{ + write_boot_label_or_serial(1, fs, label, 0); +} + +void write_serial(DOS_FS * fs, uint32_t serial) +{ + write_boot_label_or_serial(0, fs, NULL, serial); +} + off_t find_volume_de(DOS_FS * fs, DIR_ENT * de) { uint32_t cluster; @@ -513,7 +639,13 @@ off_t find_volume_de(DOS_FS * fs, DIR_ENT * de) offset = cluster_start(fs, cluster); for (i = 0; i * sizeof(DIR_ENT) < fs->cluster_size; i++) { fs_read(offset, sizeof(DIR_ENT), de); - if (de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME) + + /* no point in scanning after end of directory marker */ + if (!de->name[0]) + return 0; + + if (!IS_FREE(de->name) && + de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME) return offset; offset += sizeof(DIR_ENT); } @@ -522,7 +654,13 @@ off_t find_volume_de(DOS_FS * fs, DIR_ENT * de) for (i = 0; i < fs->root_entries; i++) { offset = fs->root_start + i * sizeof(DIR_ENT); fs_read(offset, sizeof(DIR_ENT), de); - if (de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME) + + /* no point in scanning after end of directory marker */ + if (!de->name[0]) + return 0; + + if (!IS_FREE(de->name) && + de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME) return offset; } } @@ -530,10 +668,10 @@ off_t find_volume_de(DOS_FS * fs, DIR_ENT * de) return 0; } -static void write_volume_label(DOS_FS * fs, char *label) +void write_volume_label(DOS_FS * fs, char *label) { - time_t now = time(NULL); - struct tm *mtime = localtime(&now); + time_t now; + struct tm *mtime; off_t offset; int created; DIR_ENT de; @@ -544,13 +682,25 @@ static void write_volume_label(DOS_FS * fs, char *label) created = 1; offset = alloc_rootdir_entry(fs, &de, label, 0); } + memcpy(de.name, label, 11); - de.time = htole16((unsigned short)((mtime->tm_sec >> 1) + - (mtime->tm_min << 5) + - (mtime->tm_hour << 11))); - de.date = htole16((unsigned short)(mtime->tm_mday + - ((mtime->tm_mon + 1) << 5) + - ((mtime->tm_year - 80) << 9))); + if (de.name[0] == 0xe5) + de.name[0] = 0x05; + + now = time(NULL); + mtime = (now != (time_t)-1) ? localtime(&now) : NULL; + if (mtime && mtime->tm_year >= 80 && mtime->tm_year <= 207) { + de.time = htole16((unsigned short)((mtime->tm_sec >> 1) + + (mtime->tm_min << 5) + + (mtime->tm_hour << 11))); + de.date = htole16((unsigned short)(mtime->tm_mday + + ((mtime->tm_mon + 1) << 5) + + ((mtime->tm_year - 80) << 9))); + } else { + /* fallback to 1.1.1980 00:00:00 */ + de.time = htole16(0); + de.date = htole16(1 + (1 << 5)); + } if (created) { de.attr = ATTR_VOLUME; de.ctime_ms = 0; @@ -575,3 +725,43 @@ void write_label(DOS_FS * fs, char *label) write_boot_label(fs, label); write_volume_label(fs, label); } + +void remove_label(DOS_FS *fs) +{ + off_t offset; + DIR_ENT de; + + write_boot_label(fs, "NO NAME "); + + offset = find_volume_de(fs, &de); + if (offset) { + /* mark entry as deleted */ + de.name[0] = 0xe5; + /* remove ATTR_VOLUME for compatibility with older fatlabel version + * which ignores above deletion mark for entries with ATTR_VOLUME */ + de.attr = 0; + fs_write(offset, sizeof(DIR_ENT), &de); + } +} + +const char *pretty_label(const char *label) +{ + static char buffer[256]; + char *p; + int i; + int last; + + for (last = 10; last >= 0; last--) { + if (label[last] != ' ') + break; + } + + p = buffer; + for (i = 0; i <= last && label[i] && p < buffer + sizeof(buffer) - 1; ++i) { + if (!dos_char_to_printable(&p, label[i], buffer + sizeof(buffer) - 1 - p)) + *p++ = '_'; + } + *p = 0; + + return buffer; +} |