exec+freebsd.ha (5598B)
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, pipe.1, os::stdout_file); 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 let fd = match (rt::open(path, rt::O_RDONLY, 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 95 fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; 96 97 fn platform_exec(cmd: *command) error = { 98 // We don't worry about freeing the return values from c::fromstr 99 // because once we exec(2) our heap is fried anyway 100 let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z); 101 for (let i = 0z; i < len(cmd.argv); i += 1z) { 102 append(argv, c::fromstr(cmd.argv[i])); 103 }; 104 append(argv, null); 105 106 let envp: nullable *[*]nullable *const c::char = null; 107 if (len(cmd.env) != 0) { 108 let env: []nullable *const c::char = alloc([], len(cmd.env) + 1); 109 for (let i = 0z; i < len(cmd.env); i += 1) { 110 append(env, c::fromstr(cmd.env[i])); 111 }; 112 append(env, null); 113 envp = env: *[*]nullable *const c::char; 114 }; 115 116 let need_devnull = false; 117 for (let i = 0z; i < len(cmd.files); i += 1) { 118 const from = match (cmd.files[i].0) { 119 case let file: io::file => 120 yield file; 121 case nullfd => 122 need_devnull = true; 123 continue; 124 case closefd => 125 continue; 126 }; 127 128 cmd.files[i].0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { 129 case let fd: int => 130 yield fd; 131 case let err: rt::errno => 132 return errors::errno(err); 133 }; 134 }; 135 136 const devnull: io::file = if (need_devnull) { 137 yield os::open("/dev/null")!; 138 } else -1; 139 140 for (let i = 0z; i < len(cmd.files); i += 1) { 141 const from = match (cmd.files[i].0) { 142 case let file: io::file => 143 yield file; 144 case nullfd => 145 yield devnull; 146 case closefd => 147 io::close(cmd.files[i].1)?; 148 continue; 149 }; 150 151 if (cmd.files[i].1 == from) { 152 let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { 153 case let flags: int => 154 yield flags; 155 case let e: rt::errno => 156 return errors::errno(e); 157 }; 158 rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; 159 } else { 160 match (rt::dup2(from, cmd.files[i].1)) { 161 case int => void; 162 case let e: rt::errno => 163 return errors::errno(e); 164 }; 165 }; 166 }; 167 168 if (cmd.dir != "") { 169 os::chdir(cmd.dir)?; 170 }; 171 172 return errors::errno(rt::fexecve(cmd.platform, 173 argv: *[*]nullable *const u8, 174 envp: *[*]nullable *const u8)); 175 }; 176 177 fn platform_start(cmd: *command) (process | errors::error) = { 178 // TODO: Let the user configure clone more to their taste (e.g. SIGCHLD) 179 let pipe: [2]int = [0...]; 180 match (rt::pipe2(&pipe, rt::O_CLOEXEC)) { 181 case let err: rt::errno => 182 return errors::errno(err); 183 case void => void; 184 }; 185 186 match (rt::fork()) { 187 case let err: rt::errno => 188 return errors::errno(err); 189 case let pid: int => 190 rt::close(pipe[1])!; 191 defer rt::close(pipe[0])!; 192 let errno: int = 0; 193 match (rt::read(pipe[0], &errno, size(int))) { 194 case let err: rt::errno => 195 return errors::errno(err); 196 case let n: size => 197 switch (n) { 198 case size(int) => 199 return errors::errno(errno); 200 case 0 => 201 return pid; 202 case => 203 abort("Unexpected rt::read result"); 204 }; 205 }; 206 case void => 207 rt::close(pipe[0])!; 208 let err = platform_exec(cmd); 209 if (!(err is errors::opaque_)) { 210 rt::exit(1); 211 }; 212 let err = err as errors::opaque_; 213 let err = &err.data: *rt::errno; 214 rt::write(pipe[1], err, size(int))!; 215 rt::exit(1); 216 }; 217 };