fs.zig (8759B)
1 const std = @import("std"); 2 const pi = @import("pi"); 3 4 const vfs = pi.fs.vfs; 5 const sd = pi.devices.sd; 6 const uart = pi.devices.mini_uart; 7 8 const Explorer = struct { 9 alloc: std.mem.Allocator, 10 io_w: *std.Io.Writer, 11 12 fs: vfs.FileSystem, 13 dir: vfs.Directory, 14 path: std.ArrayList([]const u8), 15 16 const Self = @This(); 17 18 pub fn init(alloc: std.mem.Allocator, fs: vfs.FileSystem, io_w: *std.Io.Writer) !Self { 19 var path: std.ArrayList([]const u8) = try .initCapacity(alloc, 1); 20 try path.append(alloc, "/"); 21 22 return .{ 23 .alloc = alloc, 24 .fs = fs, 25 .dir = try fs.open_root(), 26 .path = path, 27 .io_w = io_w, 28 }; 29 } 30 31 pub fn read_line(self: *Self) !void { 32 const path = try std.mem.concat(self.alloc, u8, self.path.items); 33 defer self.alloc.free(path); 34 try self.io_w.print("[{s}] $ ", .{path}); 35 36 var line: std.ArrayList(u8) = try .initCapacity(self.alloc, 16); 37 defer line.deinit(self.alloc); 38 39 // Wait until \n 40 while (true) { 41 const byte = uart.read_byte_sync(); 42 if (byte == '\n') break; 43 44 try line.append(self.alloc, byte); 45 } 46 47 try self.parse_line(line.items); 48 } 49 50 // I would like to apologize for this truly bad parser 51 fn parse_line(self: *Self, line: []const u8) !void { 52 var it = std.mem.splitAny(u8, line, " "); 53 54 const cmd = it.next() orelse return; 55 const entry = it.next() orelse "."; 56 57 if (std.mem.eql(u8, cmd, "ls")) { 58 try self.ls(entry); 59 } else if (std.mem.eql(u8, cmd, "tree")) { 60 try self.tree(entry); 61 } else if (std.mem.eql(u8, cmd, "cd")) { 62 try self.cd(entry); 63 } else if (std.mem.eql(u8, cmd, "stat")) { 64 try self.stat(entry); 65 } else if (std.mem.eql(u8, cmd, "cat")) { 66 try self.cat(entry); 67 } else if (std.mem.eql(u8, cmd, "help")) { 68 try self.io_w.print("Commands: ls, tree, cd, stat, cat, help, exit\n", .{}); 69 } else if (std.mem.eql(u8, cmd, "exit")) { 70 pi.reboot(); 71 } else { 72 try self.io_w.print("\"{s}\": Command not found\n", .{cmd}); 73 } 74 } 75 76 fn open_directory(self: *Self, directory: []const u8) !?vfs.Directory { 77 var entry = try self.dir.open_entry(directory) orelse { 78 try self.io_w.print("\"{s}\": No such file or directory\n", .{directory}); 79 return null; 80 }; 81 defer entry.close(); 82 83 if (!entry.stat().directory) { 84 try self.io_w.print("\"{s}\": Not a directory\n", .{directory}); 85 return null; 86 } 87 88 return try self.fs.open_directory(&entry); 89 } 90 91 fn ls(self: *Self, directory: []const u8) !void { 92 const dir = if (std.mem.eql(u8, directory, ".")) self.dir else try self.open_directory(directory) orelse return; 93 94 try self.io_w.print("Name:\n", .{}); 95 96 var it = try dir.iterate(); 97 while (try it.next()) |entry| { 98 defer entry.close(); 99 100 try self.io_w.print("- {s}\n", .{entry.stat().name}); 101 } 102 103 if (!std.mem.eql(u8, directory, ".")) { 104 dir.close(); 105 } 106 } 107 fn tree(self: *Self, directory: []const u8) !void { 108 const dir = if (std.mem.eql(u8, directory, ".")) self.dir else try self.open_directory(directory) orelse return; 109 110 try self.io_w.print("{s}\n", .{directory}); 111 112 var visited: std.ArrayList(Visited) = try .initCapacity(self.alloc, 0); 113 defer visited.deinit(self.alloc); 114 115 try walk( 116 self.alloc, 117 &self.fs, 118 dir, 119 &visited, 120 1, 121 ); 122 123 for (visited.items) |entry| { 124 const prefix = try make_prefix(self.alloc, entry.level); 125 defer self.alloc.free(prefix); 126 defer self.alloc.free(entry.name); 127 128 try self.io_w.print("{s}{s}\n", .{ prefix, entry.name }); 129 } 130 131 if (!std.mem.eql(u8, directory, ".")) { 132 dir.close(); 133 } 134 } 135 fn cd(self: *Self, directory: []const u8) !void { 136 if (std.mem.eql(u8, directory, ".")) return; 137 138 const dir = try self.open_directory(directory) orelse return; 139 140 self.dir.close(); 141 self.dir = dir; 142 143 if (std.mem.eql(u8, directory, "..")) { 144 self.alloc.free(self.path.pop() orelse unreachable); 145 self.alloc.free(self.path.pop() orelse unreachable); 146 147 if (self.path.items.len == 1) { 148 self.dir.close(); 149 self.dir = try self.fs.open_root(); 150 } 151 152 return; 153 } 154 155 try self.path.append(self.alloc, try self.alloc.dupe(u8, directory)); 156 try self.path.append(self.alloc, try self.alloc.dupe(u8, "/")); 157 } 158 fn stat(self: *Self, file: []const u8) !void { 159 const entry = (try self.dir.open_entry(file)) orelse { 160 try self.io_w.print("\"{s}\": No such file or directory\n", .{file}); 161 return; 162 }; 163 defer entry.close(); 164 165 const info = entry.stat(); 166 167 try self.io_w.print( 168 \\ File: {s} 169 \\ Size: {b} 170 \\ Kind: {s} 171 \\ Hidden: {any} 172 \\Read Only: {any} 173 \\ Creation: {d} 174 \\ Access: {d} 175 \\ Modify: {d} 176 \\ 177 , .{ 178 info.name, 179 info.size, 180 if (info.directory) "directory" else "file", 181 info.read_only, 182 info.hidden, 183 info.creation_time, 184 info.access_time, 185 info.modify_time, 186 }); 187 } 188 fn cat(self: *Self, file: []const u8) !void { 189 var file_entry = (try self.dir.open_entry(file)) orelse { 190 try self.io_w.print("\"{s}\": No such file or directory\n", .{file}); 191 return; 192 }; 193 defer file_entry.close(); 194 195 const file_handle = try self.fs.open_file(&file_entry); 196 defer file_entry.close(); 197 198 var contents: std.ArrayList(u8) = try .initCapacity(self.alloc, file_entry.stat().size); 199 defer contents.deinit(self.alloc); 200 201 while (try file_handle.read()) |bytes| { 202 defer self.alloc.free(bytes); 203 try contents.appendSlice(self.alloc, bytes); 204 } 205 206 try self.io_w.print("{s}\n", .{contents.items}); 207 } 208 209 // Tree 210 const Visited = struct { 211 name: []const u8, 212 level: usize, 213 }; 214 215 fn make_prefix(alloc: std.mem.Allocator, level: usize) ![]const u8 { 216 const prefix = "| "; 217 const base = "|- "; 218 219 return switch (level) { 220 0 => return try alloc.dupe(u8, ""), 221 1 => return try alloc.dupe(u8, base), 222 else => { 223 var out = try alloc.alloc(u8, (level - 1) * prefix.len + base.len); 224 for (0..(level - 1)) |i| { 225 @memcpy(out[i * prefix.len .. (i + 1) * prefix.len], prefix); 226 } 227 @memcpy(out[(level - 1) * prefix.len .. level * prefix.len], base); 228 229 return out; 230 }, 231 }; 232 } 233 234 fn walk(alloc: std.mem.Allocator, fs: *vfs.FileSystem, directory: vfs.Directory, visited: *std.ArrayList(Visited), level: usize) !void { 235 defer directory.close(); 236 237 var it = try directory.iterate(); 238 defer it.close(); 239 240 while (try it.next()) |entry| { 241 defer entry.close(); 242 243 const info = entry.stat(); 244 if (std.mem.eql(u8, info.name, ".") or std.mem.eql(u8, info.name, "..")) continue; 245 246 const dupe_name = try alloc.dupe(u8, info.name); 247 try visited.append(alloc, .{ .level = level, .name = dupe_name }); 248 249 if (info.directory) { 250 try walk( 251 alloc, 252 fs, 253 try fs.open_directory(@constCast(&entry)), 254 visited, 255 level + 1, 256 ); 257 } 258 } 259 } 260 }; 261 262 pub fn main() !void { 263 var buffer: [1024 * 1024]u8 = undefined; 264 var fba: std.heap.FixedBufferAllocator = .init(&buffer); 265 const alloc = fba.allocator(); 266 267 var sd_card = try sd.init(alloc); 268 var fat_vfs: pi.fs.FatVFS = .init(); 269 270 var vfs_disk = vfs.Disk.impl_by(&sd_card); 271 const vfs_fat = vfs.FileSystem.impl_by(&fat_vfs); 272 273 const mbr: pi.fs.mbr.MasterBootRecord = @bitCast(try vfs_disk.read_sector(0)); 274 275 try mbr.debug_print(&uart.writer); 276 277 try vfs_fat.open(alloc, &vfs_disk, mbr.pte1.first_lba); 278 defer vfs_fat.close(); 279 280 var explorer = try Explorer.init(alloc, vfs_fat, &uart.writer); 281 282 while (true) { 283 try explorer.read_line(); 284 } 285 }