Skip to content

Commit 1f829ba

Browse files
author
Felix "xq" Queißner
committed
Implements filesystem creation for VFAT with copying of files
1 parent 155f4bf commit 1f829ba

File tree

5 files changed

+263
-269
lines changed

5 files changed

+263
-269
lines changed

data/rootfs.dis

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ mkdir /boot/EFI/nixos/.extra-files/
33
mkdir /Users/xq/
44

55
# copy-XXX uses <dst> <src> syntax as it's consistent with other paths
6-
copy-dir /Windows ./dummy/Windows
7-
copy-file /Users/xq/README.md ./dummy/README.md
6+
copy-dir /Windows ./rootfs/Windows
7+
copy-file /Users/xq/README.md ./rootfs/README.md
88

99
# create-file <path> <size> <contents> creates nested data
1010
create-file /Users/xq/blob.data 512k fill 0x70

src/components/fs/FatFileSystem.zig

+47-14
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,9 @@ fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void {
143143
var label_buffer: [max_label_len + 3:0]u8 = undefined;
144144
const buf = std.fmt.bufPrintZ(&label_buffer, "0:{s}", .{label}) catch @panic("buffer too small");
145145

146-
_ = fatfs.api.setlabel(buf.ptr);
146+
if (fatfs.api.setlabel(buf.ptr) != 0) {
147+
return error.IoError;
148+
}
147149
} else {
148150
std.log.err("label \"{}\" is {} characters long, but only up to {} are permitted.", .{
149151
std.zig.fmtEscapes(label),
@@ -186,7 +188,7 @@ const FatType = enum {
186188
};
187189

188190
const AtomicOps = struct {
189-
pub fn mkdir(ops: AtomicOps, path: []const u8) !void {
191+
pub fn mkdir(ops: AtomicOps, path: []const u8) dim.Content.RenderError!void {
190192
_ = ops;
191193

192194
var path_buffer: [max_path_len:0]u8 = undefined;
@@ -195,11 +197,22 @@ const AtomicOps = struct {
195197
const joined = try std.mem.concatWithSentinel(fba.allocator(), u8, &.{ "0:/", path }, 0);
196198
fatfs.mkdir(joined) catch |err| switch (err) {
197199
error.Exist => {}, // this is good
198-
else => |e| return e,
200+
error.OutOfMemory => return error.OutOfMemory,
201+
error.Timeout => @panic("implementation bug in fatfs glue"),
202+
error.InvalidName => return error.ConfigurationError,
203+
error.WriteProtected => @panic("implementation bug in fatfs glue"),
204+
error.DiskErr => return error.IoError,
205+
error.NotReady => @panic("implementation bug in fatfs glue"),
206+
error.InvalidDrive => @panic("implementation bug in fatfs glue"),
207+
error.NotEnabled => @panic("implementation bug in fatfs glue"),
208+
error.NoFilesystem => @panic("implementation bug in fatfs glue"),
209+
error.IntErr => return error.IoError,
210+
error.NoPath => @panic("implementation bug in fatfs glue"),
211+
error.Denied => @panic("implementation bug in fatfs glue"),
199212
};
200213
}
201214

202-
pub fn mkfile(ops: AtomicOps, path: []const u8, host_file: std.fs.File) !void {
215+
pub fn mkfile(ops: AtomicOps, path: []const u8, reader: anytype) dim.Content.RenderError!void {
203216
_ = ops;
204217

205218
var path_buffer: [max_path_len:0]u8 = undefined;
@@ -210,20 +223,40 @@ const AtomicOps = struct {
210223

211224
const path_z = path_buffer[0..path.len :0];
212225

213-
const stat = try host_file.stat();
214-
215-
const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig;
216-
217-
_ = size;
218-
219-
var fs_file = try fatfs.File.create(path_z);
226+
var fs_file = fatfs.File.create(path_z) catch |err| switch (err) {
227+
error.OutOfMemory => return error.OutOfMemory,
228+
error.Timeout => @panic("implementation bug in fatfs glue"),
229+
error.InvalidName => return error.ConfigurationError,
230+
error.WriteProtected => @panic("implementation bug in fatfs glue"),
231+
error.DiskErr => return error.IoError,
232+
error.NotReady => @panic("implementation bug in fatfs glue"),
233+
error.InvalidDrive => @panic("implementation bug in fatfs glue"),
234+
error.NotEnabled => @panic("implementation bug in fatfs glue"),
235+
error.NoFilesystem => @panic("implementation bug in fatfs glue"),
236+
error.IntErr => return error.IoError,
237+
error.NoFile => @panic("implementation bug in fatfs glue"),
238+
error.NoPath => @panic("implementation bug in fatfs glue"),
239+
error.Denied => @panic("implementation bug in fatfs glue"),
240+
error.Exist => @panic("implementation bug in fatfs glue"),
241+
error.InvalidObject => @panic("implementation bug in fatfs glue"),
242+
error.Locked => @panic("implementation bug in fatfs glue"),
243+
error.TooManyOpenFiles => @panic("implementation bug in fatfs glue"),
244+
};
220245
defer fs_file.close();
221246

222247
var fifo: std.fifo.LinearFifo(u8, .{ .Static = 8192 }) = .init();
223-
try fifo.pump(
224-
host_file.reader(),
248+
fifo.pump(
249+
reader,
225250
fs_file.writer(),
226-
);
251+
) catch |err| switch (@as(dim.FileHandle.ReadError || fatfs.File.ReadError.Error, err)) {
252+
error.Overflow => return error.IoError,
253+
error.ReadFileFailed => return error.IoError,
254+
error.Timeout => @panic("implementation bug in fatfs glue"),
255+
error.DiskErr => return error.IoError,
256+
error.IntErr => return error.IoError,
257+
error.Denied => @panic("implementation bug in fatfs glue"),
258+
error.InvalidObject => @panic("implementation bug in fatfs glue"),
259+
};
227260
}
228261
};
229262

src/components/fs/common.zig

+175-22
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,181 @@ const dim = @import("../../dim.zig");
77

88
pub const FsOperation = union(enum) {
99
copy_file: struct {
10-
path: []const u8,
10+
path: [:0]const u8,
1111
source: dim.FileName,
1212
},
1313

1414
copy_dir: struct {
15-
path: []const u8,
15+
path: [:0]const u8,
1616
source: dim.FileName,
1717
},
1818

1919
make_dir: struct {
20-
path: []const u8,
20+
path: [:0]const u8,
2121
},
2222

2323
create_file: struct {
24-
path: []const u8,
24+
path: [:0]const u8,
2525
size: u64,
2626
contents: dim.Content,
2727
},
2828

2929
pub fn execute(op: FsOperation, executor: anytype) !void {
30-
_ = executor;
31-
switch (op) {
32-
.copy_file => |data| {
33-
_ = data;
34-
},
35-
.copy_dir => |data| {
36-
_ = data;
37-
},
38-
.make_dir => |data| {
39-
_ = data;
40-
},
41-
.create_file => |data| {
42-
_ = data;
43-
},
44-
}
30+
const exec: Executor(@TypeOf(executor)) = .init(executor);
31+
32+
try exec.execute(op);
4533
}
4634
};
4735

48-
fn parse_path(ctx: dim.Context) ![]const u8 {
36+
fn Executor(comptime T: type) type {
37+
return struct {
38+
const Exec = @This();
39+
40+
inner: T,
41+
42+
fn init(wrapped: T) Exec {
43+
return .{ .inner = wrapped };
44+
}
45+
46+
fn execute(exec: Exec, op: FsOperation) dim.Content.RenderError!void {
47+
switch (op) {
48+
.make_dir => |data| {
49+
try exec.recursive_mkdir(data.path);
50+
},
51+
52+
.copy_file => |data| {
53+
var handle = data.source.open() catch |err| switch (err) {
54+
error.FileNotFound => return, // open() already reporeted the error
55+
else => |e| return e,
56+
};
57+
defer handle.close();
58+
59+
try exec.add_file(data.path, handle.reader());
60+
},
61+
.copy_dir => |data| {
62+
var iter_dir = data.source.open_dir() catch |err| switch (err) {
63+
error.FileNotFound => return, // open() already reporeted the error
64+
else => |e| return e,
65+
};
66+
defer iter_dir.close();
67+
68+
var walker_memory: [16384]u8 = undefined;
69+
var temp_allocator: std.heap.FixedBufferAllocator = .init(&walker_memory);
70+
71+
var path_memory: [8192]u8 = undefined;
72+
73+
var walker = try iter_dir.walk(temp_allocator.allocator());
74+
defer walker.deinit();
75+
76+
while (walker.next() catch |err| return walk_err(err)) |entry| {
77+
const path = std.fmt.bufPrintZ(&path_memory, "{s}/{s}", .{
78+
data.path,
79+
entry.path,
80+
}) catch @panic("buffer too small!");
81+
82+
// std.log.debug("- {s}", .{path_buffer.items});
83+
84+
switch (entry.kind) {
85+
.file => {
86+
const fname: dim.FileName = .{
87+
.root_dir = entry.dir,
88+
.rel_path = entry.basename,
89+
};
90+
91+
var file = try fname.open();
92+
defer file.close();
93+
94+
try exec.add_file(path, file.reader());
95+
},
96+
97+
.directory => {
98+
try exec.recursive_mkdir(path);
99+
},
100+
101+
else => {
102+
var realpath_buffer: [std.fs.max_path_bytes]u8 = undefined;
103+
std.log.warn("cannot copy file {!s}: {s} is not a supported file type!", .{
104+
entry.dir.realpath(entry.path, &realpath_buffer),
105+
@tagName(entry.kind),
106+
});
107+
},
108+
}
109+
}
110+
},
111+
112+
.create_file => |data| {
113+
const buffer = try std.heap.page_allocator.alloc(u8, data.size);
114+
defer std.heap.page_allocator.free(buffer);
115+
116+
var bs: dim.BinaryStream = .init_buffer(buffer);
117+
118+
try data.contents.render(&bs);
119+
120+
var fbs: std.io.FixedBufferStream([]u8) = .{ .buffer = buffer, .pos = 0 };
121+
122+
try exec.add_file(data.path, fbs.reader());
123+
},
124+
}
125+
}
126+
127+
fn add_file(exec: Exec, path: [:0]const u8, reader: anytype) !void {
128+
if (std.fs.path.dirnamePosix(path)) |dir| {
129+
try exec.recursive_mkdir(dir);
130+
}
131+
132+
try exec.inner_mkfile(path, reader);
133+
}
134+
135+
fn recursive_mkdir(exec: Exec, path: []const u8) !void {
136+
var i: usize = 0;
137+
138+
while (std.mem.indexOfScalarPos(u8, path, i, '/')) |index| {
139+
try exec.inner_mkdir(path[0..index]);
140+
i = index + 1;
141+
}
142+
143+
try exec.inner_mkdir(path);
144+
}
145+
146+
fn inner_mkfile(exec: Exec, path: []const u8, reader: anytype) dim.Content.RenderError!void {
147+
try exec.inner.mkfile(path, reader);
148+
}
149+
150+
fn inner_mkdir(exec: Exec, path: []const u8) dim.Content.RenderError!void {
151+
try exec.inner.mkdir(path);
152+
}
153+
154+
fn walk_err(err: (std.fs.Dir.OpenError || std.mem.Allocator.Error)) dim.Content.RenderError {
155+
return switch (err) {
156+
error.InvalidUtf8 => error.InvalidPath,
157+
error.InvalidWtf8 => error.InvalidPath,
158+
error.BadPathName => error.InvalidPath,
159+
error.NameTooLong => error.InvalidPath,
160+
161+
error.OutOfMemory => error.OutOfMemory,
162+
error.FileNotFound => error.FileNotFound,
163+
164+
error.DeviceBusy => error.IoError,
165+
error.AccessDenied => error.IoError,
166+
error.SystemResources => error.IoError,
167+
error.NoDevice => error.IoError,
168+
error.Unexpected => error.IoError,
169+
error.NetworkNotFound => error.IoError,
170+
error.SymLinkLoop => error.IoError,
171+
error.ProcessFdQuotaExceeded => error.IoError,
172+
error.SystemFdQuotaExceeded => error.IoError,
173+
error.NotDir => error.IoError,
174+
};
175+
}
176+
};
177+
}
178+
179+
fn parse_path(ctx: dim.Context) ![:0]const u8 {
49180
const path = try ctx.parse_string();
50181

51182
if (path.len == 0) {
52183
try ctx.report_nonfatal_error("Path cannot be empty!", .{});
53-
return path;
184+
return "";
54185
}
55186

56187
if (!std.mem.startsWith(u8, path, "/")) {
@@ -75,7 +206,7 @@ fn parse_path(ctx: dim.Context) ![]const u8 {
75206
});
76207
};
77208

78-
return path;
209+
return try normalize(ctx.get_arena(), path);
79210
}
80211

81212
pub fn parse_ops(ctx: dim.Context, end_seq: []const u8, handler: anytype) !void {
@@ -116,3 +247,25 @@ pub fn parse_ops(ctx: dim.Context, end_seq: []const u8, handler: anytype) !void
116247
}
117248
}
118249
}
250+
251+
fn normalize(allocator: std.mem.Allocator, src_path: []const u8) ![:0]const u8 {
252+
var list = std.ArrayList([]const u8).init(allocator);
253+
defer list.deinit();
254+
255+
var parts = std.mem.tokenizeAny(u8, src_path, "\\/");
256+
257+
while (parts.next()) |part| {
258+
if (std.mem.eql(u8, part, ".")) {
259+
// "cd same" is a no-op, we can remove it
260+
continue;
261+
} else if (std.mem.eql(u8, part, "..")) {
262+
// "cd up" is basically just removing the last pushed part
263+
_ = list.pop();
264+
} else {
265+
// this is an actual "descend"
266+
try list.append(part);
267+
}
268+
}
269+
270+
return try std.mem.joinZ(allocator, "/", list.items);
271+
}

src/dim.zig

+39-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,14 @@ pub const FileName = struct {
398398

399399
pub fn open(name: FileName) OpenError!FileHandle {
400400
const file = name.root_dir.openFile(name.rel_path, .{}) catch |err| switch (err) {
401-
error.FileNotFound => return error.FileNotFound,
401+
error.FileNotFound => {
402+
var buffer: [std.fs.max_path_bytes]u8 = undefined;
403+
std.log.err("failed to open \"{}/{}\": not found", .{
404+
std.zig.fmtEscapes(name.root_dir.realpath(".", &buffer) catch |e| @errorName(e)),
405+
std.zig.fmtEscapes(name.rel_path),
406+
});
407+
return error.FileNotFound;
408+
},
402409

403410
error.NameTooLong,
404411
error.InvalidWtf8,
@@ -431,6 +438,37 @@ pub const FileName = struct {
431438
return .{ .file = file };
432439
}
433440

441+
pub fn open_dir(name: FileName) OpenError!std.fs.Dir {
442+
return name.root_dir.openDir(name.rel_path, .{ .iterate = true }) catch |err| switch (err) {
443+
error.FileNotFound => {
444+
var buffer: [std.fs.max_path_bytes]u8 = undefined;
445+
std.log.err("failed to open \"{}/{}\": not found", .{
446+
std.zig.fmtEscapes(name.root_dir.realpath(".", &buffer) catch |e| @errorName(e)),
447+
std.zig.fmtEscapes(name.rel_path),
448+
});
449+
return error.FileNotFound;
450+
},
451+
452+
error.NameTooLong,
453+
error.InvalidWtf8,
454+
error.BadPathName,
455+
error.InvalidUtf8,
456+
=> return error.InvalidPath,
457+
458+
error.DeviceBusy,
459+
error.AccessDenied,
460+
error.SystemResources,
461+
error.NoDevice,
462+
error.Unexpected,
463+
error.NetworkNotFound,
464+
error.SymLinkLoop,
465+
error.ProcessFdQuotaExceeded,
466+
error.SystemFdQuotaExceeded,
467+
error.NotDir,
468+
=> return error.IoError,
469+
};
470+
}
471+
434472
pub const GetSizeError = error{ FileNotFound, InvalidPath, IoError };
435473
pub fn get_size(name: FileName) GetSizeError!u64 {
436474
const stat = name.root_dir.statFile(name.rel_path) catch |err| switch (err) {

0 commit comments

Comments
 (0)