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

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

submit.c -> kSubmit.c

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.6 KB
Line 
1/* $Id: kSubmit.c 2842 2016-08-26 20:05:37Z 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
53#include "kbuild.h"
54#include "kmkbuiltin.h"
55#include "err.h"
56
57
58/*********************************************************************************************************************************
59* Defined Constants And Macros *
60*********************************************************************************************************************************/
61/** Hashes a pid. */
62#define KWORKER_PID_HASH(a_pid) ((size_t)(a_pid) % 61)
63
64
65/*********************************************************************************************************************************
66* Structures and Typedefs *
67*********************************************************************************************************************************/
68typedef struct WORKERINSTANCE *PWORKERINSTANCE;
69typedef struct WORKERINSTANCE
70{
71 /** Pointer to the next worker instance. */
72 PWORKERINSTANCE pNext;
73 /** Pointer to the previous worker instance. */
74 PWORKERINSTANCE pPrev;
75 /** Pointer to the next worker with the same pid hash slot. */
76 PWORKERINSTANCE pNextPidHash;
77 /** 32 or 64. */
78 unsigned cBits;
79 /** The process ID of the kWorker process. */
80 pid_t pid;
81#ifdef KBUILD_OS_WINDOWS
82 /** The process handle. */
83 HANDLE hProcess;
84 /** The bi-directional pipe we use to talk to the kWorker process. */
85 HANDLE hPipe;
86 /** For overlapped read (have valid event semaphore). */
87 OVERLAPPED OverlappedRead;
88 /** The 32-bit exit code read bufffer. */
89 uint32_t u32ReadResult;
90#else
91 /** The socket descriptor we use to talk to the kWorker process. */
92 int fdSocket;
93#endif
94
95 /** What it's busy with. NULL if idle. */
96 struct child *pBusyWith;
97} WORKERINSTANCE;
98
99
100typedef struct WORKERLIST
101{
102 /** The head of the list. NULL if empty. */
103 PWORKERINSTANCE pHead;
104 /** The tail of the list. NULL if empty. */
105 PWORKERINSTANCE pTail;
106 /** Number of list entries. */
107 size_t cEntries;
108} WORKERLIST;
109typedef WORKERLIST *PWORKERLIST;
110
111
112/*********************************************************************************************************************************
113* Global Variables *
114*********************************************************************************************************************************/
115/** List of idle worker.*/
116static WORKERLIST g_IdleList;
117/** List of busy workers. */
118static WORKERLIST g_BusyList;
119/** PID hash table for the workers.
120 * @sa KWORKER_PID_HASH() */
121static PWORKERINSTANCE g_apPidHash[61];
122
123#ifdef KBUILD_OS_WINDOWS
124/** For naming the pipes.
125 * Also indicates how many worker instances we've spawned. */
126static unsigned g_uWorkerSeqNo = 0;
127#endif
128
129/** @var g_cArchBits
130 * The bit count of the architecture this binary is compiled for. */
131/** @var g_szArch
132 * The name of the architecture this binary is compiled for. */
133/** @var g_cArchBits
134 * The bit count of the alternative architecture. */
135/** @var g_szAltArch
136 * The name of the alternative architecture. */
137#if defined(KBUILD_ARCH_AMD64)
138static unsigned g_cArchBits = 64;
139static char const g_szArch[] = "amd64";
140static unsigned g_cAltArchBits = 32;
141static char const g_szAltArch[] = "x86";
142#elif defined(KBUILD_ARCH_X86)
143static unsigned g_cArchBits = 32;
144static char const g_szArch[] = "x86";
145static unsigned g_cAltArchBits = 64;
146static char const g_szAltArch[] = "amd64";
147#else
148# error "Port me!"
149#endif
150
151
152/**
153 * Unlinks a worker instance from a list.
154 *
155 * @param pList The list.
156 * @param pWorker The worker.
157 */
158static void kSubmitListUnlink(PWORKERLIST pList, PWORKERINSTANCE pWorker)
159{
160 PWORKERINSTANCE pNext = pWorker->pNext;
161 PWORKERINSTANCE pPrev = pWorker->pPrev;
162
163 if (pNext)
164 {
165 assert(pNext->pPrev == pWorker);
166 pNext->pPrev = pPrev;
167 }
168 else
169 {
170 assert(pList->pHead == pWorker);
171 pList->pHead = pPrev;
172 }
173
174 if (pPrev)
175 {
176 assert(pPrev->pNext == pWorker);
177 pPrev->pNext = pNext;
178 }
179 else
180 {
181 assert(pList->pTail == pWorker);
182 pList->pTail = pNext;
183 }
184
185 assert(pList->cEntries > 0);
186 pList->cEntries--;
187
188 pWorker->pNext = NULL;
189 pWorker->pPrev = NULL;
190}
191
192
193/**
194 * Appends a worker instance to the tail of a list.
195 *
196 * @param pList The list.
197 * @param pWorker The worker.
198 */
199static void kSubmitListAppend(PWORKERLIST pList, PWORKERINSTANCE pWorker)
200{
201 PWORKERINSTANCE pTail = pList->pTail;
202
203 assert(pTail != pWorker);
204 assert(pList->pHead != pWorker);
205
206 pWorker->pNext = NULL;
207 pWorker->pPrev = pTail;
208 if (pTail != NULL)
209 {
210 assert(pTail->pNext == NULL);
211 pTail->pNext = pWorker;
212 }
213 else
214 {
215 assert(pList->pHead == NULL);
216 pList->pHead = pWorker;
217 pList->pTail = pWorker;
218 }
219
220 pList->cEntries++;
221}
222
223
224/**
225 * Looks up a worker by its process ID.
226 *
227 * @returns Pointer to the worker instance if found. NULL if not.
228 * @param pid The process ID of the worker.
229 */
230static PWORKERINSTANCE kSubmitFindWorkerByPid(pid_t pid)
231{
232 PWORKERINSTANCE pWorker = g_apPidHash[KWORKER_PID_HASH(pid)];
233 while (pWorker && pWorker->pid != pid)
234 pWorker = pWorker->pNextPidHash;
235 return pWorker;
236}
237
238
239/**
240 * Creates a new worker process.
241 *
242 * @returns 0 on success, non-zero value on failure.
243 * @param pWorker The worker structure. Caller does the linking
244 * (as we might be reusing an existing worker
245 * instance because a worker shut itself down due
246 * to high resource leak level).
247 * @param cVerbosity The verbosity level.
248 */
249static int kSubmitSpawnWorker(PWORKERINSTANCE pWorker, int cVerbosity)
250{
251#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
252 static const char s_szWorkerName[] = "kWorker.exe";
253#else
254 static const char s_szWorkerName[] = "kWorker";
255#endif
256 const char *pszBinPath = get_kbuild_bin_path();
257 size_t const cchBinPath = strlen(pszBinPath);
258 size_t cchExectuable;
259 size_t const cbExecutableBuf = GET_PATH_MAX;
260 PATH_VAR(szExecutable);
261
262 /*
263 * Construct the executable path.
264 */
265 if ( pWorker->cBits == g_cArchBits
266 ? cchBinPath + 1 + sizeof(s_szWorkerName) <= cbExecutableBuf
267 : cchBinPath + 1 - sizeof(g_szArch) + sizeof(g_szAltArch) + sizeof(s_szWorkerName) <= cbExecutableBuf )
268 {
269#ifdef KBUILD_OS_WINDOWS
270 static DWORD s_fDenyRemoteClients = ~(DWORD)0;
271 wchar_t wszPipeName[64];
272 HANDLE hWorkerPipe;
273 SECURITY_ATTRIBUTES SecAttrs = { /*nLength:*/ sizeof(SecAttrs), /*pAttrs:*/ NULL, /*bInheritHandle:*/ TRUE };
274#else
275 int aiPair[2] = { -1, -1 };
276#endif
277
278 memcpy(szExecutable, pszBinPath, cchBinPath);
279 cchExectuable = cchBinPath;
280
281 /* Replace the arch bin directory extension with the alternative one if requested. */
282 if (pWorker->cBits != g_cArchBits)
283 {
284 if ( cchBinPath < sizeof(g_szArch)
285 || memcmp(&szExecutable[cchBinPath - sizeof(g_szArch) + 1], g_szArch, sizeof(g_szArch) - 1) != 0)
286 return errx(1, "KBUILD_BIN_PATH does not end with main architecture (%s) as expected: %s", pszBinPath, g_szArch);
287 cchExectuable -= sizeof(g_szArch) - 1;
288 memcpy(&szExecutable[cchExectuable], g_szAltArch, sizeof(g_szAltArch) - 1);
289 cchExectuable += sizeof(g_szAltArch) - 1;
290 }
291
292 /* Append a slash and the worker name. */
293 szExecutable[cchExectuable++] = '/';
294 memcpy(&szExecutable[cchExectuable], s_szWorkerName, sizeof(s_szWorkerName));
295
296#ifdef KBUILD_OS_WINDOWS
297 /*
298 * Create the bi-directional pipe. Worker end is marked inheritable, our end is not.
299 */
300 if (s_fDenyRemoteClients == ~(DWORD)0)
301 s_fDenyRemoteClients = GetVersion() >= 0x60000 ? PIPE_REJECT_REMOTE_CLIENTS : 0;
302 _snwprintf(wszPipeName, sizeof(wszPipeName), L"\\\\.\\pipe\\kmk-%u-kWorker-%u", getpid(), g_uWorkerSeqNo++);
303 hWorkerPipe = CreateNamedPipeW(wszPipeName,
304 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE /* win2k sp2+ */,
305 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | s_fDenyRemoteClients,
306 1 /* cMaxInstances */,
307 64 /*cbOutBuffer*/,
308 65536 /*cbInBuffer*/,
309 0 /*cMsDefaultTimeout -> 50ms*/,
310 &SecAttrs /* inherit */);
311 if (hWorkerPipe != INVALID_HANDLE_VALUE)
312 {
313 pWorker->hPipe = CreateFileW(wszPipeName,
314 GENERIC_READ | GENERIC_WRITE,
315 0 /* dwShareMode - no sharing */,
316 NULL /*pSecAttr - no inherit */,
317 OPEN_EXISTING,
318 FILE_FLAG_OVERLAPPED,
319 NULL /*hTemplate*/);
320 if (pWorker->hPipe != INVALID_HANDLE_VALUE)
321 {
322 pWorker->OverlappedRead.hEvent = CreateEventW(NULL /*pSecAttrs - no inherit*/, TRUE /*bManualReset*/,
323 TRUE /*bInitialState*/, NULL /*pwszName*/);
324 if (pWorker->OverlappedRead.hEvent != NULL)
325 {
326 char szHandleArg[16];
327 const char *apszArgs[4] = { szExecutable, "--pipe", szHandleArg, NULL };
328 _snprintf(szHandleArg, sizeof(szHandleArg), "%p", hWorkerPipe);
329
330 /*
331 * Create the worker process.
332 */
333 pWorker->hProcess = (HANDLE) _spawnve(_P_NOWAIT, szExecutable, apszArgs, environ);
334 if ((intptr_t)pWorker->hProcess != -1)
335 {
336 CloseHandle(hWorkerPipe);
337 pWorker->pid = GetProcessId(pWorker->hProcess);
338 if (cVerbosity > 0)
339 fprintf(stderr, "kSubmit: created %d bit worker %d\n", pWorker->cBits, pWorker->pid);
340 return 0;
341 }
342 err(1, "_spawnve(,%s,,)", szExecutable);
343 CloseHandle(pWorker->OverlappedRead.hEvent);
344 pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
345 }
346 else
347 errx(1, "CreateEventW failed: %u", GetLastError());
348 CloseHandle(pWorker->hPipe);
349 pWorker->hPipe = INVALID_HANDLE_VALUE;
350 }
351 else
352 errx(1, "Opening named pipe failed: %u", GetLastError());
353 CloseHandle(hWorkerPipe);
354 }
355 else
356 errx(1, "CreateNamedPipeW failed: %u", GetLastError());
357
358#else
359 /*
360 * Create a socket pair.
361 */
362 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, aiPair) == 0)
363 {
364 pWorker->fdSocket = aiPair[1];
365 }
366 else
367 err(1, "socketpair");
368#endif
369 }
370 else
371 errx(1, "KBUILD_BIN_PATH is too long");
372 return -1;
373}
374
375
376/**
377 * Selects an idle worker or spawns a new one.
378 *
379 * @returns Pointer to the selected worker instance. NULL on error.
380 * @param pWorker The idle worker instance to respawn.
381 * On failure this will be freed!
382 * @param cBitsWorker The worker bitness - 64 or 32.
383 */
384static int kSubmitRespawnWorker(PWORKERINSTANCE pWorker, int cVerbosity)
385{
386 size_t idxHash;
387
388 /*
389 * Clean up after the old worker.
390 */
391#ifdef KBUILD_OS_WINDOWS
392 DWORD rcWait;
393
394 /* Close the pipe handle first, breaking the pipe in case it's not already
395 busted up. Close the event semaphore too before waiting for the process. */
396 if (!CloseHandle(pWorker->hPipe))
397 warnx("CloseHandle(pWorker->hPipe): %u", GetLastError());
398 pWorker->hPipe = INVALID_HANDLE_VALUE;
399
400 if (!CloseHandle(pWorker->OverlappedRead.hEvent))
401 warnx("CloseHandle(pWorker->OverlappedRead.hEvent): %u", GetLastError());
402 pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
403
404 /* It's probably shutdown already, if not give it 10 milliseconds before
405 we terminate it forcefully. */
406 rcWait = WaitForSingleObject(pWorker->hProcess, 10);
407 if (rcWait != WAIT_OBJECT_0)
408 {
409 BOOL fRc = TerminateProcess(pWorker->hProcess, 127);
410 rcWait = WaitForSingleObject(pWorker->hProcess, 100);
411 if (rcWait != WAIT_OBJECT_0)
412 warnx("WaitForSingleObject returns %u (and TerminateProcess %d)", rcWait, fRc);
413 }
414
415 if (!CloseHandle(pWorker->hProcess))
416 warnx("CloseHandle(pWorker->hProcess): %u", GetLastError());
417 pWorker->hProcess = INVALID_HANDLE_VALUE;
418
419#else
420 pid_t pidWait;
421 int rc;
422
423 if (close(pWorker->fdSocket) != 0)
424 warn("close(pWorker->fdSocket)");
425 pWorker->fdSocket = -1;
426
427 kill(pWorker->pid, SIGTERM);
428 pidWait = waitpid(pWorker->pid, &rc, 0);
429 if (pidWait != pWorker->pid)
430 warn("waitpid(pWorker->pid,,0)");
431#endif
432
433 /*
434 * Unlink it from the hash table.
435 */
436 idxHash = KWORKER_PID_HASH(pWorker->pid);
437 if (g_apPidHash[idxHash] == pWorker)
438 g_apPidHash[idxHash] = pWorker->pNext;
439 else
440 {
441 PWORKERINSTANCE pPrev = g_apPidHash[idxHash];
442 while (pPrev && pPrev->pNext != pWorker)
443 pPrev = pPrev->pNext;
444 assert(pPrev != NULL);
445 if (pPrev)
446 pPrev->pNext = pWorker->pNext;
447 }
448 pWorker->pid = -1;
449
450 /*
451 * Respawn it.
452 */
453 if (kSubmitSpawnWorker(pWorker, cVerbosity) == 0)
454 {
455 /*
456 * Insert it into the process ID hash table and idle list.
457 */
458 size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
459 pWorker->pNextPidHash = g_apPidHash[idxHash];
460 g_apPidHash[idxHash] = pWorker;
461 return 0;
462 }
463
464 kSubmitListUnlink(&g_IdleList, pWorker);
465 free(pWorker);
466 return -1;
467}
468
469
470/**
471 * Selects an idle worker or spawns a new one.
472 *
473 * @returns Pointer to the selected worker instance. NULL on error.
474 * @param cBitsWorker The worker bitness - 64 or 32.
475 */
476static PWORKERINSTANCE kSubmitSelectWorkSpawnNewIfNecessary(unsigned cBitsWorker, int cVerbosity)
477{
478 /*
479 * Lookup up an idle worker.
480 */
481 PWORKERINSTANCE pWorker = g_IdleList.pHead;
482 while (pWorker)
483 {
484 if (pWorker->cBits == cBitsWorker)
485 return pWorker;
486 pWorker = pWorker->pNext;
487 }
488
489 /*
490 * Create a new worker instance.
491 */
492 pWorker = (PWORKERINSTANCE)xcalloc(sizeof(*pWorker));
493 pWorker->cBits = cBitsWorker;
494 if (kSubmitSpawnWorker(pWorker, cVerbosity) == 0)
495 {
496 /*
497 * Insert it into the process ID hash table and idle list.
498 */
499 size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
500 pWorker->pNextPidHash = g_apPidHash[idxHash];
501 g_apPidHash[idxHash] = pWorker;
502
503 kSubmitListAppend(&g_IdleList, pWorker);
504 return pWorker;
505 }
506
507 free(pWorker);
508 return NULL;
509}
510
511
512/**
513 * Composes a JOB mesage for a worker.
514 *
515 * @returns Pointer to the message.
516 * @param pszExecutable The executable to run.
517 * @param papszArgs The argument vector.
518 * @param papszEnvVars The environment vector.
519 * @param pszCwd The current directory.
520 * @param pcbMsg Where to return the message length.
521 */
522static void *kSubmitComposeJobMessage(const char *pszExecutable, char **papszArgs, char **papszEnvVars,
523 const char *pszCwd, uint32_t *pcbMsg)
524{
525 size_t i;
526 size_t cbTmp;
527 uint32_t cbMsg;
528 uint8_t *pbMsg;
529 uint8_t *pbCursor;
530
531 /*
532 * Calculate the message length first.
533 */
534 cbMsg = sizeof(cbMsg);
535 cbMsg += sizeof("JOB");
536 cbMsg += strlen(pszExecutable) + 1;
537
538 for (i = 0; papszArgs[i] != NULL; i++)
539 cbMsg += strlen(papszArgs[i]) + 1;
540 cbMsg += 1;
541
542 for (i = 0; papszEnvVars[i] != NULL; i++)
543 cbMsg += strlen(papszEnvVars[i]) + 1;
544 cbMsg += 1;
545
546 cbMsg += strlen(pszCwd) + 1;
547
548 /*
549 * Compose the message.
550 */
551 pbMsg = pbCursor = xmalloc(cbMsg);
552
553 memcpy(pbCursor, &cbMsg, sizeof(cbMsg));
554 pbCursor += sizeof(cbMsg);
555 memcpy(pbCursor, "JOB", sizeof("JOB"));
556 pbCursor += sizeof("JOB");
557
558 cbTmp = strlen(pszExecutable) + 1;
559 memcpy(pbCursor, pszExecutable, cbTmp);
560 pbCursor += cbTmp;
561
562 for (i = 0; papszArgs[i] != NULL; i++)
563 {
564 cbTmp = strlen(papszArgs[i]) + 1;
565 memcpy(pbCursor, papszArgs[i], cbTmp);
566 pbCursor += cbTmp;
567 }
568 *pbCursor++ = '\0';
569
570 for (i = 0; papszEnvVars[i] != NULL; i++)
571 {
572 cbTmp = strlen(papszEnvVars[i]) + 1;
573 memcpy(pbCursor, papszEnvVars[i], cbTmp);
574 pbCursor += cbTmp;
575 }
576 *pbCursor++ = '\0';
577
578 cbTmp = strlen(pszCwd) + 1;
579 memcpy(pbCursor, pszCwd, cbTmp);
580 pbCursor += cbTmp;
581
582 assert(pbCursor - pbMsg == (size_t)cbMsg);
583
584 /* done */
585 *pcbMsg = cbMsg;
586 return pbMsg;
587}
588
589
590/**
591 * Sends the job message to the given worker, respawning the worker if
592 * necessary.
593 *
594 * @returns 0 on success, non-zero on failure.
595 *
596 * @param pWorker The work to send the request to. The worker is
597 * on the idle list.
598 * @param pvMsg The message to send.
599 * @param cbMsg The size of the message.
600 * @param cVerbosity The verbosity level.
601 */
602static int kSubmitSendJobMessage(PWORKERINSTANCE pWorker, void const *pvMsg, uint32_t cbMsg, int cVerbosity)
603{
604 int cRetries = 1;
605 for (;; cRetries--)
606 {
607 /*
608 * Try write the message.
609 */
610 uint32_t cbLeft = cbMsg;
611 uint8_t const *pbLeft = (uint8_t const *)pvMsg;
612#ifdef KBUILD_OS_WINDOWS
613 DWORD dwErr;
614 DWORD cbWritten;
615 while (WriteFile(pWorker->hPipe, pbLeft, cbLeft, &cbWritten, NULL /*pOverlapped*/))
616 {
617 assert(cbWritten <= cbLeft);
618 cbLeft -= cbWritten;
619 if (!cbLeft)
620 return 0;
621
622 /* This scenario shouldn't really ever happen. But just in case... */
623 pbLeft += cbWritten;
624 }
625 dwErr = GetLastError();
626 if ( ( dwErr != ERROR_BROKEN_PIPE
627 && dwErr != ERROR_NO_DATA)
628 || cRetries <= 0)
629 return errx(1, "Error writing to worker: %u", dwErr);
630#else
631 ssize_t cbWritten
632 while ((cbWritten = write(pWorker->fdSocket, pbLeft, cbLeft)) >= 0)
633 {
634 assert(cbWritten <= cbLeft);
635 cbLeft -= cbWritten;
636 if (!cbLeft)
637 return 0;
638
639 pbLeft += cbWritten;
640 }
641 if ( ( errno != EPIPE
642 && errno != ENOTCONN
643 && errno != ECONNRESET))
644 || cRetries <= 0)
645 return err(1, "Error writing to worker");
646# error "later"
647#endif
648
649 /*
650 * Broken connection. Try respawn the worker.
651 */
652 if (kSubmitRespawnWorker(pWorker, cVerbosity) != 0)
653 return 2;
654 }
655}
656
657
658/**
659 * Handles the --set var=value option.
660 *
661 * @returns 0 on success, non-zero exit code on error.
662 * @param papszEnv The environment vector.
663 * @param pcEnvVars Pointer to the variable holding the number of
664 * environment variables held by @a papszEnv.
665 * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
666 * environment vector.
667 * @param cVerbosity The verbosity level.
668 * @param pszValue The var=value string to apply.
669 */
670static int kSubmitOptEnvSet(char **papszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
671 int cVerbosity, const char *pszValue)
672{
673 const char *pszEqual = strchr(pszValue, '=');
674 if (pszEqual)
675 {
676 unsigned iEnvVar;
677 unsigned cEnvVars = *pcEnvVars;
678 size_t const cchVar = pszValue - pszEqual;
679 for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
680 if ( strncmp(papszEnv[iEnvVar], pszValue, cchVar) == 0
681 && papszEnv[iEnvVar][cchVar] == '=')
682 {
683 if (cVerbosity > 0)
684 fprintf(stderr, "kSubmit: replacing '%s' with '%s'\n", papszEnv[iEnvVar], pszValue);
685 free(papszEnv[iEnvVar]);
686 papszEnv[iEnvVar] = xstrdup(pszValue);
687 break;
688 }
689 if (iEnvVar == cEnvVars)
690 {
691 /* Append new variable. We probably need to resize the vector. */
692 if ((cEnvVars + 2) > *pcAllocatedEnvVars)
693 {
694 *pcAllocatedEnvVars = (cEnvVars + 2 + 0xf) & ~(unsigned)0xf;
695 papszEnv = (char **)xrealloc(papszEnv, *pcAllocatedEnvVars * sizeof(papszEnv[0]));
696 }
697 papszEnv[cEnvVars++] = xstrdup(pszValue);
698 papszEnv[cEnvVars] = NULL;
699 *pcEnvVars = cEnvVars;
700 if (cVerbosity > 0)
701 fprintf(stderr, "kSubmit: added '%s'\n", papszEnv[iEnvVar]);
702 }
703 else
704 {
705 /* Check for duplicates. */
706 for (iEnvVar++; iEnvVar < cEnvVars; iEnvVar++)
707 if ( strncmp(papszEnv[iEnvVar], pszValue, cchVar) == 0
708 && papszEnv[iEnvVar][cchVar] == '=')
709 {
710 if (cVerbosity > 0)
711 fprintf(stderr, "kSubmit: removing duplicate '%s'\n", papszEnv[iEnvVar]);
712 free(papszEnv[iEnvVar]);
713 cEnvVars--;
714 if (iEnvVar != cEnvVars)
715 papszEnv[iEnvVar] = papszEnv[cEnvVars];
716 papszEnv[cEnvVars] = NULL;
717 iEnvVar--;
718 }
719 }
720 }
721 else
722 return errx(1, "Missing '=': -E %s", pszValue);
723
724 return 0;
725}
726
727
728/**
729 * Handles the --unset var option.
730 *
731 * @returns 0 on success, non-zero exit code on error.
732 * @param papszEnv The environment vector.
733 * @param pcEnvVars Pointer to the variable holding the number of
734 * environment variables held by @a papszEnv.
735 * @param cVerbosity The verbosity level.
736 * @param pszVarToRemove The name of the variable to remove.
737 */
738static int kSubmitOptEnvUnset(char **papszEnv, unsigned *pcEnvVars, int cVerbosity, const char *pszVarToRemove)
739{
740 if (strchr(pszVarToRemove, '=') == NULL)
741 {
742 unsigned cRemoved = 0;
743 size_t const cchVar = strlen(pszVarToRemove);
744 unsigned cEnvVars = *pcEnvVars;
745 unsigned iEnvVar;
746
747 for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
748 if ( strncmp(papszEnv[iEnvVar], pszVarToRemove, cchVar) == 0
749 && papszEnv[iEnvVar][cchVar] == '=')
750 {
751 if (cVerbosity > 0)
752 fprintf(stderr, !cRemoved ? "kSubmit: removing '%s'\n"
753 : "kSubmit: removing duplicate '%s'\n", papszEnv[iEnvVar]);
754 free(papszEnv[iEnvVar]);
755 cEnvVars--;
756 if (iEnvVar != cEnvVars)
757 papszEnv[iEnvVar] = papszEnv[cEnvVars];
758 papszEnv[cEnvVars] = NULL;
759 cRemoved++;
760 iEnvVar--;
761 }
762 *pcEnvVars = cEnvVars;
763
764 if (cVerbosity > 0 && !cRemoved)
765 fprintf(stderr, "kSubmit: not found '%s'\n", pszVarToRemove);
766 }
767 else
768 return errx(1, "Found invalid variable name character '=' in: -U %s", pszVarToRemove);
769 return 0;
770}
771
772
773
774/**
775 * Handles the --chdir dir option.
776 *
777 * @returns 0 on success, non-zero exit code on error.
778 * @param pszCwd The CWD buffer. Contains current CWD on input,
779 * modified by @a pszValue on output.
780 * @param cbCwdBuf The size of the CWD buffer.
781 * @param pszValue The --chdir value to apply.
782 */
783static int kSubmitOptChDir(char *pszCwd, size_t cbCwdBuf, const char *pszValue)
784{
785 size_t cchNewCwd = strlen(pszValue);
786 size_t offDst;
787 if (cchNewCwd)
788 {
789#ifdef HAVE_DOS_PATHS
790 if (*pszValue == '/' || *pszValue == '\\')
791 {
792 if (pszValue[1] == '/' || pszValue[1] == '\\')
793 offDst = 0; /* UNC */
794 else if (pszCwd[1] == ':' && isalpha(pszCwd[0]))
795 offDst = 2; /* Take drive letter from CWD. */
796 else
797 return errx(1, "UNC relative CWD not implemented: cur='%s' new='%s'", pszCwd, pszValue);
798 }
799 else if ( pszValue[1] == ':'
800 && isalpha(pszValue[0]))
801 {
802 if (pszValue[2] == '/'|| pszValue[2] == '\\')
803 offDst = 0; /* DOS style absolute path. */
804 else if ( pszCwd[1] == ':'
805 && tolower(pszCwd[0]) == tolower(pszValue[0]) )
806 {
807 pszValue += 2; /* Same drive as CWD, append drive relative path from value. */
808 cchNewCwd -= 2;
809 offDst = strlen(pszCwd);
810 }
811 else
812 {
813 /* Get current CWD on the specified drive and append value. */
814 int iDrive = tolower(pszValue[0]) - 'a' + 1;
815 if (!_getdcwd(iDrive, pszCwd, cbCwdBuf))
816 return err(1, "_getdcwd(%d,,) failed", iDrive);
817 pszValue += 2;
818 cchNewCwd -= 2;
819 }
820 }
821#else
822 if (*pszValue == '/')
823 offDst = 0;
824#endif
825 else
826 offDst = strlen(pszCwd); /* Relative path, append to the existing CWD value. */
827
828 /* Do the copying. */
829#ifdef HAVE_DOS_PATHS
830 if (offDst > 0 && pszCwd[offDst - 1] != '/' && pszCwd[offDst - 1] != '\\')
831#else
832 if (offDst > 0 && pszCwd[offDst - 1] != '/')
833#endif
834 pszCwd[offDst++] = '/';
835 if (offDst + cchNewCwd >= cbCwdBuf)
836 return errx(1, "Too long CWD: %*.*s%s", offDst, offDst, pszCwd, pszValue);
837 memcpy(&pszCwd[offDst], pszValue, cchNewCwd + 1);
838 }
839 /* else: relative, no change - quitely ignore. */
840 return 0;
841}
842
843
844static int usage(FILE *pOut, const char *argv0)
845{
846 fprintf(pOut,
847 "usage: %s [-Z|--zap-env] [-E|--set <var=val>] [-U|--unset <var=val>]\n"
848 " [-C|--chdir <dir>] [--wcc-brain-damage]\n"
849 " [-3|--32-bit] [-6|--64-bit] [-v] -- <program> [args]\n"
850 " or: %s --help\n"
851 " or: %s --version\n"
852 "\n"
853 "Options:\n"
854 " -Z, --zap-env, -i, --ignore-environment\n"
855 " Zaps the environment. Position dependent.\n"
856 " -E, --set <var>=[value]\n"
857 " Sets an enviornment variable putenv fashion. Position dependent.\n"
858 " -U, --unset <var>\n"
859 " Removes an environment variable. Position dependent.\n"
860 " -C, --chdir <dir>\n"
861 " Specifies the current directory for the program. Relative paths\n"
862 " are relative to the previous -C option. Default is getcwd value.\n"
863 " -3, --32-bit\n"
864 " Selects a 32-bit kWorker process. Default: kmk bit count\n"
865 " -6, --64-bit\n"
866 " Selects a 64-bit kWorker process. Default: kmk bit count\n"
867 " --wcc-brain-damage\n"
868 " Works around wcc and wcc386 (Open Watcom) not following normal\n"
869 " quoting conventions on Windows, OS/2, and DOS.\n"
870 " -v,--verbose\n"
871 " More verbose execution.\n"
872 " -V,--version\n"
873 " Show the version number.\n"
874 " -h,--help\n"
875 " Show this usage information.\n"
876 "\n"
877 ,
878 argv0, argv0, argv0);
879 return 1;
880}
881
882
883int kmk_builtin_kSubmit(int argc, char **argv, char **envp, struct child *pChild)
884{
885 int rcExit = 0;
886 int iArg;
887 unsigned cAllocatedEnvVars;
888 unsigned iEnvVar;
889 unsigned cEnvVars;
890 char **papszEnv = NULL;
891 const char *pszExecutable = NULL;
892 const char *pszCwd = NULL;
893 unsigned cBitsWorker = g_cArchBits;
894 int fWatcomBrainDamage = 0;
895 int cVerbosity = 0;
896 size_t const cbCwdBuf = GET_PATH_MAX;
897 PATH_VAR(szCwd);
898
899 g_progname = argv[0];
900
901 /*
902 * Create default program environment.
903 */
904 if (getcwd_fs(szCwd, cbCwdBuf) != NULL)
905 { /* likely */ }
906 else
907 return err(1, "getcwd_fs failed\n");
908
909 papszEnv = pChild->environment;
910 if (papszEnv)
911 pChild->environment = papszEnv = target_environment(pChild->file);
912 cEnvVars = 0;
913 while (papszEnv[cEnvVars] != NULL)
914 cEnvVars++;
915 cAllocatedEnvVars = cEnvVars;
916
917 /*
918 * Parse the command line.
919 */
920 for (iArg = 1; iArg < argc; iArg++)
921 {
922 const char *pszArg = argv[iArg];
923 if (*pszArg == '-')
924 {
925 char chOpt = *++pszArg;
926 if (chOpt != '-')
927 {
928 if (chOpt != '\0')
929 { /* likely */ }
930 else
931 {
932 errx(1, "Incomplete option: '-'");
933 return usage(stderr, argv[0]);
934 }
935 }
936 else
937 {
938 pszArg++;
939
940 /* '--' indicates where the bits to execute start. */
941 if (*pszArg == '\0')
942 {
943 iArg++;
944 break;
945 }
946
947 if (strcmp(pszArg, "watcom-brain-damage") == 0)
948 {
949 fWatcomBrainDamage = 1;
950 continue;
951 }
952
953 /* convert to short. */
954 if (strcmp(pszArg, "help") == 0)
955 chOpt = 'h';
956 else if (strcmp(pszArg, "version") == 0)
957 chOpt = 'V';
958 else if (strcmp(pszArg, "set") == 0)
959 chOpt = 'E';
960 else if (strcmp(pszArg, "unset") == 0)
961 chOpt = 'U';
962 else if ( strcmp(pszArg, "zap-env") == 0
963 || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ )
964 chOpt = 'Z';
965 else if (strcmp(pszArg, "chdir") == 0)
966 chOpt = 'C';
967 else if (strcmp(pszArg, "32-bit") == 0)
968 chOpt = '3';
969 else if (strcmp(pszArg, "64-bit") == 0)
970 chOpt = '6';
971 else if (strcmp(pszArg, "verbose") == 0)
972 chOpt = 'v';
973 else if (strcmp(pszArg, "executable") == 0)
974 chOpt = 'e';
975 else
976 {
977 errx(1, "Unknown option: '%s'", pszArg - 2);
978 return usage(stderr, argv[0]);
979 }
980 pszArg = "";
981 }
982
983 do
984 {
985 /* Get option value first, if the option takes one. */
986 const char *pszValue = NULL;
987 switch (chOpt)
988 {
989 case 'E':
990 case 'U':
991 case 'C':
992 case 'e':
993 if (*pszArg != '\0')
994 pszValue = pszArg + (*pszArg == ':' || *pszArg == '=');
995 else if (++iArg < argc)
996 pszValue = argv[iArg];
997 else
998 {
999 errx(1, "Option -%c requires an value!", chOpt);
1000 return usage(stderr, argv[0]);
1001 }
1002 break;
1003 }
1004
1005 switch (chOpt)
1006 {
1007 case 'Z':
1008 case 'i': /* GNU env compatibility. */
1009 for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
1010 free(papszEnv[iEnvVar]);
1011 papszEnv[0] = NULL;
1012 cEnvVars = 0;
1013 break;
1014
1015 case 'E':
1016 rcExit = kSubmitOptEnvSet(papszEnv, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
1017 pChild->environment = papszEnv;
1018 if (rcExit == 0)
1019 break;
1020 return rcExit;
1021
1022 case 'U':
1023 rcExit = kSubmitOptEnvUnset(papszEnv, &cEnvVars, cVerbosity, pszValue);
1024 if (rcExit == 0)
1025 break;
1026 return rcExit;
1027
1028 case 'C':
1029 rcExit = kSubmitOptChDir(szCwd, cbCwdBuf, pszValue);
1030 if (rcExit == 0)
1031 break;
1032 return rcExit;
1033
1034 case '3':
1035 cBitsWorker = 32;
1036 break;
1037
1038 case '6':
1039 cBitsWorker = 64;
1040 break;
1041
1042 case 'e':
1043 pszExecutable = pszValue;
1044 break;
1045
1046 case 'v':
1047 cVerbosity++;
1048 break;
1049
1050 case 'h':
1051 usage(stdout, argv[0]);
1052 return 0;
1053
1054 case 'V':
1055 printf("kmk_submit - kBuild version %d.%d.%d (r%u)\n"
1056 "Copyright (C) 2007-2016 knut st. osmundsen\n",
1057 KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH,
1058 KBUILD_SVN_REV);
1059 return 0;
1060 }
1061 } while ((chOpt = *pszArg++) != '\0');
1062 }
1063 else
1064 {
1065 errx(1, "Unknown argument: '%s'", pszArg);
1066 return usage(stderr, argv[0]);
1067 }
1068 }
1069
1070 /*
1071 * Check that we've got something to execute.
1072 */
1073 if (iArg < argc)
1074 {
1075 uint32_t cbMsg;
1076 void *pvMsg = kSubmitComposeJobMessage(pszExecutable, &argv[iArg], papszEnv, szCwd, &cbMsg);
1077 PWORKERINSTANCE pWorker = kSubmitSelectWorkSpawnNewIfNecessary(cBitsWorker, cVerbosity);
1078 if (pWorker)
1079 {
1080 rcExit = kSubmitSendJobMessage(pWorker, pvMsg, cbMsg, cVerbosity);
1081 if (rcExit == 0)
1082 {
1083 pWorker->pBusyWith = pChild;
1084 /** @todo integrate with sub_proc.c / whatever. */
1085 }
1086 }
1087 else
1088 rcExit = 1;
1089 free(pvMsg);
1090 }
1091 else
1092 {
1093 errx(1, "Nothing to executed!");
1094 rcExit = usage(stderr, argv[0]);
1095 }
1096
1097 return rcExit;
1098}
1099
1100
1101
Note: See TracBrowser for help on using the repository browser.