exec.ha (5794B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use errors; 5 use fs; 6 use io; 7 use os; 8 use rt; 9 use types::c; 10 use unix; 11 12 // Forks the current process, returning the [[process]] of the child (to the 13 // parent) and void (to the child), or an error. 14 export fn fork() (process | void | error) = { 15 match (rt::fork()) { 16 case let err: rt::errno => 17 return errors::errno(err); 18 case let i: rt::pid_t => 19 return i: process; 20 case void => 21 return void; 22 }; 23 }; 24 25 // Creates an anonymous pipe for use with [[addfile]]. Any data written to the 26 // second file may be read from the first file. The caller should close one or 27 // both of the file descriptors after they have transferred them to another 28 // process, and after they have finished using them themselves, if applicable. 29 // 30 // This function will abort the process if the system is unable to allocate the 31 // resources for a pipe. If you need to handle this error gracefully, you may 32 // call [[unix::pipe]] yourself, but this may reduce the portability of your 33 // software. 34 // 35 // To capture the standard output of a process: 36 // 37 // const (read, write) = exec::pipe(); 38 // exec::addfile(&cmd, os::stdout_file, write); 39 // let proc = exec::start(&cmd)!; 40 // io::close(write)!; 41 // 42 // let data = io::drain(read)!; 43 // io::close(read)!; 44 // exec::wait(&proc)!; 45 // 46 // To write to the standard input of a process: 47 // 48 // const (read, write) = exec::pipe(); 49 // exec::addfile(&cmd, os::stdin_file, read); 50 // let proc = exec::start(&cmd)!; 51 // io::close(read)!; 52 // 53 // io::writeall(write, data)!; 54 // io::close(write)!; 55 // exec::wait(&proc)!; 56 export fn pipe() (io::file, io::file) = { 57 return unix::pipe()!; 58 }; 59 60 fn open(path: str) (platform_cmd | error) = { 61 // O_PATH is used because it allows us to use an executable for which we 62 // have execute permissions, but not read permissions. 63 let fd = match (rt::open(path, rt::O_PATH, 0u)) { 64 case let fd: int => 65 yield fd; 66 case let err: rt::errno => 67 return errors::errno(err); 68 }; 69 let success = false; 70 defer if (!success) rt::close(fd)!; 71 match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) { 72 case let err: rt::errno => 73 // not ideal, but better to do Something on old kernels rather 74 // than just breaking entirely 75 if (err != rt::ENOSYS) { 76 return errors::errno(err); 77 }; 78 case let b: bool => 79 if (!b) { 80 return errors::noaccess; 81 }; 82 }; 83 // Make sure we are not trying to execute anything weird. fstat() 84 // already dereferences symlinks, so if this is anything other than a 85 // regular file it cannot be executed. 86 let s = rt::st { ... }; 87 match (rt::fstat(fd, &s)) { 88 case let err: rt::errno => 89 return errors::errno(err); 90 case void => 91 if (s.mode & rt::S_IFREG == 0) { 92 return errors::noaccess; 93 }; 94 }; 95 success = true; 96 return fd; 97 }; 98 99 fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; 100 101 fn platform_exec(cmd: *command) error = { 102 // We don't worry about freeing the return values from c::fromstr 103 // because once we exec(2) our heap is fried anyway 104 let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z)!; 105 for (let arg .. cmd.argv) { 106 append(argv, c::fromstr(arg))!; 107 }; 108 append(argv, null)!; 109 110 let envp: nullable *[*]nullable *const c::char = null; 111 if (len(cmd.env) != 0) { 112 let env: []nullable *const c::char = alloc([], len(cmd.env) + 1)!; 113 for (let e .. cmd.env) { 114 append(env, c::fromstr(e))!; 115 }; 116 append(env, null)!; 117 envp = env: *[*]nullable *const c::char; 118 }; 119 120 let need_devnull = false; 121 for (let file &.. cmd.files) { 122 const from = match (file.0) { 123 case let file: io::file => 124 yield file; 125 case nullfd => 126 need_devnull = true; 127 continue; 128 case closefd => 129 continue; 130 }; 131 132 file.0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { 133 case let fd: int => 134 yield fd; 135 case let err: rt::errno => 136 return errors::errno(err); 137 }; 138 }; 139 140 const devnull: io::file = if (need_devnull) { 141 yield os::open("/dev/null", fs::flag::RDWR)!; 142 } else -1; 143 144 for (let file .. cmd.files) { 145 const from = match (file.0) { 146 case let file: io::file => 147 yield file; 148 case nullfd => 149 yield devnull; 150 case closefd => 151 io::close(file.1)?; 152 continue; 153 }; 154 155 if (file.1 == from) { 156 let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { 157 case let flags: int => 158 yield flags; 159 case let e: rt::errno => 160 return errors::errno(e); 161 }; 162 rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; 163 } else { 164 match (rt::dup2(from, file.1)) { 165 case int => void; 166 case let e: rt::errno => 167 return errors::errno(e); 168 }; 169 }; 170 }; 171 172 if (cmd.dir != "") { 173 os::chdir(cmd.dir)?; 174 }; 175 176 return errors::errno(rt::execveat(cmd.platform, 177 "\0", argv: *[*]nullable *const u8, 178 envp: *[*]nullable *const u8, rt::AT_EMPTY_PATH)); 179 }; 180 181 fn platform_start(cmd: *command) (process | errors::error) = { 182 // TODO: Let the user configure clone more to their taste (e.g. SIGCHLD) 183 let pipe: [2]int = [0...]; 184 match (rt::pipe2(&pipe, rt::O_CLOEXEC)) { 185 case let err: rt::errno => 186 return errors::errno(err); 187 case void => void; 188 }; 189 190 match (rt::clone(null, rt::SIGCHLD, null, null, 0)) { 191 case let err: rt::errno => 192 return errors::errno(err); 193 case let pid: int => 194 rt::close(pipe[1])!; 195 defer rt::close(pipe[0])!; 196 let errno: int = 0; 197 match (rt::read(pipe[0], &errno, size(int))) { 198 case let err: rt::errno => 199 return errors::errno(err); 200 case let n: size => 201 switch (n) { 202 case size(int) => 203 return errors::errno(errno); 204 case 0 => 205 return pid; 206 case => 207 abort("Unexpected rt::read result"); 208 }; 209 }; 210 case void => 211 rt::close(pipe[0])!; 212 let err = platform_exec(cmd); 213 if (!(err is errors::opaque_)) { 214 rt::exit(1); 215 }; 216 let err = err as errors::opaque_; 217 let err = &err.data: *rt::errno; 218 rt::write(pipe[1], err, size(int))!; 219 rt::exit(1); 220 }; 221 };