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

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

kmk: kSubmit is mostly done.

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