std1.0.0-beta-7 SafeC docs

Source fs/fat.sc

View in the repository

// SafeC Standard Library — FAT32 Read-Only Driver Implementation
#pragma once
#include <std/stddef.h>
#include <std/fs/fat.h>
#include <std/fs/block.h>

namespace std {

extern int   strncmp(const char* a, const char* b, size_t n);
extern size_t strlen(const char* s);

#define FAT32_EOC   0x0FFFFFF8U   // end-of-chain marker
#define FAT32_FREE  0x00000000U
#define FAT_ATTR_DIR  0x10

// Read a 32-bit little-endian value.
static unsigned int fat_read32_(const unsigned char* p) {
    unsafe {
        return (unsigned int)p[0] | ((unsigned int)p[1] << 8)
             | ((unsigned int)p[2] << 16) | ((unsigned int)p[3] << 24);
    }
    return (unsigned int)0;
}

// Read a 16-bit little-endian value.
static unsigned short fat_read16_(const unsigned char* p) {
    unsafe { return (unsigned short)p[0] | ((unsigned short)p[1] << 8); }
    return (unsigned short)0;
}

int FatCtx::init(&BlockDevice dev, unsigned long lba) {
    self.dev = *dev;
    self.partition_lba = lba;
    unsigned char boot[512];
    if (dev.read(lba, boot[0..512]) != 0) { return -1; }
    // BPB parsing
    unsafe {
        self.bpb.bytes_per_sector     = fat_read16_(boot + 11);
        self.bpb.sectors_per_cluster  = boot[13];
        self.bpb.reserved_sectors     = fat_read16_(boot + 14);
        self.bpb.num_fats             = boot[16];
        self.bpb.root_entry_count     = fat_read16_(boot + 17);
        self.bpb.fat_size_16          = fat_read16_(boot + 22);
        self.bpb.fat_size_32          = fat_read32_(boot + 36);
        self.bpb.root_cluster         = fat_read32_(boot + 44);
    }
    // Validate FAT32 signature
    unsigned char sig0; unsigned char sig1;
    unsafe { sig0 = boot[510]; sig1 = boot[511]; }
    if (sig0 != (unsigned char)0x55 || sig1 != (unsigned char)0xAA) { return -1; }

    unsigned long fat_size = (unsigned long)self.bpb.fat_size_32;
    self.fat_lba  = lba + (unsigned long)self.bpb.reserved_sectors;
    self.data_lba = self.fat_lba + (unsigned long)self.bpb.num_fats * fat_size;
    self.root_cluster = (unsigned long)self.bpb.root_cluster;
    self.bytes_per_cluster = (unsigned long)self.bpb.bytes_per_sector
                          * (unsigned long)self.bpb.sectors_per_cluster;
    // Directory walks and reads stage one whole cluster in a fixed
    // FAT_MAX_CLUSTER-byte stack buffer; refuse larger clusters (64-sector
    // clusters are common on big FAT32 volumes) rather than overflow it.
    if (self.bytes_per_cluster == (unsigned long)0 ||
        self.bytes_per_cluster > (unsigned long)FAT_MAX_CLUSTER) { return -1; }
    return 0;
}

// Convert cluster number to LBA.
static unsigned long fat_cluster_to_lba_(struct FatCtx* ctx, unsigned long cluster) {
    unsafe {
        return ctx->data_lba + (cluster - (unsigned long)2)
               * (unsigned long)ctx->bpb.sectors_per_cluster;
    }
}

inline int FatCtx::read_cluster(unsigned long cluster, []unsigned char buf) {
    unsigned long lba;
    unsafe { lba = fat_cluster_to_lba_((struct FatCtx*)self, cluster); }
    unsigned long bytes = (unsigned long)self.bpb.sectors_per_cluster * self.dev.sector_size;
    if (buf.len < bytes) { return -1; }
    return self.dev.read(lba, buf[0..bytes]);
}

inline unsigned long FatCtx::follow_fat(unsigned long start_cluster, unsigned long idx) {
    unsigned long cluster = start_cluster;
    unsigned long i = (unsigned long)0;
    unsigned char fat_sect[512];
    while (i < idx) {
        unsigned long fat_byte_off = cluster * (unsigned long)4;
        unsigned long fat_sector   = fat_byte_off / (unsigned long)512;
        unsigned long fat_off_in   = fat_byte_off % (unsigned long)512;
        if (self.dev.read(self.fat_lba + fat_sector, fat_sect[0..512]) != 0) {
            return (unsigned long)0;
        }
        unsigned int next;
        unsafe { next = fat_read32_((const unsigned char*)fat_sect + fat_off_in) & (unsigned int)0x0FFFFFFF; }
        if ((unsigned long)next >= (unsigned long)FAT32_EOC) { return (unsigned long)0; }
        cluster = (unsigned long)next;
        i = i + (unsigned long)1;
    }
    return cluster;
}

// Convert 8.3 directory-entry name to null-terminated string.
static void fat_name83_(const unsigned char* entry, char* out) {
    unsafe {
        int i = 0;
        int pos = 0;
        while (i < 8 && entry[i] != (unsigned char)0x20) {
            out[pos] = (char)entry[i];
            pos = pos + 1;
            i = i + 1;
        }
        if (entry[8] != (unsigned char)0x20) {
            out[pos] = '.'; pos = pos + 1;
            i = 8;
            while (i < 11 && entry[i] != (unsigned char)0x20) {
                out[pos] = (char)entry[i];
                pos = pos + 1;
                i = i + 1;
            }
        }
        out[pos] = (char)0;
    }
}

// Case-insensitive ASCII compare for FAT names.
static int fat_namecmp_(const char* a, const char* b) {
    unsafe {
        int i = 0;
        while (a[i] != (char)0 && b[i] != (char)0) {
            char ca = a[i];
            char cb = b[i];
            if (ca >= 'a' && ca <= 'z') { ca = (char)(ca - 32); }
            if (cb >= 'a' && cb <= 'z') { cb = (char)(cb - 32); }
            if (ca != cb) { return (int)ca - (int)cb; }
            i = i + 1;
        }
        return (int)a[i] - (int)b[i];
    }
    return 0;
}

// Walk one path component; returns cluster of found entry, 0 on miss.
// `is_dir_out` set to 1 if the entry is a directory.
static unsigned long fat_walk_component_(struct FatCtx* ctx,
                                          unsigned long dir_cluster,
                                          const char* component,
                                          &int is_dir_out,
                                          &unsigned long size_out) {
    unsigned char buf[FAT_MAX_CLUSTER];
    unsigned long cluster = dir_cluster;
    while (cluster != (unsigned long)0) {
        if (ctx->read_cluster(cluster, buf[0..FAT_MAX_CLUSTER]) != 0) { break; }
        unsigned long entries;
        unsafe { entries = ctx->bytes_per_cluster / (unsigned long)32; }
        unsigned long e = (unsigned long)0;
        while (e < entries) {
            unsafe {
                unsigned char* entry = (unsigned char*)buf + e * (unsigned long)32;
                if (entry[0] == (unsigned char)0x00) { return (unsigned long)0; }
                if (entry[0] == (unsigned char)0xE5) { e = e + (unsigned long)1; continue; }
                unsigned char attr = entry[11];
                if ((attr & (unsigned char)0x0F) == (unsigned char)0x0F) {
                    // Long file name entry — skip
                    e = e + (unsigned long)1;
                    continue;
                }
                char name[13];
                fat_name83_(entry, (char*)name);
                if (fat_namecmp_((const char*)name, component) == 0) {
                    *is_dir_out = ((attr & (unsigned char)FAT_ATTR_DIR) != (unsigned char)0) ? 1 : 0;
                    *size_out = (unsigned long)fat_read32_(entry + 28);
                    unsigned long hi = (unsigned long)fat_read16_(entry + 20);
                    unsigned long lo = (unsigned long)fat_read16_(entry + 26);
                    return (hi << 16) | lo;
                }
            }
            e = e + (unsigned long)1;
        }
        cluster = ctx->follow_fat(cluster, (unsigned long)1);
    }
    return (unsigned long)0;
}

// Tokenise path into components; write into `parts`; return count.
static int fat_split_path_(const char* path, char parts[][VFS_MAX_NAME], int max_parts) {
    int count = 0;
    unsigned long i = (unsigned long)0;
    unsigned long plen = (unsigned long)0;
    unsafe {
        while (path[i] != (char)0 && count < max_parts) {
            if (path[i] == '/') {
                if (plen > (unsigned long)0) {
                    parts[count][plen] = (char)0;
                    count = count + 1;
                    plen = (unsigned long)0;
                }
            } else {
                if (plen < (unsigned long)VFS_MAX_NAME - (unsigned long)1) {
                    parts[count][plen] = path[i];
                    plen = plen + (unsigned long)1;
                }
            }
            i = i + (unsigned long)1;
        }
        // A path deeper than max_parts leaves the loop with count ==
        // max_parts: never write a terminator into parts[max_parts].
        if (plen > (unsigned long)0 && count < max_parts) {
            parts[count][plen] = (char)0;
            count = count + 1;
        }
    }
    return count;
}

// ── FileSystem methods (see vfs.h) ────────────────────────────────────────────

int FatCtx::open(const &char path_ref, int flags, &VfsNode node_out) {
    const char* path = path_ref;
    struct FatCtx* fc;
    unsafe { fc = (struct FatCtx*)self; }
    // Tokenise path.
    char parts[FAT_MAX_DEPTH][VFS_MAX_NAME];
    int n = fat_split_path_(path, parts, FAT_MAX_DEPTH);
    if (n == 0) {
        // Root directory.
        unsafe {
            node_out.inode = fc->root_cluster;
            node_out.type  = VFS_TYPE_DIR;
            node_out.size  = (unsigned long)0;
            node_out.name[0] = '/'; node_out.name[1] = (char)0;
        }
        return 0;
    }
    unsigned long cluster;
    unsafe { cluster = fc->root_cluster; }
    int i = 0;
    int is_dir = 1;
    unsigned long fsize = (unsigned long)0;
    while (i < n) {
        int d = 0;
        unsigned long next;
        unsafe {
            next = fat_walk_component_(fc, cluster, (const char*)parts[i], (&int)&d, (&unsigned long)&fsize);
        }
        if (next == (unsigned long)0) { return -1; }
        cluster = next;
        is_dir  = d;
        i = i + 1;
    }
    unsafe {
        node_out.inode = cluster;
        node_out.type  = (is_dir != 0) ? VFS_TYPE_DIR : VFS_TYPE_FILE;
        node_out.size  = (is_dir != 0) ? (unsigned long)0 : fsize;
        unsigned long nlen = strlen(parts[n - 1]);
        unsigned long k = (unsigned long)0;
        while (k < nlen && k < (unsigned long)VFS_MAX_NAME - (unsigned long)1) {
            node_out.name[k] = parts[n-1][k];
            k = k + (unsigned long)1;
        }
        node_out.name[k] = (char)0;
    }
    return 0;
}

unsigned long FatCtx::read(unsigned long inode, unsigned long offset, []unsigned char buf) {
    unsigned long len = buf.len;
    struct FatCtx* fc;
    unsafe { fc = (struct FatCtx*)self; }
    unsigned long bpc;
    unsafe { bpc = fc->bytes_per_cluster; }
    unsigned long cluster_idx = offset / bpc;
    unsigned long off_in = offset % bpc;
    unsigned long cluster = fc->follow_fat(inode, cluster_idx);
    if (cluster == (unsigned long)0) { return (unsigned long)0; }

    unsigned char cbuf[FAT_MAX_CLUSTER];
    if (fc->read_cluster(cluster, cbuf[0..FAT_MAX_CLUSTER]) != 0) {
        return (unsigned long)0;
    }
    unsigned long avail = bpc - off_in;
    if (avail > len) { avail = len; }
    unsafe {
        unsigned long k = (unsigned long)0;
        while (k < avail) {
            buf[k] = cbuf[off_in + k];
            k = k + (unsigned long)1;
        }
    }
    return avail;
}

int FatCtx::readdir_raw_(unsigned long inode, fn void(&VfsNode, ?&Erased) cb, ?&Erased user) {
    struct FatCtx* fc;
    unsafe { fc = (struct FatCtx*)self; }
    int count = 0;
    unsigned long cluster = inode;
    unsigned char buf[FAT_MAX_CLUSTER];
    while (cluster != (unsigned long)0) {
        if (fc->read_cluster(cluster, buf[0..FAT_MAX_CLUSTER]) != 0) { break; }
        unsigned long entries;
        unsafe { entries = fc->bytes_per_cluster / (unsigned long)32; }
        unsigned long e = (unsigned long)0;
        while (e < entries) {
            unsafe {
                unsigned char* entry = (unsigned char*)buf + e * (unsigned long)32;
                if (entry[0] == (unsigned char)0x00) { goto done_; }
                if (entry[0] == (unsigned char)0xE5) { e = e + (unsigned long)1; continue; }
                unsigned char attr = entry[11];
                if ((attr & (unsigned char)0x0F) == (unsigned char)0x0F) {
                    e = e + (unsigned long)1;
                    continue;
                }
                struct VfsNode node;
                char name[13];
                fat_name83_(entry, (char*)name);
                unsigned long nlen = strlen((const char*)name);
                unsigned long k = (unsigned long)0;
                while (k < nlen && k < (unsigned long)VFS_MAX_NAME - (unsigned long)1) {
                    node.name[k] = name[k]; k = k + (unsigned long)1;
                }
                node.name[k] = (char)0;
                node.type     = ((attr & (unsigned char)FAT_ATTR_DIR) != (unsigned char)0)
                                  ? VFS_TYPE_DIR : VFS_TYPE_FILE;
                node.size     = (unsigned long)fat_read32_(entry + 28);
                unsigned long hi = (unsigned long)fat_read16_(entry + 20);
                unsigned long lo = (unsigned long)fat_read16_(entry + 26);
                node.inode    = (hi << 16) | lo;
                node.fs_ctx   = null;
                cb(&node, user);
                count = count + 1;
            }
            e = e + (unsigned long)1;
        }
        cluster = fc->follow_fat(cluster, (unsigned long)1);
    }
    done_:
    return count;
}

} // namespace std