exec.ha (4820B)
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 use path; 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 // const (read, write) = exec::pipe(); 39 // exec::addfile(&cmd, os::stdout_file, write); 40 // let proc = exec::start(&cmd)!; 41 // io::close(write)!; 42 // 43 // let data = io::drain(read)!; 44 // io::close(read)!; 45 // exec::wait(&proc)!; 46 // 47 // To write to the standard input of a process: 48 // 49 // const (read, write) = exec::pipe(); 50 // exec::addfile(&cmd, os::stdin_file, read); 51 // let proc = exec::start(&cmd)!; 52 // io::close(read)!; 53 // 54 // io::writeall(write, data)!; 55 // io::close(write)!; 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 if (os::access(path, os::amode::X_OK)?) { 63 // Length was already checked by access() 64 return path::init(path)!; 65 }; 66 return errors::noaccess; 67 }; 68 69 fn platform_finish(cmd: *command) void = void; 70 71 fn platform_exec(cmd: *command) error = { 72 // We don't worry about freeing the return values from c::fromstr 73 // because once we exec(2) our heap is fried anyway 74 let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z)!; 75 for (let arg .. cmd.argv) { 76 append(argv, c::fromstr(arg))!; 77 }; 78 append(argv, null)!; 79 80 let envp: nullable *[*]nullable *const c::char = null; 81 if (len(cmd.env) != 0) { 82 let env: []nullable *const c::char = alloc([], len(cmd.env) + 1)!; 83 for (let e .. cmd.env) { 84 append(env, c::fromstr(e))!; 85 }; 86 append(env, null)!; 87 envp = env: *[*]nullable *const c::char; 88 }; 89 90 let need_devnull = false; 91 for (let file &.. cmd.files) { 92 const from = match (file.0) { 93 case let file: io::file => 94 yield file; 95 case nullfd => 96 need_devnull = true; 97 continue; 98 case closefd => 99 continue; 100 }; 101 102 file.0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { 103 case let fd: int => 104 yield fd; 105 case let err: rt::errno => 106 return errors::errno(err); 107 }; 108 }; 109 110 const devnull: io::file = if (need_devnull) { 111 yield os::open("/dev/null", fs::flag::RDWR)!; 112 } else -1; 113 114 for (let file .. cmd.files) { 115 const from = match (file.0) { 116 case let file: io::file => 117 yield file; 118 case nullfd => 119 yield devnull; 120 case closefd => 121 io::close(file.1)?; 122 continue; 123 }; 124 125 if (file.1 == from) { 126 let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { 127 case let flags: int => 128 yield flags; 129 case let e: rt::errno => 130 return errors::errno(e); 131 }; 132 rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; 133 } else { 134 match (rt::dup2(from, file.1)) { 135 case int => void; 136 case let e: rt::errno => 137 return errors::errno(e); 138 }; 139 }; 140 }; 141 142 if (cmd.dir != "") { 143 os::chdir(cmd.dir)?; 144 }; 145 146 return errors::errno(rt::execve(path::string(&cmd.platform), 147 argv: *[*]nullable *const u8, 148 envp: *[*]nullable *const u8)); 149 }; 150 151 fn platform_start(cmd: *command) (process | errors::error) = { 152 // TODO: Let the user configure clone more to their taste (e.g. SIGCHLD) 153 let pipe: [2]int = [0...]; 154 match (rt::pipe2(&pipe, rt::O_CLOEXEC)) { 155 case let err: rt::errno => 156 return errors::errno(err); 157 case void => void; 158 }; 159 160 match (rt::fork()) { 161 case let err: rt::errno => 162 return errors::errno(err); 163 case let pid: int => 164 rt::close(pipe[1])!; 165 defer rt::close(pipe[0])!; 166 let errno: int = 0; 167 match (rt::read(pipe[0], &errno, size(int))) { 168 case let err: rt::errno => 169 return errors::errno(err); 170 case let n: size => 171 switch (n) { 172 case size(int) => 173 return errors::errno(errno); 174 case 0 => 175 return pid; 176 case => 177 abort("Unexpected rt::read result"); 178 }; 179 }; 180 case void => 181 rt::close(pipe[0])!; 182 let err = platform_exec(cmd); 183 if (!(err is errors::opaque_)) { 184 rt::exit(1); 185 }; 186 let err = err as errors::opaque_; 187 let err = &err.data: *rt::errno; 188 rt::write(pipe[1], err, size(int))!; 189 rt::exit(1); 190 }; 191 };