Skip to content

Commit aa24c5d

Browse files
committed
system: introduce system abstraction
This is basically a slighly stripped back re-implementation of https://github.com/leecannon/zsw/
1 parent b5e5bdc commit aa24c5d

21 files changed

+1423
-184
lines changed

src/Command.zig

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ execute: *const fn (
2323
allocator: std.mem.Allocator,
2424
io: IO,
2525
args: *Arg.Iterator,
26-
cwd: std.fs.Dir,
26+
system: System,
2727
exe_path: []const u8,
2828
) Error!void,
2929

@@ -190,7 +190,7 @@ pub const TestExecuteSettings = struct {
190190
stdin: ?std.io.AnyReader = null,
191191
stdout: ?std.io.AnyWriter = null,
192192
stderr: ?std.io.AnyWriter = null,
193-
cwd: ?std.fs.Dir = null,
193+
system_description: System.TestBackend.Description = .{},
194194
};
195195

196196
pub fn testExecute(
@@ -200,11 +200,15 @@ pub fn testExecute(
200200
) ExposedError!void {
201201
std.debug.assert(builtin.is_test);
202202

203-
const cwd_provided = settings.cwd != null;
204-
205-
var tmp_dir: std.testing.TmpDir = if (!cwd_provided) std.testing.tmpDir(.{}) else undefined;
206-
defer if (!cwd_provided) tmp_dir.cleanup();
207-
const cwd = if (settings.cwd) |c| c else tmp_dir.dir;
203+
const system: System = .{
204+
._backend = System.TestBackend.create(
205+
std.testing.allocator,
206+
settings.system_description,
207+
) catch |err| {
208+
std.debug.panic("unable to create system backend: {s}", .{@errorName(err)});
209+
},
210+
};
211+
defer system._backend.destroy();
208212

209213
var arg_iter: Arg.Iterator = .{ .slice = .{ .slice = arguments } };
210214

@@ -218,7 +222,7 @@ pub fn testExecute(
218222
std.testing.allocator,
219223
io,
220224
&arg_iter,
221-
cwd,
225+
system,
222226
command.name,
223227
) catch |full_err| command.narrowError(io, command.name, full_err);
224228
}
@@ -356,6 +360,8 @@ pub const TestFuzzOptions = struct {
356360

357361
/// If true the command is expected to output something to stderr on failure.
358362
expect_stderr_output_on_failure: bool = true,
363+
364+
system_description: System.TestBackend.Description = .{},
359365
};
360366

361367
pub fn testFuzz(command: Command, options: TestFuzzOptions) !void {
@@ -388,7 +394,11 @@ pub fn testFuzz(command: Command, options: TestFuzzOptions) !void {
388394

389395
context.inner_command.testExecute(
390396
arguments,
391-
.{ .stdout = stdout.writer().any(), .stderr = stderr.writer().any() },
397+
.{
398+
.stdout = stdout.writer().any(),
399+
.stderr = stderr.writer().any(),
400+
.system_description = context.options.system_description,
401+
},
392402
) catch |err| {
393403
switch (err) {
394404
error.OutOfMemory => {
@@ -491,6 +501,7 @@ pub const enabled_command_lookup: std.StaticStringMap(Command) = .initComptime(b
491501
const Arg = @import("Arg.zig");
492502
const IO = @import("IO.zig");
493503
const shared = @import("shared.zig");
504+
const System = @import("system/System.zig");
494505

495506
const builtin = @import("builtin");
496507
const std = @import("std");

src/commands/basename.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ const impl = struct {
4242
allocator: std.mem.Allocator,
4343
io: IO,
4444
args: *Arg.Iterator,
45-
cwd: std.fs.Dir,
45+
system: System,
4646
exe_path: []const u8,
4747
) Command.Error!void {
4848
const z: tracy.Zone = .begin(.{ .src = @src(), .name = command.name });
4949
defer z.end();
5050

51-
_ = cwd;
51+
_ = system;
5252

5353
const options = try parseArguments(allocator, io, args, exe_path);
5454
log.debug("{}", .{options});
@@ -387,6 +387,7 @@ const Arg = @import("../Arg.zig");
387387
const Command = @import("../Command.zig");
388388
const IO = @import("../IO.zig");
389389
const shared = @import("../shared.zig");
390+
const System = @import("../system/System.zig");
390391

391392
const log = std.log.scoped(.basename);
392393

src/commands/clear.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ const impl = struct {
3535
allocator: std.mem.Allocator,
3636
io: IO,
3737
args: *Arg.Iterator,
38-
cwd: std.fs.Dir,
38+
system: System,
3939
exe_path: []const u8,
4040
) Command.Error!void {
4141
const z: tracy.Zone = .begin(.{ .src = @src(), .name = command.name });
4242
defer z.end();
4343

44-
_ = cwd;
44+
_ = system;
4545

4646
const options = try parseArguments(allocator, io, args, exe_path);
4747
log.debug("{}", .{options});
@@ -194,6 +194,7 @@ const Arg = @import("../Arg.zig");
194194
const Command = @import("../Command.zig");
195195
const IO = @import("../IO.zig");
196196
const shared = @import("../shared.zig");
197+
const System = @import("../system/System.zig");
197198

198199
const log = std.log.scoped(.clear);
199200

src/commands/dirname.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ const impl = struct {
3737
allocator: std.mem.Allocator,
3838
io: IO,
3939
args: *Arg.Iterator,
40-
cwd: std.fs.Dir,
40+
system: System,
4141
exe_path: []const u8,
4242
) Command.Error!void {
4343
const z: tracy.Zone = .begin(.{ .src = @src(), .name = command.name });
4444
defer z.end();
4545

46-
_ = cwd;
46+
_ = system;
4747

4848
const options = try parseArguments(allocator, io, args, exe_path);
4949
log.debug("{}", .{options});
@@ -251,6 +251,7 @@ const Arg = @import("../Arg.zig");
251251
const Command = @import("../Command.zig");
252252
const IO = @import("../IO.zig");
253253
const shared = @import("../shared.zig");
254+
const System = @import("../system/System.zig");
254255

255256
const log = std.log.scoped(.dirname);
256257

src/commands/false.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ const impl = struct {
2828
allocator: std.mem.Allocator,
2929
io: IO,
3030
args: *Arg.Iterator,
31-
cwd: std.fs.Dir,
31+
system: System,
3232
exe_path: []const u8,
3333
) Command.Error!void {
3434
const z: tracy.Zone = .begin(.{ .src = @src(), .name = command.name });
3535
defer z.end();
3636

3737
_ = io;
3838
_ = exe_path;
39-
_ = cwd;
39+
_ = system;
4040
_ = allocator;
4141

4242
_ = try args.nextWithHelpOrVersion(true);
@@ -84,6 +84,7 @@ const Arg = @import("../Arg.zig");
8484
const Command = @import("../Command.zig");
8585
const IO = @import("../IO.zig");
8686
const shared = @import("../shared.zig");
87+
const System = @import("../system/System.zig");
8788

8889
const std = @import("std");
8990
const tracy = @import("tracy");

src/commands/groups.zig

Lines changed: 107 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const impl = struct {
3636
allocator: std.mem.Allocator,
3737
io: IO,
3838
args: *Arg.Iterator,
39-
cwd: std.fs.Dir,
39+
system: System,
4040
exe_path: []const u8,
4141
) Command.Error!void {
4242
const z: tracy.Zone = .begin(.{ .src = @src(), .name = command.name });
@@ -46,25 +46,50 @@ const impl = struct {
4646

4747
const opt_arg = try args.nextWithHelpOrVersion(true);
4848

49-
const passwd_file = try shared.mapFile(command, allocator, io, cwd, "/etc/passwd");
50-
defer passwd_file.close();
49+
const mapped_passwd_file = blk: {
50+
const passwd_file = system.cwd().openFile("/etc/passwd", .{}) catch |err|
51+
return command.printErrorAlloc(
52+
allocator,
53+
io,
54+
"unable to open '/etc/passwd': {s}",
55+
.{@errorName(err)},
56+
);
57+
errdefer if (shared.free_on_close) passwd_file.close();
58+
59+
const stat = passwd_file.stat() catch |err|
60+
return command.printErrorAlloc(
61+
allocator,
62+
io,
63+
"unable to stat '/etc/passwd': {s}",
64+
.{@errorName(err)},
65+
);
66+
67+
break :blk passwd_file.mapReadonly(stat.size) catch |err|
68+
return command.printErrorAlloc(
69+
allocator,
70+
io,
71+
"unable to map '/etc/passwd': {s}",
72+
.{@errorName(err)},
73+
);
74+
};
75+
defer if (shared.free_on_close) mapped_passwd_file.close();
5176

5277
return if (opt_arg) |arg|
53-
namedUser(allocator, io, arg.raw, passwd_file.file_contents, cwd)
78+
namedUser(allocator, io, arg.raw, mapped_passwd_file.file_contents, system)
5479
else
55-
currentUser(allocator, io, passwd_file.file_contents, cwd);
80+
currentUser(allocator, io, mapped_passwd_file.file_contents, system);
5681
}
5782

5883
fn currentUser(
5984
allocator: std.mem.Allocator,
6085
io: IO,
6186
passwd_file_contents: []const u8,
62-
cwd: std.fs.Dir,
87+
system: System,
6388
) Command.Error!void {
6489
const z: tracy.Zone = .begin(.{ .src = @src(), .name = "current user" });
6590
defer z.end();
6691

67-
const euid = std.os.linux.geteuid();
92+
const euid = system.getEffectiveUserId();
6893

6994
log.debug("currentUser called, euid: {}", .{euid});
7095

@@ -98,7 +123,7 @@ const impl = struct {
98123
"format of '/etc/passwd' is invalid",
99124
);
100125

101-
return printGroups(allocator, entry.user_name, primary_group_id, io, cwd);
126+
return printGroups(allocator, entry.user_name, primary_group_id, io, system);
102127
}
103128

104129
return command.printError(
@@ -112,7 +137,7 @@ const impl = struct {
112137
io: IO,
113138
user: []const u8,
114139
passwd_file_contents: []const u8,
115-
cwd: std.fs.Dir,
140+
system: System,
116141
) Command.Error!void {
117142
const z: tracy.Zone = .begin(.{ .src = @src(), .name = "namedUser" });
118143
defer z.end();
@@ -140,7 +165,7 @@ const impl = struct {
140165
"format of '/etc/passwd' is invalid",
141166
);
142167

143-
return printGroups(allocator, entry.user_name, primary_group_id, io, cwd);
168+
return printGroups(allocator, entry.user_name, primary_group_id, io, system);
144169
}
145170

146171
return command.printErrorAlloc(allocator, io, "unknown user '{s}'", .{user});
@@ -151,7 +176,7 @@ const impl = struct {
151176
user: []const u8,
152177
primary_group_id: std.posix.uid_t,
153178
io: IO,
154-
cwd: std.fs.Dir,
179+
system: System,
155180
) !void {
156181
const z: tracy.Zone = .begin(.{ .src = @src(), .name = "print groups" });
157182
defer z.end();
@@ -162,10 +187,35 @@ const impl = struct {
162187
.{ user, primary_group_id },
163188
);
164189

165-
const group_file = try shared.mapFile(command, allocator, io, cwd, "/etc/group");
166-
defer group_file.close();
190+
const mapped_group_file = blk: {
191+
const group_file = system.cwd().openFile("/etc/group", .{}) catch |err|
192+
return command.printErrorAlloc(
193+
allocator,
194+
io,
195+
"unable to open '/etc/group': {s}",
196+
.{@errorName(err)},
197+
);
198+
errdefer if (shared.free_on_close) group_file.close();
199+
200+
const stat = group_file.stat() catch |err|
201+
return command.printErrorAlloc(
202+
allocator,
203+
io,
204+
"unable to stat '/etc/group': {s}",
205+
.{@errorName(err)},
206+
);
167207

168-
var group_file_iter = shared.groupFileIterator(group_file.file_contents);
208+
break :blk group_file.mapReadonly(stat.size) catch |err|
209+
return command.printErrorAlloc(
210+
allocator,
211+
io,
212+
"unable to map '/etc/group': {s}",
213+
.{@errorName(err)},
214+
);
215+
};
216+
defer if (shared.free_on_close) mapped_group_file.close();
217+
218+
var group_file_iter = shared.groupFileIterator(mapped_group_file.file_contents);
169219

170220
var first = true;
171221

@@ -211,14 +261,55 @@ const impl = struct {
211261
try command.testVersion();
212262
}
213263

214-
// TODO: How do we test this without introducing the amount of complexity that https://github.com/leecannon/zsw does?
215-
// https://github.com/leecannon/zig-coreutils/issues/7
264+
test "groups" {
265+
const passwd_contents =
266+
\\root:x:0:0::/root:/usr/bin/bash
267+
\\daemon:x:1:1::/:/usr/sbin/nologin
268+
\\bin:x:2:2::/:/usr/sbin/nologin
269+
\\sys:x:3:3::/:/usr/sbin/nologin
270+
\\user:x:1001:1001:A User:/home/user:/usr/bin/zsh
271+
\\
272+
;
273+
274+
const group_contents =
275+
\\root:x:0:
276+
\\daemon:x:1:
277+
\\bin:x:2:
278+
\\sys:x:3:user
279+
\\user:x:1001:
280+
\\wheel:x:10:user
281+
\\
282+
;
283+
284+
const file_system: *System.TestBackend.Description.FileSystemDescription = try .create(std.testing.allocator);
285+
defer file_system.destroy();
286+
287+
const etc_dir = try file_system.root.addDirectory("etc");
288+
_ = try etc_dir.addFile("passwd", passwd_contents);
289+
_ = try etc_dir.addFile("group", group_contents);
290+
291+
var stdout: std.ArrayList(u8) = .init(std.testing.allocator);
292+
defer stdout.deinit();
293+
294+
try command.testExecute(&.{}, .{
295+
.stdout = stdout.writer().any(),
296+
.system_description = .{
297+
.file_system = file_system,
298+
.user_group = .{
299+
.effective_user_id = 1001,
300+
},
301+
},
302+
});
303+
304+
try std.testing.expectEqualStrings("sys user wheel\n", stdout.items);
305+
}
216306
};
217307

218308
const Arg = @import("../Arg.zig");
219309
const Command = @import("../Command.zig");
220310
const IO = @import("../IO.zig");
221311
const shared = @import("../shared.zig");
312+
const System = @import("../system/System.zig");
222313

223314
const log = std.log.scoped(.groups);
224315

0 commit comments

Comments
 (0)