1 /** 2 * Copyright: Copyright Jason White, 2016 3 * License: MIT 4 * Authors: Jason White 5 * 6 * Description: 7 * Delegates dependency detection to the child process. 8 * 9 * This is done by creating pipes for the child process to send back the 10 * dependency information. The environment variables BUTTON_INPUTS and 11 * BUTTON_OUTPUTS are set to the file descriptors that the child should write 12 * to. This is also useful for the child process to determine if it is running 13 * under this build system or not. The child only needs to check if both of 14 * those environment variables are set. 15 * 16 * This handler should be used for commands that know how to communicate with 17 * Button. It is also commonly used by other handlers to run the command. 18 */ 19 module button.handlers.base; 20 21 import button.log; 22 import button.resource; 23 import button.context; 24 25 // Open /dev/null to be used by all child processes as its standard input. 26 version (Posix) 27 { 28 private __gshared static int devnull; 29 30 shared static this() 31 { 32 import io.file.stream : sysEnforce; 33 import core.sys.posix.fcntl : open, O_RDONLY; 34 devnull = open("/dev/null", O_RDONLY); 35 sysEnforce(devnull != -1, "Failed to open /dev/null"); 36 } 37 38 shared static ~this() 39 { 40 import core.sys.posix.unistd : close; 41 close(devnull); 42 } 43 } 44 45 version (Posix) 46 void execute( 47 ref BuildContext ctx, 48 const(string)[] args, 49 string workDir, 50 ref Resources inputs, 51 ref Resources outputs, 52 TaskLogger logger 53 ) 54 { 55 // FIXME: Commands should use a separate logger. It only uses the 56 // TaskLogger because there used to never be more than one command in a 57 // task. 58 59 import core.sys.posix.unistd; 60 import core.stdc.stdio : sprintf; 61 62 import io.file.stream : sysEnforce; 63 64 import std.string : toStringz; 65 import std.array : array; 66 67 import button.deps : deps; 68 import button.command : CommandError; 69 70 int[2] stdfds, inputfds, outputfds; 71 72 sysEnforce(pipe(stdfds) != -1); // Standard output 73 sysEnforce(pipe(inputfds) != -1); // Implicit inputs 74 sysEnforce(pipe(outputfds) != -1); // Implicit outputs 75 76 // Convert D command argument list to a null-terminated argument list 77 auto argv = new const(char)*[args.length+1]; 78 foreach (i; 0 .. args.length) 79 argv[i] = toStringz(args[i]); 80 argv[$-1] = null; 81 82 // Working directory 83 const(char)* cwd = null; 84 if (workDir.length) 85 cwd = workDir.toStringz(); 86 87 char[16] inputsenv, outputsenv; 88 sprintf(inputsenv.ptr, "%d", inputfds[1]); 89 sprintf(outputsenv.ptr, "%d", outputfds[1]); 90 91 immutable pid = fork(); 92 sysEnforce(pid >= 0, "Failed to fork current process"); 93 94 // Child process 95 if (pid == 0) 96 { 97 close(stdfds[0]); 98 close(inputfds[0]); 99 close(outputfds[0]); 100 101 executeChild(argv, cwd, devnull, stdfds[1], inputfds[1], 102 outputfds[1], inputsenv.ptr, outputsenv.ptr); 103 104 // Unreachable 105 } 106 107 // In the parent process 108 close(stdfds[1]); 109 close(inputfds[1]); 110 close(outputfds[1]); 111 112 // TODO: Parse the resources as they come in instead of all at once at 113 // the end. 114 auto implicit = readOutput(stdfds[0], inputfds[0], outputfds[0], logger); 115 116 // Add the inputs and outputs 117 inputs.put(implicit.inputs.deps); 118 outputs.put(implicit.outputs.deps); 119 120 // Wait for the child to exit 121 immutable exitCode = waitFor(pid); 122 123 if (exitCode != 0) 124 throw new CommandError(exitCode); 125 } 126 127 private version (Posix) 128 { 129 import std.array : Appender; 130 131 auto readOutput(int stdfd, int inputsfd, int outputsfd, TaskLogger logger) 132 { 133 import std.array : appender; 134 import std.algorithm : max; 135 import std.typecons : tuple; 136 import std.exception : assumeUnique; 137 138 import core.stdc.errno; 139 import core.sys.posix.unistd; 140 import core.sys.posix.sys.select; 141 142 import io.file.stream : SysException; 143 144 ubyte[4096] buf; 145 fd_set readfds = void; 146 147 auto inputs = appender!(ubyte[]); 148 auto outputs = appender!(ubyte[]); 149 150 while (true) 151 { 152 FD_ZERO(&readfds); 153 154 int nfds = 0; 155 156 if (stdfd != -1) 157 { 158 FD_SET(stdfd, &readfds); 159 nfds = max(nfds, stdfd); 160 } 161 162 if (inputsfd != -1) 163 { 164 FD_SET(inputsfd, &readfds); 165 nfds = max(nfds, inputsfd); 166 } 167 168 if (outputsfd != -1) 169 { 170 FD_SET(outputsfd, &readfds); 171 nfds = max(nfds, outputsfd); 172 } 173 174 if (nfds == 0) 175 break; 176 177 immutable r = select(nfds + 1, &readfds, null, null, null); 178 179 if (r == -1) 180 { 181 if (errno == EINTR) 182 continue; 183 184 throw new SysException("select() failed"); 185 } 186 187 if (r == 0) break; // Nothing in the set 188 189 // Read stdout/stderr from child 190 if (FD_ISSET(stdfd, &readfds)) 191 { 192 immutable len = read(stdfd, buf.ptr, buf.length); 193 if (len > 0) 194 { 195 logger.output(buf[0 .. len]); 196 } 197 else 198 { 199 close(stdfd); 200 stdfd = -1; 201 } 202 } 203 204 // Read inputs from child 205 if (FD_ISSET(inputsfd, &readfds)) 206 readFromChild(inputsfd, inputs, buf); 207 208 // Read inputs from child 209 if (FD_ISSET(outputsfd, &readfds)) 210 readFromChild(outputsfd, outputs, buf); 211 } 212 213 return tuple!("inputs", "outputs")( 214 assumeUnique(inputs.data), 215 assumeUnique(outputs.data) 216 ); 217 } 218 219 void readFromChild(ref int fd, ref Appender!(ubyte[]) a, ubyte[] buf) 220 { 221 import core.sys.posix.unistd : read, close; 222 223 immutable len = read(fd, buf.ptr, buf.length); 224 225 if (len > 0) 226 { 227 a.put(buf[0 .. len]); 228 } 229 else 230 { 231 // Either the other end of the pipe was closed or the end of the 232 // stream was reached. 233 close(fd); 234 fd = -1; 235 } 236 } 237 238 int waitFor(int pid) 239 { 240 import core.sys.posix.sys.wait; 241 import core.stdc.errno; 242 import io.file.stream : SysException; 243 244 while (true) 245 { 246 int status; 247 immutable check = waitpid(pid, &status, 0) == -1; 248 if (check == -1) 249 { 250 if (errno == ECHILD) 251 { 252 throw new SysException("Child process does not exist"); 253 } 254 else 255 { 256 // Keep waiting 257 assert(errno == EINTR); 258 continue; 259 } 260 } 261 262 if (WIFEXITED(status)) 263 return WEXITSTATUS(status); 264 else if (WIFSIGNALED(status)) 265 return -WTERMSIG(status); 266 } 267 } 268 269 /** 270 * Executes the child process. This is called after the fork(). 271 * 272 * NOTE: Memory should not be allocated here. It can cause the child process 273 * to hang. 274 */ 275 void executeChild(const(char*)[] argv, const(char)* cwd, 276 int devnull, int stdfd, 277 int inputsfd, int outputsfd, 278 const(char)* inputsenv, const(char)* outputsenv) 279 { 280 import core.sys.posix.unistd; 281 import core.sys.posix.stdlib : setenv; 282 import core.stdc.stdio : perror, stderr, fprintf; 283 import core.stdc.string : strerror; 284 import core.stdc.errno : errno; 285 286 // Get standard input from /dev/null. With potentially multiple tasks 287 // executing in parallel, the child cannot use standard input. 288 if (dup2(devnull, STDIN_FILENO) == -1) 289 { 290 perror("dup2"); 291 _exit(1); 292 } 293 294 close(devnull); 295 296 // Let the child know two bits of information: (1) that it is being run 297 // under this build system and (2) which file descriptors to use to send 298 // back dependency information. 299 setenv("BUTTON_INPUTS", inputsenv, 1); 300 setenv("BUTTON_OUTPUTS", outputsenv, 1); 301 302 // Redirect stdout/stderr to the pipe the parent reads from. There is no 303 // differentiation between stdout and stderr. 304 if (dup2(stdfd, STDOUT_FILENO) == -1) 305 { 306 perror("dup2"); 307 _exit(1); 308 } 309 310 if (dup2(stdfd, STDERR_FILENO) == -1) 311 { 312 perror("dup2"); 313 _exit(1); 314 } 315 316 close(stdfd); 317 318 if (cwd && (chdir(cwd) != 0)) 319 { 320 fprintf(stderr, "button: Error: Invalid working directory '%s' (%s)\n", 321 cwd, strerror(errno)); 322 _exit(1); 323 } 324 325 execvp(argv[0], argv.ptr); 326 327 // If we get this far, something went wrong. Most likely, the command does 328 // not exist. 329 fprintf(stderr, "button: Failed executing process '%s' (%s)\n", 330 argv[0], strerror(errno)); 331 _exit(1); 332 } 333 }