/..

#CONTENT

#TOP

fs.zig
RS
const std = @import("std");
const mem = std.mem;
const pathutil = std.fs.path;
const term = @import("term.zig");
const disk = @import("disk.zig");

pub const Fs = @This();
const Partition = @import("partition.zig");

cluster_bit_used: u8,
cluster_bit_width: u8,
fat_size: u32,

first_data_sector: u32,
first_fat_sector: u32,
first_root_cluster: u32,

sectors_per_cluster: u8,
bytes_per_sector: u16,

total_sectors: u32,
data_sectors: u32,
reserved_sectors: u32,
root_dir_sectors: u32,
total_clusters: u32,

kind: Kind,
partition: *Partition,

const Kind = enum {
    fat12,
    fat16,
    fat32,
};

const Time = packed struct {
    created_second: u5 = 0,
    created_minute: u6 = 0,
    created_hour: u5 = 0,
};

const Date = packed struct {
    created_day: u5 = 0,
    created_month: u4 = 0,
    created_year: u7 = 0,
};

const File = extern struct {
    name: [8]u8 align(1),
    extension: [3]u8 align(1),
    attributes: u8 align(1),
    reserved_for_windowsNT: u8 align(1) = 0,
    deciseconds: u8 align(1) = 0,
    created_time: Time align(1) = .{},
    created_date: Date align(1) = .{},
    accessed_date: Date align(1) = .{},
    cluster_hi: u16 align(1),
    modified_time: Time align(1) = .{},
    modified_date: Date align(1) = .{},
    cluster_lo: u16 align(1),
    size: u32 align(1),
    fs: *Fs,

    const Self = @This();
    const Error = error{
        NotFound,
        InvalidPath,
    };

    pub fn cluster(self: Self, fs: *Fs) !Cluster {
        return try Cluster.new(fs, (@as(u32, self.cluster_hi) << 16) | @as(u32, self.cluster_lo));
    }

    pub fn open(self: *const Self, path: []const u8) !Self {
        const stem = pathutil.stem(path);
        if (stem.len > 8) {
            @panic("path name too long");
        }

        const ext = pathutil.extension(path)[1..];
        if (ext.len > 3) {
            @panic("extension too long");
        }

        var name = [_]u8{0x20} ** 8;
        var extension = [_]u8{0x20} ** 3;
        mem.copy(u8, &name, stem);
        mem.copy(u8, &extension, ext);

        var files = [_]File{undefined} ** 16;

        var clusters = try self.cluster(self.fs);

        while (true) {
            const sector = clusters.sector(self.fs);

            self.fs.partition.load(.{
                .sector_count = 1,
                .buffer = @ptrCast([*]u8, &files),
                .sector = sector,
            });

            for (files) |file| {
                if (file.name[0] == 0) {
                    return Error.NotFound;
                }

                if (file.name[0] == 0xE5) {
                    continue;
                }

                if (mem.eql(u8, &name, &file.name) and mem.eql(u8, &extension, &file.extension)) {
                    return .{
                        .name = file.name,
                        .extension = file.extension,
                        .attributes = file.attributes,
                        .reserved_for_windowsNT = file.reserved_for_windowsNT,
                        .deciseconds = file.deciseconds,
                        .created_time = file.created_time,
                        .created_date = file.created_date,
                        .accessed_date = file.accessed_date,
                        .cluster_hi = file.cluster_hi,
                        .modified_time = file.modified_time,
                        .modified_date = file.modified_date,
                        .cluster_lo = file.cluster_lo,
                        .size = file.size,
                        .fs = self.fs,
                    };
                }
            }

            clusters = try clusters.next(self.fs);
        }

        return Error.NotFound;
    }

    pub fn read(self: *const Self, buffer: []u8) !void {
        var offset: u32 = 0;
        var scratch = [_]u8{0} ** 512;
        var clusters = try self.cluster(self.fs);
        while (offset < self.size) {
            self.fs.partition.load(.{
                .sector_count = 1,
                .buffer = @ptrCast([*]u8, &scratch),
                .sector = clusters.sector(self.fs),
            });

            mem.copy(u8, buffer[offset .. offset + 512], &scratch);

            clusters = clusters.next(self.fs) catch |err| {
                if (err == Cluster.Error.EndOfChain) {
                    return;
                }
                return err;
            };
            offset += 512;
        }
    }
};

const Metadata = struct {
    name: [256]u8,
    attributes: u8,
};

pub const Parameters = extern struct {
    stub: [3]u8 align(1),
    oem_name: [8]u8 align(1),
    bytes_per_sector: u16 align(1),
    sectors_per_cluster: u8 align(1),
    reserved_sectors: u16 align(1),
    fat_count: u8 align(1),
    root_entry_count: u16 align(1),
    total_sectors16: u16 align(1),
    media_type: u8 align(1),
    table_size16: u16 align(1),
    sectors_per_track: u16 align(1),
    head_side_count: u16 align(1),
    hidden_sector_count: u32 align(1),
    total_sectors32: u32 align(1),
    extended: [54]u8 align(1),

    const Self = @This();

    pub fn extended32(self: *const Self) *align(1) const Extended32 {
        return @ptrCast(*align(1) const Extended32, &self.extended);
    }
};

const Extended32 = extern struct {
    table_size32: u32 align(1),
    flags: u16 align(1),
    version: u16 align(1),
    root_cluster: u32 align(1),
    info: u16 align(1),
    backup_bootsector_sector: u16 align(1),
    reserved_0: [12]u8 align(1),
    drive: u8 align(1),
    reserved_1: u8 align(1),
    boot_signature: u8 align(1),
    volume_id: u32 align(1),
    volume_label: [11]u8 align(1),
    fat_type_label: [8]u8 align(1),
};

pub fn from(partition: *Partition, parameters_addr: usize) Fs {
    const parameters = @intToPtr(*Parameters, parameters_addr);

    var total_sectors = parameters.total_sectors32;
    var fat_size = parameters.extended32().table_size32;

    var root_dir_sectors = (parameters.root_entry_count * 32 + parameters.bytes_per_sector - 1) / parameters.bytes_per_sector;
    var first_data_sector = parameters.reserved_sectors + (parameters.fat_count * fat_size);
    var first_fat_sector = parameters.reserved_sectors;
    var data_sectors = total_sectors - (parameters.reserved_sectors + parameters.fat_count * fat_size + root_dir_sectors);
    var total_clusters = data_sectors / parameters.sectors_per_cluster;

    var kind: Kind = .fat32;
    var first_root_cluster = parameters.extended32().root_cluster;

    term.print("FAT FS TYPE: {s}\r\n", .{@tagName(kind)});

    return .{
        .kind = kind,
        .fat_size = fat_size,
        .cluster_bit_used = 28,
        .cluster_bit_width = 32,
        .first_data_sector = first_data_sector,
        .first_fat_sector = first_fat_sector,
        .sectors_per_cluster = parameters.sectors_per_cluster,
        .bytes_per_sector = parameters.bytes_per_sector,
        .total_sectors = total_sectors,
        .data_sectors = data_sectors,
        .reserved_sectors = parameters.reserved_sectors,
        .total_clusters = total_clusters,
        .partition = partition,
        .first_root_cluster = first_root_cluster,
        .root_dir_sectors = root_dir_sectors,
    };
}

pub fn root(self: *Fs) File {
    return .{
        .name = .{ '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ' },
        .attributes = 0x10,
        .extension = .{ ' ', ' ', ' ' },
        .cluster_hi = @truncate(u16, self.first_root_cluster >> 16),
        .cluster_lo = @truncate(u16, self.first_root_cluster),
        .size = self.root_dir_sectors * self.bytes_per_sector,
        .fs = self,
    };
}

fn cluster_mask(self: *const Fs) u32 {
    return (@as(u32, 1) << @truncate(u5, self.cluster_bit_used)) - 1;
}

pub const Cluster = struct {
    cluster: u32,

    const Self = @This();
    pub const Error = error{
        EndOfChain,
    };

    fn new(fs: *const Fs, cluster: u32) !Self {
        if (cluster >= 0xFFFFFFF8 & fs.cluster_mask()) {
            return Error.EndOfChain;
        }

        return .{ .cluster = cluster & ((@as(u32, 1) << @truncate(u5, fs.cluster_bit_used)) - 1) };
    }

    pub fn sector(self: *const Self, fs: *const Fs) u32 {
        return (self.cluster - 2) * fs.sectors_per_cluster + fs.first_data_sector;
    }

    pub fn next(self: *const Self, fs: *const Fs) !Self {
        var scratch = [_]u8{0} ** 512;
        var cluster = self.cluster;

        var fat_offset: u32 = cluster * 4;

        const next_sector = fs.first_fat_sector + (fat_offset / fs.bytes_per_sector);
        const offset = fat_offset % fs.bytes_per_sector;

        fs.partition.load(.{
            .sector_count = 1,
            .buffer = @ptrCast([*]u8, &scratch),
            .sector = next_sector,
        });

        const raw = @ptrCast([*]u8, &scratch) + offset;
        cluster = @ptrCast(*align(1) u32, raw).*;

        return try Self.new(fs, cluster);
    }
};