diff --git a/lib/std/c.zig b/lib/std/c.zig index 53cd75f789d1..184b5ad7de3f 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -10536,6 +10536,8 @@ pub extern "c" fn recvmsg(sockfd: fd_t, msg: *msghdr, flags: u32) isize; pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int; +pub extern "c" fn getuid() uid_t; + pub extern "c" fn setuid(uid: uid_t) c_int; pub extern "c" fn setgid(gid: gid_t) c_int; pub extern "c" fn seteuid(euid: uid_t) c_int; diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index a256758a07e2..d6201c45e7a6 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -24,10 +24,7 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi return fs.path.join(allocator, &[_][]const u8{ local_app_data_dir, appname }); }, .macos => { - const home_dir = posix.getenv("HOME") orelse { - // TODO look in /etc/passwd - return error.AppDataDirUnavailable; - }; + const home_dir = posix.getenv("HOME") orelse (getHomeDirFromPasswd() catch return error.AppDataDirUnavailable); return fs.path.join(allocator, &[_][]const u8{ home_dir, "Library", "Application Support", appname }); }, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos, .serenity => { @@ -37,10 +34,7 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi } } - const home_dir = posix.getenv("HOME") orelse { - // TODO look in /etc/passwd - return error.AppDataDirUnavailable; - }; + const home_dir = posix.getenv("HOME") orelse (getHomeDirFromPasswd() catch return error.AppDataDirUnavailable); return fs.path.join(allocator, &[_][]const u8{ home_dir, ".local", "share", appname }); }, .haiku => { @@ -57,6 +51,102 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi } } +/// Parses home directory from /etc/passwd. +fn getHomeDirFromPasswd() ![]u8 { + const ReaderState = enum { + start, + wait_for_next_line, + skip_username, + skip_password, + read_user_id, + skip_group_id, + skip_gecos, + read_home_directory, + }; + + const file = try fs.openFileAbsolute("/etc/passwd", .{}); + defer file.close(); + + const reader = file.reader(); + + var buf: [std.heap.page_size_min]u8 = undefined; + var state = ReaderState.start; + const currentUid = std.posix.getuid(); + var uid: posix.uid_t = 0; + var home_dir_start: usize = 0; + var home_dir_len: usize = 0; + + while (true) { + const bytes_read = try reader.read(buf[0..]); + for (0.., buf[0..bytes_read]) |i, byte| { + switch (state) { + .start => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => state = .skip_password, + else => continue, + }, + .wait_for_next_line => switch (byte) { + '\n' => { + uid = 0; + home_dir_len = 0; + state = .start; + }, + else => continue, + }, + .skip_username => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => std.debug.print("*", .{}), + else => continue, + }, + .skip_password => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => state = .read_user_id, + else => continue, + }, + .read_user_id => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => state = if (uid == currentUid) .skip_group_id else .wait_for_next_line, + else => { + const digit = switch (byte) { + '0'...'9' => byte - '0', + else => return error.CorruptPasswordFile, + }; + { + const ov = @mulWithOverflow(uid, 10); + if (ov[1] != 0) return error.CorruptPasswordFile; + uid = ov[0]; + } + { + const ov = @addWithOverflow(uid, digit); + if (ov[1] != 0) return error.CorruptPasswordFile; + uid = ov[0]; + } + }, + }, + .skip_group_id => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => state = .skip_gecos, + else => continue, + }, + .skip_gecos => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => { + home_dir_start = i + 1; + state = .read_home_directory; + }, + else => continue, + }, + .read_home_directory => switch (byte) { + '\n', ':' => return buf[home_dir_start .. home_dir_start + home_dir_len], + else => { + home_dir_len += 1; + }, + }, + } + } + } +} + test getAppDataDir { if (native_os == .wasi) return error.SkipZigTest; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index f920784ca542..ed336fd543cb 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -3407,6 +3407,12 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read } } +/// POSIX IEEE Std 1003.1-2024: https://pubs.opengroup.org/onlinepubs/9799919799/ +/// Conforming environments will always be successful and never modify errno. +pub fn getuid() uid_t { + return system.getuid(); +} + pub const SetEidError = error{ InvalidUserId, PermissionDenied,