pang-o-lin/src/fat12.c

596 lines
19 KiB
C

#include "fat12.h"
#include "fat12-internals.h"
#include "io.h"
#ifdef DEBUG
#include <stdio.h>
#endif
#include <ctype.h>
struct fat_dirent_ptr
{
struct fat_directory_entry entry;
uint32_t sector;
uint32_t offset;
};
void fat12_write_u8(void *ptr, uint8_t val)
{
((uint8_t *)ptr)[0] = (val >> 0) & 0xff;
}
uint8_t fat12_read_u8(void *ptr)
{
uint16_t val = 0;
val |= (((uint8_t *)ptr)[0] << 0) & 0xff;
return val;
}
void fat12_write_u16(void *ptr, uint16_t val)
{
((uint8_t *)ptr)[0] = (val >> 0) & 0xff;
((uint8_t *)ptr)[1] = (val >> 8) & 0xff;
}
uint16_t fat12_read_u16(void *ptr)
{
uint16_t val = 0;
val |= (((uint8_t *)ptr)[0] << 0) & 0x00ff;
val |= (((uint8_t *)ptr)[1] << 8) & 0xff00;
return val;
}
void fat12_write_u32(void *ptr, uint32_t val)
{
((uint8_t *)ptr)[0] = (val >> 0) & 0xff;
((uint8_t *)ptr)[1] = (val >> 8) & 0xff;
((uint8_t *)ptr)[2] = (val >> 16) & 0xff;
((uint8_t *)ptr)[3] = (val >> 24) & 0xff;
}
uint32_t fat12_read_u32(void *ptr)
{
uint16_t val = 0;
val |= (((uint8_t *)ptr)[0] << 0) & 0x000000ff;
val |= (((uint8_t *)ptr)[1] << 8) & 0x0000ff00;
val |= (((uint8_t *)ptr)[2] << 16) & 0x00ff0000;
val |= (((uint8_t *)ptr)[3] << 24) & 0xff000000;
return val;
}
void fat12_set_cluster(void *fat, uint32_t cluster, uint32_t value)
{
uint32_t offset = (cluster * 3) / 2;
// fprintf(stderr, "Cluster %d -> %03x (%02x%02x -> ", cluster, value, ((uint8_t *)fat)[offset], ((uint8_t *)fat)[offset + 1]);
if (cluster & 1)
{
((uint8_t *)fat)[offset] = (((uint8_t *)fat)[offset] & 0x0f) | ((value << 4) & 0xf0);
((uint8_t *)fat)[offset + 1] = value >> 4;
}
else
{
((uint8_t *)fat)[offset] = value;
((uint8_t *)fat)[offset + 1] = (((uint8_t *)fat)[offset + 1] & 0xf0) | ((value >> 8) & 0x0f);
}
// fprintf(stderr, "%02x%02x)\n", ((uint8_t *)fat)[offset], ((uint8_t *)fat)[offset + 1]);
}
uint32_t fat12_get_cluster(void *fat, uint32_t cluster)
{
uint32_t offset = (cluster * 3) / 2;
uint32_t value = 0;
if (cluster & 1)
{
value |= (((uint8_t *)fat)[offset] >> 4) & 0x00f;
value |= (((uint8_t *)fat)[offset + 1] << 4) & 0xff0;
}
else
{
value |= ((uint8_t *)fat)[offset];
value |= (((uint8_t *)fat)[offset + 1] << 8) & 0xf00;
}
// fprintf(stderr, "Cluster %d <- %03x\n", cluster, value);
return value;
}
static void fat_dirent_set_name(struct fat_directory_entry *dirent, const char *filename)
{
int i;
int ext_split = -1;
int name_off = 0;
int dirent_off = 0;
for (name_off = 0; filename[name_off] != '\0'; name_off++)
{
if (filename[name_off] == '.')
ext_split = name_off + 1;
}
for (dirent_off = 0; dirent_off < 8; dirent_off++)
{
if (dirent_off < (ext_split - 1))
dirent->file_name[dirent_off] = toupper(filename[dirent_off]);
else
dirent->file_name[dirent_off] = ' ';
}
name_off = ext_split + 1;
for (dirent_off = 0; dirent_off < 3; dirent_off++)
{
if ((ext_split >= 0) && (filename[ext_split]))
dirent->extension[dirent_off] = toupper(filename[ext_split++]);
else
dirent->extension[dirent_off] = ' ';
}
}
struct fat12_partition *fat12_alloc(void)
{
struct fat12_partition *part = malloc(sizeof(struct fat12_partition));
memset(part, 0, sizeof(*part));
return part;
}
static uint32_t cluster_for_sector(struct fat12_partition *part, uint32_t sector) {
return ((sector - part->first_offset) / part->cluster_size) + 2;
}
static uint32_t sector_for_cluster(struct fat12_partition *part, uint32_t cluster) {
return ((cluster - 2) * part->cluster_size) + part->first_offset;
}
static int fat12_ls_foreach_sector(struct fat12_partition *part, uint32_t sector, uint32_t count,
void *data, int (*callback)(void *data, const struct fat12_dirent *dirent))
{
struct fat12_dirent dirent;
uint8_t buf[part->sector_size];
uint32_t offset;
pang_read(part->io, sector * part->sector_size, buf, sizeof(buf));
for (offset = 0; offset < count; offset++)
{
struct fat_directory_entry *entry = &((struct fat_directory_entry *)buf)[offset];
memset(&dirent, 0, sizeof(dirent));
memcpy(dirent.filename, entry->file_name, 8);
memcpy(dirent.filename + 8, entry->extension, 3);
dirent.first_cluster = fat12_read_u16(&entry->first_cluster);
dirent.size = fat12_read_u32(&entry->file_size);
dirent.file_attributes = entry->file_attributes;
dirent.parent_cluster = sector_for_cluster(part, sector);
if ((entry->file_name[0] != 0xe5) && (entry->file_name[0] != 0x00))
callback(data, &dirent);
}
return 0;
}
int fat12_ls_foreach(struct fat12_partition *part, uint32_t dir_cluster, void *data, int (*callback)(void *data, const struct fat12_dirent *dirent))
{
uint32_t sector;
if (dir_cluster < 2)
{
sector = part->root_offset;
uint32_t dir_entries;
uint32_t max_entries = part->root_entries;
uint32_t entries_per_sector = part->sector_size / sizeof(struct fat_directory_entry);
uint32_t loop = 0;
for (dir_entries = 0; dir_entries < max_entries; dir_entries += entries_per_sector)
{
uint32_t entries_to_list = entries_per_sector;
if (max_entries - dir_entries < entries_to_list)
entries_to_list = max_entries - dir_entries;
fat12_ls_foreach_sector(part, part->root_offset + loop++, entries_to_list, data, callback);
}
}
else
{
sector = dir_cluster * part->cluster_size + part->first_offset;
while (dir_cluster > 1 && dir_cluster < 0xff8)
{
// dir_cluster = fat12_get_cluster(part->fd, dir_cluster);
}
}
return 0;
}
static int update_taf(void *_part, const struct fat12_dirent *dir) {
struct fat12_partition *part = _part;
((uint16_t *)part->taf)[dir->first_cluster] = 0x8000 | dir->parent_cluster;
uint32_t last_cluster = dir->first_cluster;
while (1) {
uint32_t cluster = fat12_get_cluster(part->fat, last_cluster);
if ((cluster == 0) || (cluster >= 0xff8)) {
break;
}
((uint16_t *)part->taf)[cluster] = last_cluster;
last_cluster = cluster;
}
if (dir->file_attributes & FAT_ATTR_DIR)
fat12_ls_foreach(part, sector_for_cluster(part, dir->first_cluster), part, update_taf);
}
void fat12_reload(struct fat12_partition *part)
{
pang_read(part->io, part->fat_offset * part->sector_size, part->fat, part->fat_size * part->sector_size);
memset(part->taf, 0, sizeof(part->taf));
fat12_ls_foreach(part, 0, part, update_taf);
}
int fat12_open(struct fat12_partition *part, const char *filename)
{
if (!part)
return -1;
if (part->io)
return -1;
part->io = pang_open(filename);
if (!part->io)
return -1;
if (pang_read(part->io, 0, part->sector_buffer, sizeof(part->sector_buffer)) != sizeof(part->sector_buffer))
{
if (!errno)
errno = -ENOSPC;
return -1;
}
part->sector_size = fat12_read_u16(part->sector_buffer + 0x0b);
// These are in single-unit quantities
part->root_entries = fat12_read_u16(part->sector_buffer + 0x11);
part->sector_count = fat12_read_u16(part->sector_buffer + 0x13);
// These units are "sectors"
part->fat_offset = fat12_read_u16(part->sector_buffer + 0x0e);
part->fat_size = fat12_read_u16(part->sector_buffer + 0x16);
part->root_offset = part->fat_offset + part->fat_size;
part->first_offset = part->root_offset + (part->root_entries * sizeof(struct fat_directory_entry)) / part->sector_size;
part->cluster_size = fat12_read_u8(part->sector_buffer + 0x0d);
uint32_t bytes = (part->sector_count - part->first_offset) * part->sector_size;
part->cluster_count = bytes / part->sector_size / part->cluster_size;
part->cluster_bytes = (part->cluster_count * 3) / 2;
#ifdef DEBUG
///
fprintf(stderr, "part->sector_size: %d\n", part->sector_size);
// These are in single-unit quantities
fprintf(stderr, "part->root_entries: %d\n", part->root_entries);
fprintf(stderr, "part->sector_count: %d\n", part->sector_count);
// These units are "sectors"
fprintf(stderr, "part->fat_offset: %d\n", part->fat_offset);
fprintf(stderr, "part->fat_size: %d\n", part->fat_size);
fprintf(stderr, "part->root_offset: %d\n", part->root_offset);
fprintf(stderr, "part->first_offset: %d\n", part->first_offset);
fprintf(stderr, "part->cluster_size: %d\n", part->cluster_size);
fprintf(stderr, "part->cluster_count: %d\n", part->cluster_count);
fprintf(stderr, "part->cluster_bytes: %d\n", part->cluster_bytes);
fprintf(stderr, "data_bytes: %d\n", bytes);
///
#endif // DEBUG
if (1)
{
part->fat = malloc(part->fat_size * part->sector_size);
part->taf = malloc(part->cluster_count * sizeof(uint16_t));
fat12_reload(part);
}
return 0;
}
void fat12_flush(struct fat12_partition *part)
{
pang_write(part->io, part->fat_offset * part->sector_size, part->fat, part->fat_size * part->sector_size);
}
int fat12_close(struct fat12_partition *part)
{
if (!part)
return -1;
if (1)
{
fat12_flush(part);
free(part->fat);
}
if (-1 == pang_close(&part->io))
{
return -1;
}
return 0;
}
void fat12_free(struct fat12_partition **part)
{
if (!part)
return;
if (!*part)
return;
if ((*part)->io)
fat12_close(*part);
free(*part);
*part = NULL;
}
uint32_t fat12_find_free_cluster(struct fat12_partition *part, uint32_t start)
{
uint32_t cluster_value;
if (start < 1)
start = 1;
do
{
cluster_value = fat12_get_cluster(part->fat, ++start);
// fprintf(stderr, "Cluster %d value: %03x\n", start, cluster_value);
} while (cluster_value != 0);
// fprintf(stderr, "Found free cluster %d\n", start);
return start;
}
static int get_next_directory_sector(struct fat12_partition *part, int depth, int sector)
{
uint32_t entries_per_sector = part->sector_size / sizeof(struct fat_directory_entry);
if (depth == 0)
{
if ((part->root_offset - sector) * entries_per_sector >= part->root_entries)
return -1;
return sector + 1;
}
uint32_t cluster = cluster_for_sector(part, sector) + 2;
if (((sector + 1) & ((part->cluster_size * part->sector_size) - 1)) == (sector + 1))
{
uint32_t next_cluster = fat12_get_cluster(part->fat, cluster);
if (next_cluster >= 0xff8 || next_cluster == 0)
return -1;
return sector_for_cluster(part, next_cluster);
}
return sector + 1;
}
static int find_object(struct fat12_partition *part, const char *filename[], uint32_t prune, struct fat_dirent_ptr *ptr)
{
uint32_t file_cluster = 0;
uint32_t depth = 0;
// Find the dirent of the file
uint32_t dirent_cluster = 0;
// Filename we're searching for
struct fat_directory_entry dirent;
uint32_t entries_per_sector = part->sector_size / sizeof(struct fat_directory_entry);
struct fat_directory_entry entries[entries_per_sector];
// If the root directory matches, take that.
int sector = part->root_offset;
if (!filename[prune]) {
memset(&ptr->entry, 0, sizeof(ptr->entry));
ptr->sector = sector;
ptr->offset = 0;
return 0;
}
next_entry:
fat_dirent_set_name(&dirent, filename[depth]);
int i = 0;
int check_entry_i;
do
{
pang_read(part->io, sector * part->sector_size, entries, sizeof(entries));
for (check_entry_i = 0; (check_entry_i < entries_per_sector) && (i < part->root_entries); check_entry_i++, i++)
{
if (!memcmp(&entries[check_entry_i], &dirent, 11))
{
if (filename[depth + 1 + prune])
{
depth++;
sector = fat12_read_u16(entries[check_entry_i].first_cluster) * part->cluster_size + part->first_offset;
goto next_entry;
}
ptr->entry = entries[check_entry_i];
ptr->offset = check_entry_i;
ptr->sector = sector;
return 0;
}
}
} while ((sector = get_next_directory_sector(part, depth, sector)) != -1);
// ENOTFOUND
return -1;
}
static int find_file(struct fat12_partition *part, const char *filename[], struct fat_dirent_ptr *ptr)
{
return find_object(part, filename, 0, ptr);
}
static int find_parent(struct fat12_partition *part, const char *filename[], struct fat_dirent_ptr *ptr)
{
return find_object(part, filename, 1, ptr);
}
static int fat12_add_dirent_sector(struct fat12_partition *part, uint32_t sector, uint32_t count, const struct fat_directory_entry *new_dirent)
{
struct fat12_dirent dirent;
uint8_t buf[part->sector_size];
uint32_t offset;
pang_read(part->io, sector * part->sector_size, buf, sizeof(buf));
for (offset = 0; offset < count; offset++)
{
struct fat_directory_entry *entry = &((struct fat_directory_entry *)buf)[offset];
memset(&dirent, 0, sizeof(dirent));
memcpy(dirent.filename, entry->file_name, 8);
memcpy(dirent.filename + 8, entry->extension, 3);
dirent.first_cluster = fat12_read_u16(&entry->first_cluster);
dirent.size = fat12_read_u32(&entry->file_size);
if ((entry->file_name[0] == 0xe5) || (entry->file_name[0] == 0x00))
{
((struct fat_directory_entry *)buf)[offset] = *new_dirent;
pang_write(part->io, sector * part->sector_size, buf, sizeof(buf));
return 0;
}
}
return -1;
}
static int fat12_dir_update_or_add_dirent(struct fat12_partition *part, uint32_t sector, const struct fat_directory_entry *dirent)
{
if (1)
{
uint32_t sector = part->root_offset;
uint32_t dir_entries;
uint32_t max_entries = part->root_entries;
uint32_t entries_per_sector = part->sector_size / sizeof(struct fat_directory_entry);
uint32_t loop = 0;
for (dir_entries = 0; dir_entries < max_entries; dir_entries += entries_per_sector)
{
uint32_t entries_to_list = entries_per_sector;
if (max_entries - dir_entries < entries_to_list)
entries_to_list = max_entries - dir_entries;
// TODO: See if a filename already exists
if (!fat12_add_dirent_sector(part, part->root_offset + loop++, entries_to_list, dirent))
return 0;
}
}
return 0;
}
int fat12_write_file(struct fat12_partition *part, const char *path[], const void *bfr, uint32_t size)
{
// TODO: directory support
uint32_t first_cluster;
uint32_t cluster;
const char *filename;
int i;
for (i = 0; path[i+1]; i++)
;
filename = path[i];
struct fat_dirent_ptr parent;
if (-1 == find_parent(part, path, &parent)) {
// ENOTFOUND
return -1;
}
cluster = first_cluster = fat12_find_free_cluster(part, 1);
struct fat_directory_entry dirent;
memset(&dirent, 0, sizeof(dirent));
fat_dirent_set_name(&dirent, filename);
dirent.file_attributes = FAT_ATTR_FILE;
dirent.reserved1 = 0x18;
fat12_write_u16(dirent.first_cluster, first_cluster);
fat12_write_u32(dirent.file_size, size);
fat12_dir_update_or_add_dirent(part, parent.sector, &dirent);
while (size > 0)
{
uint32_t bytes_to_write = size;
if (bytes_to_write > part->cluster_size * part->sector_size)
bytes_to_write = part->cluster_size * part->sector_size;
pang_write(part->io,
((cluster - 2) * part->cluster_size + part->first_offset) * part->sector_size,
bfr,
bytes_to_write);
size -= bytes_to_write;
// fprintf(stderr, "%s: %d bytes left\n", filename, size);
if (!size)
fat12_set_cluster(part->fat, cluster, 0xff8);
else
{
uint32_t next_cluster = fat12_find_free_cluster(part, cluster);
fat12_set_cluster(part->fat, cluster, next_cluster);
cluster = next_cluster;
}
}
}
int fat12_delete_file(struct fat12_partition *part, const char *filename[])
{
// Find the root cluster of the file
struct fat_dirent_ptr ptr;
if (-1 == find_file(part, filename, &ptr))
{
fprintf(stderr, "Couldn't find filename %s\n", filename[0]);
return 0;
}
// Change the first character of the dirent to 0xe5 to indicate
// it's been deleted.
// Set all clusters in the FAT to 0x000, indicating they're free.
uint32_t this_cluster = fat12_read_u16(ptr.entry.first_cluster);
do
{
uint32_t next_cluster = fat12_get_cluster(part->fat, this_cluster);
// fprintf(stderr, "This cluster: %03d Next cluster: %03d Next-next cluster: %03d\n", this_cluster, next_cluster, next_next_cluster);
fat12_set_cluster(part->fat, this_cluster, 0);
this_cluster = next_cluster;
} while ((this_cluster < 0xff8) && (this_cluster > 1));
// Free the file by setting the first letter of the filename to 0xe5.
ptr.entry.file_name[0] = 0xe5;
struct fat_directory_entry full_dir[part->sector_size / sizeof(struct fat_directory_entry)];
pang_read(part->io, ptr.sector * part->sector_size, full_dir, part->sector_size);
full_dir[ptr.offset] = ptr.entry;
pang_write(part->io, ptr.sector * part->sector_size, full_dir, part->sector_size);
return 0;
}
int fat12_defragment_file(struct fat12_partition *part, const char *path[])
{
struct fat_dirent_ptr ptr;
uint32_t cluster;
if (-1 == find_file(part, path, &ptr))
return -1;
cluster = cluster_for_sector(part, ptr.sector);
while (1) {
uint32_t next_cluster = fat12_get_cluster(part->fat, cluster);
if ((next_cluster == 0) || (cluster >= 0xff8)) {
return 0;
}
if (next_cluster != cluster + 1) {
// File is fragmented!
// We need to figure out if it's part of a chain, or if it's the root node.
if (fat12_get_cluster(part->fat, cluster + 1) == 0) {
// Yay, the cluster is free! Swap our data into here
}
else {
fprintf(stderr, "Cluster is used by: %04x\n", part->taf[cluster + 1]);
/*
// The cluster is used. Figure out if it's in the FAT, or if it's a dirent.
uint32_t fat_scan_offset;
for (fat_scan_offset = 2; fat_scan_offset < part->cluster_count; fat_scan_offset++) {
if (fat12_get_cluster(part->fat, fat_scan_offset) == (cluster + 1)) {
// Found a match, update the FAT entry and swap our data with the one at this offset.
}
}
// Didn't find a match, so it must be a directory entry.
*/
exit(1);
}
}
cluster = next_cluster + 1;
}
}