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