Commit fd2d502e41

Andrew Kelley <superjoe30@gmail.com>
2017-09-26 08:42:06
std.os.ChildProcess: ability to set both uid and gid
1 parent cba4a9a
std/os/child_process.zig
@@ -40,6 +40,9 @@ pub const ChildProcess = struct {
     /// Set to change the user id when spawning the child process.
     pub uid: ?u32,
 
+    /// Set to change the group id when spawning the child process.
+    pub gid: ?u32,
+
     /// Set to change the current working directory when spawning the child process.
     pub cwd: ?[]const u8,
 
@@ -77,6 +80,7 @@ pub const ChildProcess = struct {
             .env_map = null,
             .cwd = null,
             .uid = null,
+            .gid = null,
             .stdin = null,
             .stdout = null,
             .stderr = null,
@@ -89,7 +93,9 @@ pub const ChildProcess = struct {
     }
 
     pub fn setUserName(self: &ChildProcess, name: []const u8) -> %void {
-        self.uid = %return os.getUserId(name);
+        const user_info = %return os.getUserInfo(name);
+        self.uid = user_info.uid;
+        self.gid = user_info.gid;
     }
 
     /// onTerm can be called before `spawn` returns.
@@ -294,7 +300,11 @@ pub const ChildProcess = struct {
             }
 
             if (self.uid) |uid| {
-                os.posix_setuid(uid) %% |err| forkChildErrReport(err_pipe[1], err);
+                os.posix_setreuid(uid, uid) %% |err| forkChildErrReport(err_pipe[1], err);
+            }
+
+            if (self.gid) |gid| {
+                os.posix_setregid(gid, gid) %% |err| forkChildErrReport(err_pipe[1], err);
             }
 
             os.posixExecve(self.argv, env_map, self.allocator) %%
std/os/get_user_id.zig
@@ -3,10 +3,15 @@ const Os = builtin.Os;
 const os = @import("index.zig");
 const io = @import("../io.zig");
 
+pub const UserInfo = struct {
+    uid: u32,
+    gid: u32,
+};
+
 /// POSIX function which gets a uid from username.
-pub fn getUserId(name: []const u8) -> %u32 {
+pub fn getUserInfo(name: []const u8) -> %UserInfo {
     return switch (builtin.os) {
-        Os.linux, Os.darwin, Os.macosx, Os.ios => posixGetUserId(name),
+        Os.linux, Os.darwin, Os.macosx, Os.ios => posixGetUserInfo(name),
         else => @compileError("Unsupported OS"),
     };
 }
@@ -15,13 +20,17 @@ const State = enum {
     Start,
     WaitForNextLine,
     SkipPassword,
-    ReadId,
+    ReadUserId,
+    ReadGroupId,
 };
 
 error UserNotFound;
 error CorruptPasswordFile;
 
-pub fn posixGetUserId(name: []const u8) -> %u32 {
+// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
+// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
+
+pub fn posixGetUserInfo(name: []const u8) -> %UserInfo {
     var in_stream = %return io.InStream.open("/etc/passwd", null);
     defer in_stream.close();
 
@@ -29,6 +38,7 @@ pub fn posixGetUserId(name: []const u8) -> %u32 {
     var name_index: usize = 0;
     var state = State.Start;
     var uid: u32 = 0;
+    var gid: u32 = 0;
 
     while (true) {
         const amt_read = %return in_stream.read(buf[0..]);
@@ -56,12 +66,15 @@ pub fn posixGetUserId(name: []const u8) -> %u32 {
                 State.SkipPassword => switch (byte) {
                     '\n' => return error.CorruptPasswordFile,
                     ':' => {
-                        state = State.ReadId;
+                        state = State.ReadUserId;
                     },
                     else => continue,
                 },
-                State.ReadId => switch (byte) {
-                    '\n', ':' => return uid,
+                State.ReadUserId => switch (byte) {
+                    ':' => {
+                        state = State.ReadGroupId;
+                    },
+                    '\n' => return error.CorruptPasswordFile,
                     else => {
                         const digit = switch (byte) {
                             '0' ... '9' => byte - '0',
@@ -71,6 +84,22 @@ pub fn posixGetUserId(name: []const u8) -> %u32 {
                         if (@addWithOverflow(u32, uid, digit, &uid)) return error.CorruptPasswordFile;
                     },
                 },
+                State.ReadGroupId => switch (byte) {
+                    '\n', ':' => {
+                        return UserInfo {
+                            .uid = uid,
+                            .gid = gid,
+                        };
+                    },
+                    else => {
+                        const digit = switch (byte) {
+                            '0' ... '9' => byte - '0',
+                            else => return error.CorruptPasswordFile,
+                        };
+                        if (@mulWithOverflow(u32, gid, 10, &gid)) return error.CorruptPasswordFile;
+                        if (@addWithOverflow(u32, gid, digit, &gid)) return error.CorruptPasswordFile;
+                    },
+                },
             }
         }
         if (amt_read < buf.len) return error.UserNotFound;
std/os/index.zig
@@ -20,7 +20,8 @@ pub const line_sep = switch (builtin.os) {
 
 pub const page_size = 4 * 1024;
 
-pub const getUserId = @import("get_user_id.zig").getUserId;
+pub const UserInfo = @import("get_user_id.zig").UserInfo;
+pub const getUserInfo = @import("get_user_id.zig").getUserInfo;
 
 const debug = @import("../debug.zig");
 const assert = debug.assert;
@@ -999,3 +1000,36 @@ pub fn posix_setuid(uid: u32) -> %void {
         else => error.Unexpected,
     };
 }
+
+pub fn posix_setreuid(ruid: u32, euid: u32) -> %void {
+    const err = posix.getErrno(posix.setreuid(ruid, euid));
+    if (err == 0) return;
+    return switch (err) {
+        posix.EAGAIN => error.ResourceLimitReached,
+        posix.EINVAL => error.InvalidUserId,
+        posix.EPERM => error.PermissionDenied,
+        else => error.Unexpected,
+    };
+}
+
+pub fn posix_setgid(gid: u32) -> %void {
+    const err = posix.getErrno(posix.setgid(gid));
+    if (err == 0) return;
+    return switch (err) {
+        posix.EAGAIN => error.ResourceLimitReached,
+        posix.EINVAL => error.InvalidUserId,
+        posix.EPERM => error.PermissionDenied,
+        else => error.Unexpected,
+    };
+}
+
+pub fn posix_setregid(rgid: u32, egid: u32) -> %void {
+    const err = posix.getErrno(posix.setregid(rgid, egid));
+    if (err == 0) return;
+    return switch (err) {
+        posix.EAGAIN => error.ResourceLimitReached,
+        posix.EINVAL => error.InvalidUserId,
+        posix.EPERM => error.PermissionDenied,
+        else => error.Unexpected,
+    };
+}
std/os/linux.zig
@@ -484,6 +484,18 @@ pub fn setuid(uid: u32) -> usize {
     arch.syscall1(arch.SYS_setuid, uid)
 }
 
+pub fn setgid(gid: u32) -> usize {
+    arch.syscall1(arch.SYS_setgid, gid)
+}
+
+pub fn setreuid(ruid: u32, euid: u32) -> usize {
+    arch.syscall2(arch.SYS_setreuid, ruid, euid)
+}
+
+pub fn setregid(rgid: u32, egid: u32) -> usize {
+    arch.syscall2(arch.SYS_setregid, rgid, egid)
+}
+
 pub fn sigprocmask(flags: u32, noalias set: &const sigset_t, noalias oldset: ?&sigset_t) -> usize {
     arch.syscall4(arch.SYS_rt_sigprocmask, flags, @ptrToInt(set), @ptrToInt(oldset), NSIG/8)
 }