exec.ha (5615B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use errors; 5 use io; 6 use os; 7 use rt; 8 use types::c; 9 use unix; 10 11 // Forks the current process, returning the [[process]] of the child (to the 12 // parent) and void (to the child), or an error. 13 export fn fork() (process | void | error) = { 14 match (rt::fork()) { 15 case let err: rt::errno => 16 return errors::errno(err); 17 case let i: rt::pid_t => 18 return i: process; 19 case void => 20 return void; 21 }; 22 }; 23 24 // Creates an anonymous pipe for use with [[addfile]]. Any data written to the 25 // second file may be read from the first file. The caller should close one or 26 // both of the file descriptors after they have transferred them to another 27 // process, and after they have finished using them themselves, if applicable. 28 // 29 // This function will abort the process if the system is unable to allocate the 30 // resources for a pipe. If you need to handle this error gracefully, you may 31 // call [[unix::pipe]] yourself, but this may reduce the portability of your 32 // software. 33 // 34 // To capture the standard output of a process: 35 // 36 // let pipe = exec::pipe(); 37 // exec::addfile(&cmd, os::stdout_file, pipe.1); 38 // let proc = exec::start(&cmd)!; 39 // io::close(pipe.1)!; 40 // 41 // let data = io::drain(pipe.0)!; 42 // io::close(pipe.0)!; 43 // exec::wait(&proc)!; 44 // 45 // To write to the standard input of a process: 46 // 47 // let pipe = exec::pipe(); 48 // exec::addfile(&cmd, os::stdin_file, pipe.0); 49 // let proc = exec::start(&cmd)!; 50 // 51 // io::writeall(data)!; 52 // io::close(pipe.1)!; 53 // io::close(pipe.0)!; 54 // exec::wait(&proc)!; 55 export fn pipe() (io::file, io::file) = { 56 return unix::pipe()!; 57 }; 58 59 fn open(path: str) (platform_cmd | error) = { 60 // O_PATH is used because it allows us to use an executable for which we 61 // have execute permissions, but not read permissions. 62 let fd = match (rt::open(path, rt::O_PATH, 0u)) { 63 case let fd: int => 64 yield fd; 65 case let err: rt::errno => 66 return errors::errno(err); 67 }; 68 let success = false; 69 defer if (!success) rt::close(fd)!; 70 match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) { 71 case let err: rt::errno => 72 return errors::errno(err); 73 case let b: bool => 74 if (!b) { 75 return errors::noaccess; 76 }; 77 }; 78 // Make sure we are not trying to execute anything weird. fstat() 79 // already dereferences symlinks, so if this is anything other than a 80 // regular file it cannot be executed. 81 let s = rt::st { ... }; 82 match (rt::fstat(fd, &s)) { 83 case let err: rt::errno => 84 return errors::errno(err); 85 case void => 86 if (s.mode & rt::S_IFREG == 0) { 87 return errors::noaccess; 88 }; 89 }; 90 success = true; 91 return fd; 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")!; 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::execveat(cmd.platform, 172 "\0", argv: *[*]nullable *const u8, 173 envp: *[*]nullable *const u8, rt::AT_EMPTY_PATH)); 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::clone(null, rt::SIGCHLD, null, null, 0)) { 186 case let err: rt::errno => 187 return errors::errno(err); 188 case let pid: int => 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 };