source: trunk/src/kmk/kmkbuiltin/kSubmit.c@ 2844

Last change on this file since 2844 was 2844, checked in by bird, 9 years ago

kSubmit/kWorker: updates

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 50.6 KB
Line 
1/* $Id: kSubmit.c 2844 2016-08-29 16:31:33Z bird $ */
2/** @file
3 * kMk Builtin command - submit job to a kWorker.
4 */
5
6/*
7 * Copyright (c) 2007-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/*******************************************************************************
27* Header Files *
28*******************************************************************************/
29#ifdef __APPLE__
30# define _POSIX_C_SOURCE 1 /* 10.4 sdk and unsetenv */
31#endif
32#include "make.h"
33#include "job.h"
34#include "variable.h"
35#include "pathstuff.h"
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <errno.h>
40#include <assert.h>
41#ifdef HAVE_ALLOCA_H
42# include <alloca.h>
43#endif
44#if defined(_MSC_VER)
45# include <ctype.h>
46# include <io.h>
47# include <direct.h>
48# include <process.h>
49#else
50# include <unistd.h>
51#endif
52#ifdef KBUILD_OS_WINDOWS
53# include "sub_proc.h"
54# include "quote_argv.h"
55#endif
56
57#include "kbuild.h"
58#include "kmkbuiltin.h"
59#include "err.h"
60
61
62/*********************************************************************************************************************************
63* Defined Constants And Macros *
64*********************************************************************************************************************************/
65/** Hashes a pid. */
66#define KWORKER_PID_HASH(a_pid) ((size_t)(a_pid) % 61)
67
68
69/*********************************************************************************************************************************
70* Structures and Typedefs *
71*********************************************************************************************************************************/
72typedef struct WORKERINSTANCE *PWORKERINSTANCE;
73typedef struct WORKERINSTANCE
74{
75 /** Pointer to the next worker instance. */
76 PWORKERINSTANCE pNext;
77 /** Pointer to the previous worker instance. */
78 PWORKERINSTANCE pPrev;
79 /** Pointer to the next worker with the same pid hash slot. */
80 PWORKERINSTANCE pNextPidHash;
81 /** 32 or 64. */
82 unsigned cBits;
83 /** The process ID of the kWorker process. */
84 pid_t pid;
85 union
86 {
87 struct
88 {
89 /** The exit code. */
90 int32_t rcExit;
91 /** Set to 1 if the worker is exiting. */
92 uint8_t bWorkerExiting;
93 uint8_t abUnused[3];
94 } s;
95 uint8_t ab[8];
96 } Result;
97 /** Number of result bytes read alread. */
98 size_t cbResultRead;
99
100#ifdef KBUILD_OS_WINDOWS
101 /** The process handle. */
102 HANDLE hProcess;
103 /** The bi-directional pipe we use to talk to the kWorker process. */
104 HANDLE hPipe;
105 /** For overlapped read (have valid event semaphore). */
106 OVERLAPPED OverlappedRead;
107#else
108 /** The socket descriptor we use to talk to the kWorker process. */
109 int fdSocket;
110#endif
111
112 /** What it's busy with. NULL if idle. */
113 struct child *pBusyWith;
114} WORKERINSTANCE;
115
116
117typedef struct WORKERLIST
118{
119 /** The head of the list. NULL if empty. */
120 PWORKERINSTANCE pHead;
121 /** The tail of the list. NULL if empty. */
122 PWORKERINSTANCE pTail;
123 /** Number of list entries. */
124 size_t cEntries;
125} WORKERLIST;
126typedef WORKERLIST *PWORKERLIST;
127
128
129/*********************************************************************************************************************************
130* Global Variables *
131*********************************************************************************************************************************/
132/** List of idle worker.*/
133static WORKERLIST g_IdleList;
134/** List of busy workers. */
135static WORKERLIST g_BusyList;
136/** PID hash table for the workers.
137 * @sa KWORKER_PID_HASH() */
138static PWORKERINSTANCE g_apPidHash[61];
139
140#ifdef KBUILD_OS_WINDOWS
141/** For naming the pipes.
142 * Also indicates how many worker instances we've spawned. */
143static unsigned g_uWorkerSeqNo = 0;
144#endif
145/** Set if we've registred the atexit handler already. */
146static int g_fAtExitRegistered = 0;
147
148/** @var g_cArchBits
149 * The bit count of the architecture this binary is compiled for. */
150/** @var g_szArch
151 * The name of the architecture this binary is compiled for. */
152/** @var g_cArchBits
153 * The bit count of the alternative architecture. */
154/** @var g_szAltArch
155 * The name of the alternative architecture. */
156#if defined(KBUILD_ARCH_AMD64)
157static unsigned g_cArchBits = 64;
158static char const g_szArch[] = "amd64";
159static unsigned g_cAltArchBits = 32;
160static char const g_szAltArch[] = "x86";
161#elif defined(KBUILD_ARCH_X86)
162static unsigned g_cArchBits = 32;
163static char const g_szArch[] = "x86";
164static unsigned g_cAltArchBits = 64;
165static char const g_szAltArch[] = "amd64";
166#else
167# error "Port me!"
168#endif
169
170
171
172/**
173 * Unlinks a worker instance from a list.
174 *
175 * @param pList The list.
176 * @param pWorker The worker.
177 */
178static void kSubmitListUnlink(PWORKERLIST pList, PWORKERINSTANCE pWorker)
179{
180 PWORKERINSTANCE pNext = pWorker->pNext;
181 PWORKERINSTANCE pPrev = pWorker->pPrev;
182
183 if (pNext)
184 {
185 assert(pNext->pPrev == pWorker);
186 pNext->pPrev = pPrev;
187 }
188 else
189 {
190 assert(pList->pHead == pWorker);
191 pList->pHead = pPrev;
192 }
193
194 if (pPrev)
195 {
196 assert(pPrev->pNext == pWorker);
197 pPrev->pNext = pNext;
198 }
199 else
200 {
201 assert(pList->pTail == pWorker);
202 pList->pTail = pNext;
203 }
204
205 assert(pList->cEntries > 0);
206 pList->cEntries--;
207
208 pWorker->pNext = NULL;
209 pWorker->pPrev = NULL;
210}
211
212
213/**
214 * Appends a worker instance to the tail of a list.
215 *
216 * @param pList The list.
217 * @param pWorker The worker.
218 */
219static void kSubmitListAppend(PWORKERLIST pList, PWORKERINSTANCE pWorker)
220{
221 PWORKERINSTANCE pTail = pList->pTail;
222
223 assert(pTail != pWorker);
224 assert(pList->pHead != pWorker);
225
226 pWorker->pNext = NULL;
227 pWorker->pPrev = pTail;
228 if (pTail != NULL)
229 {
230 assert(pTail->pNext == NULL);
231 pTail->pNext = pWorker;
232 }
233 else
234 {
235 assert(pList->pHead == NULL);
236 pList->pHead = pWorker;
237 pList->pTail = pWorker;
238 }
239
240 pList->cEntries++;
241}
242
243
244/**
245 * Remove worker from the process ID hash table.
246 *
247 * @param pWorker The worker.
248 */
249static void kSubmitPidHashRemove(PWORKERINSTANCE pWorker)
250{
251 size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
252 if (g_apPidHash[idxHash] == pWorker)
253 g_apPidHash[idxHash] = pWorker->pNext;
254 else
255 {
256 PWORKERINSTANCE pPrev = g_apPidHash[idxHash];
257 while (pPrev && pPrev->pNext != pWorker)
258 pPrev = pPrev->pNext;
259 assert(pPrev != NULL);
260 if (pPrev)
261 pPrev->pNext = pWorker->pNext;
262 }
263 pWorker->pid = -1;
264}
265
266
267/**
268 * Looks up a worker by its process ID.
269 *
270 * @returns Pointer to the worker instance if found. NULL if not.
271 * @param pid The process ID of the worker.
272 */
273static PWORKERINSTANCE kSubmitFindWorkerByPid(pid_t pid)
274{
275 PWORKERINSTANCE pWorker = g_apPidHash[KWORKER_PID_HASH(pid)];
276 while (pWorker && pWorker->pid != pid)
277 pWorker = pWorker->pNextPidHash;
278 return pWorker;
279}
280
281
282/**
283 * Creates a new worker process.
284 *
285 * @returns 0 on success, non-zero value on failure.
286 * @param pWorker The worker structure. Caller does the linking
287 * (as we might be reusing an existing worker
288 * instance because a worker shut itself down due
289 * to high resource leak level).
290 * @param cVerbosity The verbosity level.
291 */
292static int kSubmitSpawnWorker(PWORKERINSTANCE pWorker, int cVerbosity)
293{
294#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
295 static const char s_szWorkerName[] = "kWorker.exe";
296#else
297 static const char s_szWorkerName[] = "kWorker";
298#endif
299 const char *pszBinPath = get_kbuild_bin_path();
300 size_t const cchBinPath = strlen(pszBinPath);
301 size_t cchExectuable;
302 size_t const cbExecutableBuf = GET_PATH_MAX;
303 PATH_VAR(szExecutable);
304
305 /*
306 * Construct the executable path.
307 */
308 if ( pWorker->cBits == g_cArchBits
309 ? cchBinPath + 1 + sizeof(s_szWorkerName) <= cbExecutableBuf
310 : cchBinPath + 1 - sizeof(g_szArch) + sizeof(g_szAltArch) + sizeof(s_szWorkerName) <= cbExecutableBuf )
311 {
312#ifdef KBUILD_OS_WINDOWS
313 static DWORD s_fDenyRemoteClients = ~(DWORD)0;
314 wchar_t wszPipeName[64];
315 HANDLE hWorkerPipe;
316 SECURITY_ATTRIBUTES SecAttrs = { /*nLength:*/ sizeof(SecAttrs), /*pAttrs:*/ NULL, /*bInheritHandle:*/ TRUE };
317#else
318 int aiPair[2] = { -1, -1 };
319#endif
320
321 memcpy(szExecutable, pszBinPath, cchBinPath);
322 cchExectuable = cchBinPath;
323
324 /* Replace the arch bin directory extension with the alternative one if requested. */
325 if (pWorker->cBits != g_cArchBits)
326 {
327 if ( cchBinPath < sizeof(g_szArch)
328 || memcmp(&szExecutable[cchBinPath - sizeof(g_szArch) + 1], g_szArch, sizeof(g_szArch) - 1) != 0)
329 return errx(1, "KBUILD_BIN_PATH does not end with main architecture (%s) as expected: %s", pszBinPath, g_szArch);
330 cchExectuable -= sizeof(g_szArch) - 1;
331 memcpy(&szExecutable[cchExectuable], g_szAltArch, sizeof(g_szAltArch) - 1);
332 cchExectuable += sizeof(g_szAltArch) - 1;
333 }
334
335 /* Append a slash and the worker name. */
336 szExecutable[cchExectuable++] = '/';
337 memcpy(&szExecutable[cchExectuable], s_szWorkerName, sizeof(s_szWorkerName));
338
339#ifdef KBUILD_OS_WINDOWS
340 /*
341 * Create the bi-directional pipe. Worker end is marked inheritable, our end is not.
342 */
343 if (s_fDenyRemoteClients == ~(DWORD)0)
344 s_fDenyRemoteClients = GetVersion() >= 0x60000 ? PIPE_REJECT_REMOTE_CLIENTS : 0;
345 _snwprintf(wszPipeName, sizeof(wszPipeName), L"\\\\.\\pipe\\kmk-%u-kWorker-%u", getpid(), g_uWorkerSeqNo++);
346 hWorkerPipe = CreateNamedPipeW(wszPipeName,
347 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE /* win2k sp2+ */,
348 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | s_fDenyRemoteClients,
349 1 /* cMaxInstances */,
350 64 /*cbOutBuffer*/,
351 65536 /*cbInBuffer*/,
352 0 /*cMsDefaultTimeout -> 50ms*/,
353 &SecAttrs /* inherit */);
354 if (hWorkerPipe != INVALID_HANDLE_VALUE)
355 {
356 pWorker->hPipe = CreateFileW(wszPipeName,
357 GENERIC_READ | GENERIC_WRITE,
358 0 /* dwShareMode - no sharing */,
359 NULL /*pSecAttr - no inherit */,
360 OPEN_EXISTING,
361 FILE_FLAG_OVERLAPPED,
362 NULL /*hTemplate*/);
363 if (pWorker->hPipe != INVALID_HANDLE_VALUE)
364 {
365 pWorker->OverlappedRead.hEvent = CreateEventW(NULL /*pSecAttrs - no inherit*/, TRUE /*bManualReset*/,
366 TRUE /*bInitialState*/, NULL /*pwszName*/);
367 if (pWorker->OverlappedRead.hEvent != NULL)
368 {
369 char szHandleArg[32];
370 const char *apszArgs[4] = { szExecutable, "--pipe", szHandleArg, NULL };
371 _snprintf(szHandleArg, sizeof(szHandleArg), "%p", hWorkerPipe);
372
373 /*
374 * Create the worker process.
375 */
376 pWorker->hProcess = (HANDLE) _spawnve(_P_NOWAIT, szExecutable, apszArgs, environ);
377 if ((intptr_t)pWorker->hProcess != -1)
378 {
379 CloseHandle(hWorkerPipe);
380 pWorker->pid = GetProcessId(pWorker->hProcess);
381 if (cVerbosity > 0)
382 fprintf(stderr, "kSubmit: created %d bit worker %d\n", pWorker->cBits, pWorker->pid);
383 return 0;
384 }
385 err(1, "_spawnve(,%s,,)", szExecutable);
386 CloseHandle(pWorker->OverlappedRead.hEvent);
387 pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
388 }
389 else
390 errx(1, "CreateEventW failed: %u", GetLastError());
391 CloseHandle(pWorker->hPipe);
392 pWorker->hPipe = INVALID_HANDLE_VALUE;
393 }
394 else
395 errx(1, "Opening named pipe failed: %u", GetLastError());
396 CloseHandle(hWorkerPipe);
397 }
398 else
399 errx(1, "CreateNamedPipeW failed: %u", GetLastError());
400
401#else
402 /*
403 * Create a socket pair.
404 */
405 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, aiPair) == 0)
406 {
407 pWorker->fdSocket = aiPair[1];
408 }
409 else
410 err(1, "socketpair");
411#endif
412 }
413 else
414 errx(1, "KBUILD_BIN_PATH is too long");
415 return -1;
416}
417
418
419/**
420 * Selects an idle worker or spawns a new one.
421 *
422 * @returns Pointer to the selected worker instance. NULL on error.
423 * @param pWorker The idle worker instance to respawn.
424 * On failure this will be freed!
425 * @param cBitsWorker The worker bitness - 64 or 32.
426 */
427static int kSubmitRespawnWorker(PWORKERINSTANCE pWorker, int cVerbosity)
428{
429 /*
430 * Clean up after the old worker.
431 */
432#ifdef KBUILD_OS_WINDOWS
433 DWORD rcWait;
434
435 /* Close the pipe handle first, breaking the pipe in case it's not already
436 busted up. Close the event semaphore too before waiting for the process. */
437 if (pWorker->hPipe != INVALID_HANDLE_VALUE)
438 {
439 if (!CloseHandle(pWorker->hPipe))
440 warnx("CloseHandle(pWorker->hPipe): %u", GetLastError());
441 pWorker->hPipe = INVALID_HANDLE_VALUE;
442 }
443
444 if (!CloseHandle(pWorker->OverlappedRead.hEvent))
445 warnx("CloseHandle(pWorker->OverlappedRead.hEvent): %u", GetLastError());
446 pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
447
448 /* It's probably shutdown already, if not give it 10 milliseconds before
449 we terminate it forcefully. */
450 rcWait = WaitForSingleObject(pWorker->hProcess, 10);
451 if (rcWait != WAIT_OBJECT_0)
452 {
453 BOOL fRc = TerminateProcess(pWorker->hProcess, 127);
454 rcWait = WaitForSingleObject(pWorker->hProcess, 100);
455 if (rcWait != WAIT_OBJECT_0)
456 warnx("WaitForSingleObject returns %u (and TerminateProcess %d)", rcWait, fRc);
457 }
458
459 if (!CloseHandle(pWorker->hProcess))
460 warnx("CloseHandle(pWorker->hProcess): %u", GetLastError());
461 pWorker->hProcess = INVALID_HANDLE_VALUE;
462
463#else
464 pid_t pidWait;
465 int rc;
466
467 if (pWorker->fdSocket != -1)
468 {
469 if (close(pWorker->fdSocket) != 0)
470 warn("close(pWorker->fdSocket)");
471 pWorker->fdSocket = -1;
472 }
473
474 kill(pWorker->pid, SIGTERM);
475 pidWait = waitpid(pWorker->pid, &rc, 0);
476 if (pidWait != pWorker->pid)
477 warn("waitpid(pWorker->pid,,0)");
478#endif
479
480 /*
481 * Unlink it from the hash table.
482 */
483 kSubmitPidHashRemove(pWorker);
484
485 /*
486 * Respawn it.
487 */
488 if (kSubmitSpawnWorker(pWorker, cVerbosity) == 0)
489 {
490 /*
491 * Insert it into the process ID hash table and idle list.
492 */
493 size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
494 pWorker->pNextPidHash = g_apPidHash[idxHash];
495 g_apPidHash[idxHash] = pWorker;
496 return 0;
497 }
498
499 kSubmitListUnlink(&g_IdleList, pWorker);
500 free(pWorker);
501 return -1;
502}
503
504
505/**
506 * Selects an idle worker or spawns a new one.
507 *
508 * @returns Pointer to the selected worker instance. NULL on error.
509 * @param cBitsWorker The worker bitness - 64 or 32.
510 */
511static PWORKERINSTANCE kSubmitSelectWorkSpawnNewIfNecessary(unsigned cBitsWorker, int cVerbosity)
512{
513 /*
514 * Lookup up an idle worker.
515 */
516 PWORKERINSTANCE pWorker = g_IdleList.pHead;
517 while (pWorker)
518 {
519 if (pWorker->cBits == cBitsWorker)
520 return pWorker;
521 pWorker = pWorker->pNext;
522 }
523
524 /*
525 * Create a new worker instance.
526 */
527 pWorker = (PWORKERINSTANCE)xcalloc(sizeof(*pWorker));
528 pWorker->cBits = cBitsWorker;
529 if (kSubmitSpawnWorker(pWorker, cVerbosity) == 0)
530 {
531 /*
532 * Insert it into the process ID hash table and idle list.
533 */
534 size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
535 pWorker->pNextPidHash = g_apPidHash[idxHash];
536 g_apPidHash[idxHash] = pWorker;
537
538 kSubmitListAppend(&g_IdleList, pWorker);
539 return pWorker;
540 }
541
542 free(pWorker);
543 return NULL;
544}
545
546
547/**
548 * Composes a JOB mesage for a worker.
549 *
550 * @returns Pointer to the message.
551 * @param pszExecutable The executable to run.
552 * @param papszArgs The argument vector.
553 * @param papszEnvVars The environment vector.
554 * @param pszCwd The current directory.
555 * @param pcbMsg Where to return the message length.
556 */
557static void *kSubmitComposeJobMessage(const char *pszExecutable, char **papszArgs, char **papszEnvVars,
558 const char *pszCwd, uint32_t *pcbMsg)
559{
560 size_t cbTmp;
561 uint32_t i;
562 uint32_t cbMsg;
563 uint32_t cArgs;
564 uint32_t cEnvVars;
565 uint8_t *pbMsg;
566 uint8_t *pbCursor;
567
568 /*
569 * Adjust input.
570 */
571 if (!pszExecutable)
572 pszExecutable = papszArgs[0];
573
574 /*
575 * Calculate the message length first.
576 */
577 cbMsg = sizeof(cbMsg);
578 cbMsg += sizeof("JOB");
579 cbMsg += strlen(pszExecutable) + 1;
580 cbMsg += strlen(pszCwd) + 1;
581
582 cbMsg += sizeof(cArgs);
583 for (i = 0; papszArgs[i] != NULL; i++)
584 cbMsg += 1 + strlen(papszArgs[i]) + 1;
585 cArgs = i;
586
587 cbMsg += sizeof(cArgs);
588 for (i = 0; papszEnvVars[i] != NULL; i++)
589 cbMsg += strlen(papszEnvVars[i]) + 1;
590 cEnvVars = i;
591
592
593 /*
594 * Compose the message.
595 */
596 pbMsg = pbCursor = xmalloc(cbMsg);
597
598 memcpy(pbCursor, &cbMsg, sizeof(cbMsg));
599 pbCursor += sizeof(cbMsg);
600 memcpy(pbCursor, "JOB", sizeof("JOB"));
601 pbCursor += sizeof("JOB");
602
603 cbTmp = strlen(pszExecutable) + 1;
604 memcpy(pbCursor, pszExecutable, cbTmp);
605 pbCursor += cbTmp;
606
607 cbTmp = strlen(pszCwd) + 1;
608 memcpy(pbCursor, pszCwd, cbTmp);
609 pbCursor += cbTmp;
610
611 memcpy(pbCursor, &cArgs, sizeof(cArgs));
612 pbCursor += sizeof(cArgs);
613 for (i = 0; papszArgs[i] != NULL; i++)
614 {
615 *pbCursor++ = 0; /* Argument expansion flags (MSC, EMX). */
616 cbTmp = strlen(papszArgs[i]) + 1;
617 memcpy(pbCursor, papszArgs[i], cbTmp);
618 pbCursor += cbTmp;
619 }
620 assert(i == cArgs);
621
622 memcpy(pbCursor, &cEnvVars, sizeof(cEnvVars));
623 pbCursor += sizeof(cEnvVars);
624 for (i = 0; papszEnvVars[i] != NULL; i++)
625 {
626 cbTmp = strlen(papszEnvVars[i]) + 1;
627 memcpy(pbCursor, papszEnvVars[i], cbTmp);
628 pbCursor += cbTmp;
629 }
630 assert(i == cEnvVars);
631
632 assert(pbCursor - pbMsg == (size_t)cbMsg);
633
634 /* done */
635 *pcbMsg = cbMsg;
636 return pbMsg;
637}
638
639
640/**
641 * Sends the job message to the given worker, respawning the worker if
642 * necessary.
643 *
644 * @returns 0 on success, non-zero on failure.
645 *
646 * @param pWorker The work to send the request to. The worker is
647 * on the idle list.
648 * @param pvMsg The message to send.
649 * @param cbMsg The size of the message.
650 * @param fNoRespawning Set if
651 * @param cVerbosity The verbosity level.
652 */
653static int kSubmitSendJobMessage(PWORKERINSTANCE pWorker, void const *pvMsg, uint32_t cbMsg, int fNoRespawning, int cVerbosity)
654{
655 int cRetries;
656
657 /*
658 * Respawn the worker if it stopped by itself and we closed the pipe already.
659 */
660#ifdef KBUILD_OS_WINDOWS
661 if (pWorker->hPipe == INVALID_HANDLE_VALUE)
662#else
663 if (pWorker->fdSocket == -1)
664#endif
665 {
666 if (!fNoRespawning)
667 {
668 if (cVerbosity > 0)
669 fprintf(stderr, "kSubmit: Respawning worker (#1)...\n");
670 if (kSubmitRespawnWorker(pWorker, cVerbosity) != 0)
671 return 2;
672 }
673
674 }
675
676 /*
677 * Restart-on-broken-pipe loop. Necessary?
678 */
679 for (cRetries = !fNoRespawning ? 1 : 0; ; cRetries--)
680 {
681 /*
682 * Try write the message.
683 */
684 uint32_t cbLeft = cbMsg;
685 uint8_t const *pbLeft = (uint8_t const *)pvMsg;
686#ifdef KBUILD_OS_WINDOWS
687 DWORD dwErr;
688 DWORD cbWritten;
689 while (WriteFile(pWorker->hPipe, pbLeft, cbLeft, &cbWritten, NULL /*pOverlapped*/))
690 {
691 assert(cbWritten <= cbLeft);
692 cbLeft -= cbWritten;
693 if (!cbLeft)
694 return 0;
695
696 /* This scenario shouldn't really ever happen. But just in case... */
697 pbLeft += cbWritten;
698 }
699 dwErr = GetLastError();
700 if ( ( dwErr != ERROR_BROKEN_PIPE
701 && dwErr != ERROR_NO_DATA)
702 || cRetries <= 0)
703 return errx(1, "Error writing to worker: %u", dwErr);
704#else
705 ssize_t cbWritten
706 while ((cbWritten = write(pWorker->fdSocket, pbLeft, cbLeft)) >= 0)
707 {
708 assert(cbWritten <= cbLeft);
709 cbLeft -= cbWritten;
710 if (!cbLeft)
711 return 0;
712
713 pbLeft += cbWritten;
714 }
715 if ( ( errno != EPIPE
716 && errno != ENOTCONN
717 && errno != ECONNRESET))
718 || cRetries <= 0)
719 return err(1, "Error writing to worker");
720# error "later"
721#endif
722
723 /*
724 * Broken connection. Try respawn the worker.
725 */
726 if (cVerbosity > 0)
727 fprintf(stderr, "kSubmit: Respawning worker (#2)...\n");
728 if (kSubmitRespawnWorker(pWorker, cVerbosity) != 0)
729 return 2;
730 }
731}
732
733
734/**
735 * Closes the connection on a worker that said it is going to exit now.
736 *
737 * This is a way of dealing with imperfect resource management in the worker, it
738 * will monitor it a little and trigger a respawn when it looks bad.
739 *
740 * This function just closes the pipe / socket connection to the worker. The
741 * kSubmitSendJobMessage function will see this a trigger a respawn the next
742 * time the worker is engaged. This will usually mean there's a little delay in
743 * which the process can terminate without us having to actively wait for it.
744 *
745 * @param pWorker The worker instance.
746 */
747static void kSubmitCloseConnectOnExitingWorker(PWORKERINSTANCE pWorker)
748{
749#ifdef KBUILD_OS_WINDOWS
750 if (!CloseHandle(pWorker->hPipe))
751 warnx("CloseHandle(pWorker->hPipe): %u", GetLastError());
752 pWorker->hPipe = INVALID_HANDLE_VALUE;
753#else
754 if (close(pWorker->fdSocket) != 0)
755 warn("close(pWorker->fdSocket)");
756 pWorker->fdSocket = -1;
757#endif
758}
759
760
761#ifdef KBUILD_OS_WINDOWS
762
763/**
764 * Handles read failure.
765 *
766 * @returns Exit code.
767 * @param pWorker The worker instance.
768 * @param dwErr The error code.
769 */
770static int kSubmitWinReadFailed(PWORKERINSTANCE pWorker, DWORD dwErr)
771{
772 DWORD dwExitCode;
773
774 if (pWorker->cbResultRead == 0)
775 errx(1, "ReadFile failed: %u", dwErr);
776 else
777 errx(1, "ReadFile failed: %u (read %u bytes)", dwErr, pWorker->cbResultRead);
778 assert(dwErr != 0);
779
780 /* Complete the result. */
781 pWorker->Result.s.rcExit = 127;
782 pWorker->Result.s.bWorkerExiting = 1;
783 pWorker->cbResultRead = sizeof(pWorker->Result);
784
785 if (GetExitCodeProcess(pWorker->hProcess, &dwExitCode))
786 {
787 if (dwExitCode != 0)
788 pWorker->Result.s.rcExit = dwExitCode;
789 }
790
791 return dwErr != 0 ? (int)(dwErr & 0x7fffffff) : 0x7fffffff;
792
793}
794
795
796/**
797 * Used by
798 * @returns 0 if we got the whole result, -1 if I/O is pending, and windows last
799 * error on ReadFile failure.
800 * @param pWorker The worker instance.
801 */
802static int kSubmitReadMoreResultWin(PWORKERINSTANCE pWorker)
803{
804 /*
805 * Set up the result read, telling the sub_proc.c unit about it.
806 */
807 while (pWorker->cbResultRead < sizeof(pWorker->Result))
808 {
809 DWORD cbRead = 0;
810
811 BOOL fRc = ResetEvent(pWorker->OverlappedRead.hEvent);
812 assert(fRc); (void)fRc;
813
814 pWorker->OverlappedRead.Offset = 0;
815 pWorker->OverlappedRead.OffsetHigh = 0;
816
817 if (!ReadFile(pWorker->hPipe, &pWorker->Result.ab[pWorker->cbResultRead],
818 sizeof(pWorker->Result) - pWorker->cbResultRead,
819 &cbRead,
820 &pWorker->OverlappedRead))
821 {
822 DWORD dwErr = GetLastError();
823 if (dwErr == ERROR_IO_PENDING)
824 return -1;
825 return kSubmitWinReadFailed(pWorker, dwErr);
826 }
827
828 pWorker->cbResultRead += cbRead;
829 assert(pWorker->cbResultRead <= sizeof(pWorker->Result));
830 }
831 return 0;
832}
833
834#endif /* KBUILD_OS_WINDOWS */
835
836/**
837 * Marks the worker active.
838 *
839 * On windows this involves setting up the async result read and telling
840 * sub_proc.c about the process.
841 *
842 * @returns Exit code.
843 * @param pWorker The worker instance to mark as active.
844 * @param cVerbosity The verbosity level.
845 * @param pChild The kmk child to associate the job with.
846 * @param pPidSpawned If @a *pPidSpawned is non-zero if the child is
847 * running, otherwise the worker is already done
848 * and we've returned the exit code of the job.
849 */
850static int kSubmitMarkActive(PWORKERINSTANCE pWorker, int cVerbosity, struct child *pChild, pid_t *pPidSpawned)
851{
852#ifdef KBUILD_OS_WINDOWS
853 int rc;
854#endif
855
856 pWorker->cbResultRead = 0;
857
858#ifdef KBUILD_OS_WINDOWS
859 /*
860 * Setup the async result read on windows. If we're slow and the worker
861 * very fast, this may actually get the result immediately.
862 */
863l_again:
864 rc = kSubmitReadMoreResultWin(pWorker);
865 if (rc == -1)
866 {
867 if (process_kmk_register_submit(pWorker->OverlappedRead.hEvent, (intptr_t)pWorker, pPidSpawned) == 0)
868 { /* likely */ }
869 else
870 {
871 /* We need to do the waiting here because sub_proc.c has too much to do. */
872 warnx("Too many processes for sub_proc.c to handle!");
873 WaitForSingleObject(pWorker->OverlappedRead.hEvent, INFINITE);
874 goto l_again;
875 }
876 }
877 else
878 {
879 assert(rc == 0 || pWorker->Result.s.rcExit != 0);
880 if (pWorker->Result.s.bWorkerExiting)
881 kSubmitCloseConnectOnExitingWorker(pWorker);
882 *pPidSpawned = 0;
883 return pWorker->Result.s.rcExit;
884 }
885#endif
886
887 /*
888 * Mark it busy and move it to the active instance.
889 */
890 pWorker->pBusyWith = pChild;
891#ifndef KBUILD_OS_WINDOWS
892 *pPidSpawned = pWorker->pid;
893#endif
894
895 kSubmitListUnlink(&g_IdleList, pWorker);
896 kSubmitListAppend(&g_BusyList, pWorker);
897 return 0;
898}
899
900
901#ifdef KBUILD_OS_WINDOWS
902
903/**
904 * Retrieve the worker child result.
905 *
906 * If incomplete, we restart the ReadFile operation like kSubmitMarkActive does.
907 *
908 * @returns 0 on success, -1 if ReadFile was restarted.
909 * @param pvUser The worker instance.
910 * @param prcExit Where to return the exit code.
911 * @param piSigNo Where to return the signal number.
912 */
913int kSubmitSubProcGetResult(intptr_t pvUser, int *prcExit, int *piSigNo)
914{
915 PWORKERINSTANCE pWorker = (PWORKERINSTANCE)pvUser;
916
917 /*
918 * Get the overlapped result. There should be one since we're here
919 * because of a satisfied WaitForMultipleObject.
920 */
921 DWORD cbRead = 0;
922 if (GetOverlappedResult(pWorker->hPipe, &pWorker->OverlappedRead, &cbRead, TRUE))
923 {
924 pWorker->cbResultRead += cbRead;
925 assert(pWorker->cbResultRead <= sizeof(pWorker->Result));
926
927 /* More to be read? */
928 while (pWorker->cbResultRead < sizeof(pWorker->Result))
929 {
930 int rc = kSubmitReadMoreResultWin(pWorker);
931 if (rc == -1)
932 return -1;
933 assert(rc == 0 || pWorker->Result.s.rcExit != 0);
934 }
935 assert(pWorker->cbResultRead == sizeof(pWorker->Result));
936 }
937 else
938 {
939 DWORD dwErr = GetLastError();
940 kSubmitWinReadFailed(pWorker, dwErr);
941 }
942
943 /*
944 * Okay, we've got a result.
945 */
946 *prcExit = pWorker->Result.s.rcExit;
947 switch (pWorker->Result.s.rcExit)
948 {
949 default: *piSigNo = 0; break;
950 case CONTROL_C_EXIT: *piSigNo = SIGINT; break;
951 case STATUS_INTEGER_DIVIDE_BY_ZERO: *piSigNo = SIGFPE; break;
952 case STATUS_ACCESS_VIOLATION: *piSigNo = SIGSEGV; break;
953 case STATUS_PRIVILEGED_INSTRUCTION:
954 case STATUS_ILLEGAL_INSTRUCTION: *piSigNo = SIGILL; break;
955 }
956 return 0;
957}
958
959
960int kSubmitSubProcKill(intptr_t pvUser, int iSignal)
961{
962 return -1;
963}
964
965#endif /* KBUILD_OS_WINDOWS */
966
967
968/**
969 * atexit callback that trigger worker termination.
970 */
971static void kSubmitAtExitCallback(void)
972{
973 PWORKERINSTANCE pWorker;
974 DWORD msStartTick;
975 DWORD cKillRaids = 0;
976
977 /*
978 * Tell all the workers to exit by breaking the connection.
979 */
980 for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
981 kSubmitCloseConnectOnExitingWorker(pWorker);
982 for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
983 kSubmitCloseConnectOnExitingWorker(pWorker);
984
985 /*
986 * Wait a little while for them to stop.
987 */
988 Sleep(0);
989 msStartTick = GetTickCount();
990 for (;;)
991 {
992 /*
993 * Collect handles of running processes.
994 */
995 PWORKERINSTANCE apWorkers[MAXIMUM_WAIT_OBJECTS];
996 HANDLE ahHandles[MAXIMUM_WAIT_OBJECTS];
997 DWORD cHandles = 0;
998
999 for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
1000 if (pWorker->hProcess != INVALID_HANDLE_VALUE)
1001 {
1002 if (cHandles < MAXIMUM_WAIT_OBJECTS)
1003 {
1004 apWorkers[cHandles] = pWorker;
1005 ahHandles[cHandles] = pWorker->hProcess;
1006 }
1007 cHandles++;
1008 }
1009 for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
1010 if (pWorker->hProcess != INVALID_HANDLE_VALUE)
1011 {
1012 if (cHandles < MAXIMUM_WAIT_OBJECTS)
1013 {
1014 apWorkers[cHandles] = pWorker;
1015 ahHandles[cHandles] = pWorker->hProcess;
1016 }
1017 cHandles++;
1018 }
1019 if (cHandles == 0)
1020 return;
1021
1022 /*
1023 * Wait for the processes.
1024 */
1025 for (;;)
1026 {
1027 DWORD cMsElapsed = GetTickCount() - msStartTick;
1028 DWORD dwWait = WaitForMultipleObjects(cHandles <= MAXIMUM_WAIT_OBJECTS ? cHandles : MAXIMUM_WAIT_OBJECTS,
1029 ahHandles, FALSE /*bWaitAll*/,
1030 cMsElapsed < 1000 ? 1000 - cMsElapsed + 16 : 16);
1031 if ( dwWait >= WAIT_OBJECT_0
1032 && dwWait <= WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS)
1033 {
1034 size_t idx = dwWait - WAIT_OBJECT_0;
1035 CloseHandle(apWorkers[idx]->hProcess);
1036 apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE;
1037
1038 if (cHandles <= MAXIMUM_WAIT_OBJECTS)
1039 {
1040 /* Restart the wait with the worker removed, or quit if it was the last worker. */
1041 cHandles--;
1042 if (!cHandles)
1043 return;
1044 if (idx != cHandles)
1045 {
1046 apWorkers[idx] = apWorkers[cHandles];
1047 ahHandles[idx] = ahHandles[cHandles];
1048 }
1049 continue;
1050 }
1051 /* else: Reconstruct the wait array so we get maximum coverage. */
1052 }
1053 else if (dwWait == WAIT_TIMEOUT)
1054 {
1055 /* Terminate the whole bunch. */
1056 cKillRaids++;
1057 if (cKillRaids <= 2)
1058 {
1059 fprintf(stderr, "kmk/kSubmit: Killing %u lingering worker processe(s)!\n", cHandles);
1060 for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
1061 if (pWorker->hProcess != INVALID_HANDLE_VALUE)
1062 TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT);
1063 for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
1064 if (pWorker->hProcess != INVALID_HANDLE_VALUE)
1065 TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT);
1066 }
1067 else
1068 {
1069 fprintf(stderr, "kmk/kSubmit: Giving up on the last %u worker processe(s). :-(\n", cHandles);
1070 break;
1071 }
1072 }
1073 else
1074 {
1075 /* Some kind of wait error. Could be a bad handle, check each and remove
1076 bad ones as well as completed ones. */
1077 size_t idx;
1078 fprintf(stderr, "kmk/kSubmit: WaitForMultipleObjects unexpectedly returned %#u (err=%u)\n",
1079 dwWait, GetLastError());
1080 for (idx = 0; idx < cHandles; idx++)
1081 {
1082 dwWait = WaitForSingleObject(ahHandles[idx], 0 /*ms*/);
1083 if (dwWait != WAIT_TIMEOUT)
1084 {
1085 CloseHandle(apWorkers[idx]->hProcess);
1086 apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE;
1087 }
1088 }
1089 }
1090 break;
1091 } /* wait loop */
1092 } /* outer wait loop */
1093}
1094
1095
1096
1097/**
1098 * Handles the --set var=value option.
1099 *
1100 * @returns 0 on success, non-zero exit code on error.
1101 * @param papszEnv The environment vector.
1102 * @param pcEnvVars Pointer to the variable holding the number of
1103 * environment variables held by @a papszEnv.
1104 * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
1105 * environment vector.
1106 * @param cVerbosity The verbosity level.
1107 * @param pszValue The var=value string to apply.
1108 */
1109static int kSubmitOptEnvSet(char **papszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
1110 int cVerbosity, const char *pszValue)
1111{
1112 const char *pszEqual = strchr(pszValue, '=');
1113 if (pszEqual)
1114 {
1115 unsigned iEnvVar;
1116 unsigned cEnvVars = *pcEnvVars;
1117 size_t const cchVar = pszValue - pszEqual;
1118 for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
1119 if ( strncmp(papszEnv[iEnvVar], pszValue, cchVar) == 0
1120 && papszEnv[iEnvVar][cchVar] == '=')
1121 {
1122 if (cVerbosity > 0)
1123 fprintf(stderr, "kSubmit: replacing '%s' with '%s'\n", papszEnv[iEnvVar], pszValue);
1124 free(papszEnv[iEnvVar]);
1125 papszEnv[iEnvVar] = xstrdup(pszValue);
1126 break;
1127 }
1128 if (iEnvVar == cEnvVars)
1129 {
1130 /* Append new variable. We probably need to resize the vector. */
1131 if ((cEnvVars + 2) > *pcAllocatedEnvVars)
1132 {
1133 *pcAllocatedEnvVars = (cEnvVars + 2 + 0xf) & ~(unsigned)0xf;
1134 papszEnv = (char **)xrealloc(papszEnv, *pcAllocatedEnvVars * sizeof(papszEnv[0]));
1135 }
1136 papszEnv[cEnvVars++] = xstrdup(pszValue);
1137 papszEnv[cEnvVars] = NULL;
1138 *pcEnvVars = cEnvVars;
1139 if (cVerbosity > 0)
1140 fprintf(stderr, "kSubmit: added '%s'\n", papszEnv[iEnvVar]);
1141 }
1142 else
1143 {
1144 /* Check for duplicates. */
1145 for (iEnvVar++; iEnvVar < cEnvVars; iEnvVar++)
1146 if ( strncmp(papszEnv[iEnvVar], pszValue, cchVar) == 0
1147 && papszEnv[iEnvVar][cchVar] == '=')
1148 {
1149 if (cVerbosity > 0)
1150 fprintf(stderr, "kSubmit: removing duplicate '%s'\n", papszEnv[iEnvVar]);
1151 free(papszEnv[iEnvVar]);
1152 cEnvVars--;
1153 if (iEnvVar != cEnvVars)
1154 papszEnv[iEnvVar] = papszEnv[cEnvVars];
1155 papszEnv[cEnvVars] = NULL;
1156 iEnvVar--;
1157 }
1158 }
1159 }
1160 else
1161 return errx(1, "Missing '=': -E %s", pszValue);
1162
1163 return 0;
1164}
1165
1166
1167/**
1168 * Handles the --unset var option.
1169 *
1170 * @returns 0 on success, non-zero exit code on error.
1171 * @param papszEnv The environment vector.
1172 * @param pcEnvVars Pointer to the variable holding the number of
1173 * environment variables held by @a papszEnv.
1174 * @param cVerbosity The verbosity level.
1175 * @param pszVarToRemove The name of the variable to remove.
1176 */
1177static int kSubmitOptEnvUnset(char **papszEnv, unsigned *pcEnvVars, int cVerbosity, const char *pszVarToRemove)
1178{
1179 if (strchr(pszVarToRemove, '=') == NULL)
1180 {
1181 unsigned cRemoved = 0;
1182 size_t const cchVar = strlen(pszVarToRemove);
1183 unsigned cEnvVars = *pcEnvVars;
1184 unsigned iEnvVar;
1185
1186 for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
1187 if ( strncmp(papszEnv[iEnvVar], pszVarToRemove, cchVar) == 0
1188 && papszEnv[iEnvVar][cchVar] == '=')
1189 {
1190 if (cVerbosity > 0)
1191 fprintf(stderr, !cRemoved ? "kSubmit: removing '%s'\n"
1192 : "kSubmit: removing duplicate '%s'\n", papszEnv[iEnvVar]);
1193 free(papszEnv[iEnvVar]);
1194 cEnvVars--;
1195 if (iEnvVar != cEnvVars)
1196 papszEnv[iEnvVar] = papszEnv[cEnvVars];
1197 papszEnv[cEnvVars] = NULL;
1198 cRemoved++;
1199 iEnvVar--;
1200 }
1201 *pcEnvVars = cEnvVars;
1202
1203 if (cVerbosity > 0 && !cRemoved)
1204 fprintf(stderr, "kSubmit: not found '%s'\n", pszVarToRemove);
1205 }
1206 else
1207 return errx(1, "Found invalid variable name character '=' in: -U %s", pszVarToRemove);
1208 return 0;
1209}
1210
1211
1212
1213/**
1214 * Handles the --chdir dir option.
1215 *
1216 * @returns 0 on success, non-zero exit code on error.
1217 * @param pszCwd The CWD buffer. Contains current CWD on input,
1218 * modified by @a pszValue on output.
1219 * @param cbCwdBuf The size of the CWD buffer.
1220 * @param pszValue The --chdir value to apply.
1221 */
1222static int kSubmitOptChDir(char *pszCwd, size_t cbCwdBuf, const char *pszValue)
1223{
1224 size_t cchNewCwd = strlen(pszValue);
1225 size_t offDst;
1226 if (cchNewCwd)
1227 {
1228#ifdef HAVE_DOS_PATHS
1229 if (*pszValue == '/' || *pszValue == '\\')
1230 {
1231 if (pszValue[1] == '/' || pszValue[1] == '\\')
1232 offDst = 0; /* UNC */
1233 else if (pszCwd[1] == ':' && isalpha(pszCwd[0]))
1234 offDst = 2; /* Take drive letter from CWD. */
1235 else
1236 return errx(1, "UNC relative CWD not implemented: cur='%s' new='%s'", pszCwd, pszValue);
1237 }
1238 else if ( pszValue[1] == ':'
1239 && isalpha(pszValue[0]))
1240 {
1241 if (pszValue[2] == '/'|| pszValue[2] == '\\')
1242 offDst = 0; /* DOS style absolute path. */
1243 else if ( pszCwd[1] == ':'
1244 && tolower(pszCwd[0]) == tolower(pszValue[0]) )
1245 {
1246 pszValue += 2; /* Same drive as CWD, append drive relative path from value. */
1247 cchNewCwd -= 2;
1248 offDst = strlen(pszCwd);
1249 }
1250 else
1251 {
1252 /* Get current CWD on the specified drive and append value. */
1253 int iDrive = tolower(pszValue[0]) - 'a' + 1;
1254 if (!_getdcwd(iDrive, pszCwd, cbCwdBuf))
1255 return err(1, "_getdcwd(%d,,) failed", iDrive);
1256 pszValue += 2;
1257 cchNewCwd -= 2;
1258 }
1259 }
1260#else
1261 if (*pszValue == '/')
1262 offDst = 0;
1263#endif
1264 else
1265 offDst = strlen(pszCwd); /* Relative path, append to the existing CWD value. */
1266
1267 /* Do the copying. */
1268#ifdef HAVE_DOS_PATHS
1269 if (offDst > 0 && pszCwd[offDst - 1] != '/' && pszCwd[offDst - 1] != '\\')
1270#else
1271 if (offDst > 0 && pszCwd[offDst - 1] != '/')
1272#endif
1273 pszCwd[offDst++] = '/';
1274 if (offDst + cchNewCwd >= cbCwdBuf)
1275 return errx(1, "Too long CWD: %*.*s%s", offDst, offDst, pszCwd, pszValue);
1276 memcpy(&pszCwd[offDst], pszValue, cchNewCwd + 1);
1277 }
1278 /* else: relative, no change - quitely ignore. */
1279 return 0;
1280}
1281
1282
1283static int usage(FILE *pOut, const char *argv0)
1284{
1285 fprintf(pOut,
1286 "usage: %s [-Z|--zap-env] [-E|--set <var=val>] [-U|--unset <var=val>]\n"
1287 " [-C|--chdir <dir>] [--wcc-brain-damage]\n"
1288 " [-3|--32-bit] [-6|--64-bit] [-v] -- <program> [args]\n"
1289 " or: %s --help\n"
1290 " or: %s --version\n"
1291 "\n"
1292 "Options:\n"
1293 " -Z, --zap-env, -i, --ignore-environment\n"
1294 " Zaps the environment. Position dependent.\n"
1295 " -E, --set <var>=[value]\n"
1296 " Sets an enviornment variable putenv fashion. Position dependent.\n"
1297 " -U, --unset <var>\n"
1298 " Removes an environment variable. Position dependent.\n"
1299 " -C, --chdir <dir>\n"
1300 " Specifies the current directory for the program. Relative paths\n"
1301 " are relative to the previous -C option. Default is getcwd value.\n"
1302 " -3, --32-bit\n"
1303 " Selects a 32-bit kWorker process. Default: kmk bit count\n"
1304 " -6, --64-bit\n"
1305 " Selects a 64-bit kWorker process. Default: kmk bit count\n"
1306 " --wcc-brain-damage\n"
1307 " Works around wcc and wcc386 (Open Watcom) not following normal\n"
1308 " quoting conventions on Windows, OS/2, and DOS.\n"
1309 " -v,--verbose\n"
1310 " More verbose execution.\n"
1311 " -V,--version\n"
1312 " Show the version number.\n"
1313 " -h,--help\n"
1314 " Show this usage information.\n"
1315 "\n"
1316 ,
1317 argv0, argv0, argv0);
1318 return 1;
1319}
1320
1321
1322int kmk_builtin_kSubmit(int argc, char **argv, char **envp, struct child *pChild, pid_t *pPidSpawned)
1323{
1324 int rcExit = 0;
1325 int iArg;
1326 unsigned cAllocatedEnvVars;
1327 unsigned iEnvVar;
1328 unsigned cEnvVars;
1329 char **papszEnv = NULL;
1330 const char *pszExecutable = NULL;
1331 const char *pszCwd = NULL;
1332 unsigned cBitsWorker = g_cArchBits;
1333 int fWatcomBrainDamage = 0;
1334 int cVerbosity = 0;
1335 size_t const cbCwdBuf = GET_PATH_MAX;
1336 PATH_VAR(szCwd);
1337
1338 g_progname = argv[0];
1339
1340 /*
1341 * Create default program environment.
1342 */
1343 if (getcwd_fs(szCwd, cbCwdBuf) != NULL)
1344 { /* likely */ }
1345 else
1346 return err(1, "getcwd_fs failed\n");
1347
1348 papszEnv = pChild->environment;
1349 if (!papszEnv)
1350 pChild->environment = papszEnv = target_environment(pChild->file);
1351 cEnvVars = 0;
1352 while (papszEnv[cEnvVars] != NULL)
1353 cEnvVars++;
1354 cAllocatedEnvVars = cEnvVars;
1355
1356 /*
1357 * Parse the command line.
1358 */
1359 for (iArg = 1; iArg < argc; iArg++)
1360 {
1361 const char *pszArg = argv[iArg];
1362 if (*pszArg == '-')
1363 {
1364 char chOpt = *++pszArg;
1365 if (chOpt != '-')
1366 {
1367 if (chOpt != '\0')
1368 { /* likely */ }
1369 else
1370 {
1371 errx(1, "Incomplete option: '-'");
1372 return usage(stderr, argv[0]);
1373 }
1374 }
1375 else
1376 {
1377 pszArg++;
1378
1379 /* '--' indicates where the bits to execute start. */
1380 if (*pszArg == '\0')
1381 {
1382 iArg++;
1383 break;
1384 }
1385
1386 if (strcmp(pszArg, "watcom-brain-damage") == 0)
1387 {
1388 fWatcomBrainDamage = 1;
1389 continue;
1390 }
1391
1392 /* convert to short. */
1393 if (strcmp(pszArg, "help") == 0)
1394 chOpt = 'h';
1395 else if (strcmp(pszArg, "version") == 0)
1396 chOpt = 'V';
1397 else if (strcmp(pszArg, "set") == 0)
1398 chOpt = 'E';
1399 else if (strcmp(pszArg, "unset") == 0)
1400 chOpt = 'U';
1401 else if ( strcmp(pszArg, "zap-env") == 0
1402 || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ )
1403 chOpt = 'Z';
1404 else if (strcmp(pszArg, "chdir") == 0)
1405 chOpt = 'C';
1406 else if (strcmp(pszArg, "32-bit") == 0)
1407 chOpt = '3';
1408 else if (strcmp(pszArg, "64-bit") == 0)
1409 chOpt = '6';
1410 else if (strcmp(pszArg, "verbose") == 0)
1411 chOpt = 'v';
1412 else if (strcmp(pszArg, "executable") == 0)
1413 chOpt = 'e';
1414 else
1415 {
1416 errx(1, "Unknown option: '%s'", pszArg - 2);
1417 return usage(stderr, argv[0]);
1418 }
1419 pszArg = "";
1420 }
1421
1422 do
1423 {
1424 /* Get option value first, if the option takes one. */
1425 const char *pszValue = NULL;
1426 switch (chOpt)
1427 {
1428 case 'E':
1429 case 'U':
1430 case 'C':
1431 case 'e':
1432 if (*pszArg != '\0')
1433 pszValue = pszArg + (*pszArg == ':' || *pszArg == '=');
1434 else if (++iArg < argc)
1435 pszValue = argv[iArg];
1436 else
1437 {
1438 errx(1, "Option -%c requires an value!", chOpt);
1439 return usage(stderr, argv[0]);
1440 }
1441 break;
1442 }
1443
1444 switch (chOpt)
1445 {
1446 case 'Z':
1447 case 'i': /* GNU env compatibility. */
1448 for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
1449 free(papszEnv[iEnvVar]);
1450 papszEnv[0] = NULL;
1451 cEnvVars = 0;
1452 break;
1453
1454 case 'E':
1455 rcExit = kSubmitOptEnvSet(papszEnv, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
1456 pChild->environment = papszEnv;
1457 if (rcExit == 0)
1458 break;
1459 return rcExit;
1460
1461 case 'U':
1462 rcExit = kSubmitOptEnvUnset(papszEnv, &cEnvVars, cVerbosity, pszValue);
1463 if (rcExit == 0)
1464 break;
1465 return rcExit;
1466
1467 case 'C':
1468 rcExit = kSubmitOptChDir(szCwd, cbCwdBuf, pszValue);
1469 if (rcExit == 0)
1470 break;
1471 return rcExit;
1472
1473 case '3':
1474 cBitsWorker = 32;
1475 break;
1476
1477 case '6':
1478 cBitsWorker = 64;
1479 break;
1480
1481 case 'e':
1482 pszExecutable = pszValue;
1483 break;
1484
1485 case 'v':
1486 cVerbosity++;
1487 break;
1488
1489 case 'h':
1490 usage(stdout, argv[0]);
1491 return 0;
1492
1493 case 'V':
1494 return kbuild_version(argv[0]);
1495 }
1496 } while ((chOpt = *pszArg++) != '\0');
1497 }
1498 else
1499 {
1500 errx(1, "Unknown argument: '%s'", pszArg);
1501 return usage(stderr, argv[0]);
1502 }
1503 }
1504
1505 /*
1506 * Check that we've got something to execute.
1507 */
1508 if (iArg < argc)
1509 {
1510 uint32_t cbMsg;
1511 void *pvMsg = kSubmitComposeJobMessage(pszExecutable, &argv[iArg], papszEnv, szCwd, &cbMsg);
1512 PWORKERINSTANCE pWorker = kSubmitSelectWorkSpawnNewIfNecessary(cBitsWorker, cVerbosity);
1513 if (pWorker)
1514 {
1515#ifdef KBUILD_OS_WINDOWS
1516 /* Quote the argv elements, but first we need unquoted pszExecute. */
1517 char *pszFreeExec = NULL;
1518 if (!pszExecutable)
1519 pszExecutable = pszFreeExec = xstrdup(argv[0]);
1520 quote_argv(argc, argv, fWatcomBrainDamage, 1 /*fFreeOrLeak*/);
1521#endif
1522
1523 rcExit = kSubmitSendJobMessage(pWorker, pvMsg, cbMsg, 0 /*fNoRespawning*/, cVerbosity);
1524 if (rcExit == 0)
1525 rcExit = kSubmitMarkActive(pWorker, cVerbosity, pChild, pPidSpawned);
1526
1527 if (!g_fAtExitRegistered)
1528 if (atexit(kSubmitAtExitCallback) == 0)
1529 g_fAtExitRegistered = 1;
1530
1531#ifdef KBUILD_OS_WINDOWS
1532 free(pszFreeExec);
1533#endif
1534 }
1535 else
1536 rcExit = 1;
1537 free(pvMsg);
1538 }
1539 else
1540 {
1541 errx(1, "Nothing to executed!");
1542 rcExit = usage(stderr, argv[0]);
1543 }
1544
1545 return rcExit;
1546}
1547
1548
1549
Note: See TracBrowser for help on using the repository browser.