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