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