exec.ha (5483B)
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 let fd = match (rt::open(path, rt::O_RDONLY, 0u)) { 62 case let fd: int => 63 yield fd; 64 case let err: rt::errno => 65 return errors::errno(err); 66 }; 67 let success = false; 68 defer if (!success) rt::close(fd)!; 69 match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) { 70 case let err: rt::errno => 71 return errors::errno(err); 72 case let b: bool => 73 if (!b) { 74 return errors::noaccess; 75 }; 76 }; 77 // Make sure we are not trying to execute anything weird. fstat() 78 // already dereferences symlinks, so if this is anything other than a 79 // regular file it cannot be executed. 80 let s = rt::st { ... }; 81 match (rt::fstat(fd, &s)) { 82 case let err: rt::errno => 83 return errors::errno(err); 84 case void => 85 if (s.mode & rt::S_IFREG == 0) { 86 return errors::noaccess; 87 }; 88 }; 89 success = true; 90 return fd; 91 92 }; 93 94 fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; 95 96 fn platform_exec(cmd: *command) error = { 97 // We don't worry about freeing the return values from c::fromstr 98 // because once we exec(2) our heap is fried anyway 99 let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z)!; 100 for (let arg .. cmd.argv) { 101 append(argv, c::fromstr(arg))!; 102 }; 103 append(argv, null)!; 104 105 let envp: nullable *[*]nullable *const c::char = null; 106 if (len(cmd.env) != 0) { 107 let env: []nullable *const c::char = alloc([], len(cmd.env) + 1)!; 108 for (let e .. cmd.env) { 109 append(env, c::fromstr(e))!; 110 }; 111 append(env, null)!; 112 envp = env: *[*]nullable *const c::char; 113 }; 114 115 let need_devnull = false; 116 for (let file &.. cmd.files) { 117 const from = match (file.0) { 118 case let file: io::file => 119 yield file; 120 case nullfd => 121 need_devnull = true; 122 continue; 123 case closefd => 124 continue; 125 }; 126 127 file.0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { 128 case let fd: int => 129 yield fd; 130 case let err: rt::errno => 131 return errors::errno(err); 132 }; 133 }; 134 135 const devnull: io::file = if (need_devnull) { 136 yield os::open("/dev/null", fs::flag::RDWR)!; 137 } else -1; 138 139 for (let file .. cmd.files) { 140 const from = match (file.0) { 141 case let file: io::file => 142 yield file; 143 case nullfd => 144 yield devnull; 145 case closefd => 146 io::close(file.1)?; 147 continue; 148 }; 149 150 if (file.1 == from) { 151 let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { 152 case let flags: int => 153 yield flags; 154 case let e: rt::errno => 155 return errors::errno(e); 156 }; 157 rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; 158 } else { 159 match (rt::dup2(from, file.1)) { 160 case int => void; 161 case let e: rt::errno => 162 return errors::errno(e); 163 }; 164 }; 165 }; 166 167 if (cmd.dir != "") { 168 os::chdir(cmd.dir)?; 169 }; 170 171 return errors::errno(rt::fexecve(cmd.platform, 172 argv: *[*]nullable *const u8, 173 envp: *[*]nullable *const u8)); 174 }; 175 176 fn platform_start(cmd: *command) (process | errors::error) = { 177 // TODO: Let the user configure clone more to their taste (e.g. SIGCHLD) 178 let pipe: [2]int = [0...]; 179 match (rt::pipe2(&pipe, rt::O_CLOEXEC)) { 180 case let err: rt::errno => 181 return errors::errno(err); 182 case void => void; 183 }; 184 185 match (rt::fork()) { 186 case let err: rt::errno => 187 return errors::errno(err); 188 case let pid: rt::pid_t => 189 rt::close(pipe[1])!; 190 defer rt::close(pipe[0])!; 191 let errno: int = 0; 192 match (rt::read(pipe[0], &errno, size(int))) { 193 case let err: rt::errno => 194 return errors::errno(err); 195 case let n: size => 196 switch (n) { 197 case size(int) => 198 return errors::errno(errno); 199 case 0 => 200 return pid; 201 case => 202 abort("Unexpected rt::read result"); 203 }; 204 }; 205 case void => 206 rt::close(pipe[0])!; 207 let err = platform_exec(cmd); 208 if (!(err is errors::opaque_)) { 209 rt::exit(1); 210 }; 211 let err = err as errors::opaque_; 212 let err = &err.data: *rt::errno; 213 rt::write(pipe[1], err, size(int))!; 214 rt::exit(1); 215 }; 216 };