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