VirtualBox

source: kBuild/trunk/src/kmk/kmkbuiltin.c@ 3333

Last change on this file since 3333 was 3333, checked in by bird, 4 years ago

kmk/kmkbuiltin.c: Handle escaped end-of-line better in kmk_builtin_command.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.7 KB
Line 
1/* $Id: kmkbuiltin.c 3333 2020-04-20 23:55:38Z bird $ */
2/** @file
3 * kMk Builtin command execution.
4 */
5
6/*
7 * Copyright (c) 2005-2018 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/*********************************************************************************************************************************
28* Header Files *
29*********************************************************************************************************************************/
30#include <string.h>
31#include <stdlib.h>
32#include <stdio.h>
33#include <ctype.h>
34#include <assert.h>
35#include <sys/stat.h>
36#ifdef _MSC_VER
37# include <io.h>
38#endif
39
40#include "makeint.h"
41#include "job.h"
42#include "variable.h"
43#if defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
44# include "w32/winchildren.h"
45#endif
46#include "kmkbuiltin/err.h"
47#include "kmkbuiltin.h"
48
49#ifndef _MSC_VER
50extern char **environ;
51#endif
52
53
54/*********************************************************************************************************************************
55* Global Variables *
56*********************************************************************************************************************************/
57#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
58extern int print_stats_flag;
59#endif
60
61
62
63int kmk_builtin_command(const char *pszCmd, struct child *pChild, char ***ppapszArgvToSpawn, pid_t *pPidSpawned)
64{
65 int argc;
66 char **argv;
67 int rc;
68 char *pszzCmd;
69 char *pszDst;
70 int fOldStyle = 0;
71
72 /*
73 * Check and skip the prefix.
74 */
75 if (strncmp(pszCmd, "kmk_builtin_", sizeof("kmk_builtin_") - 1))
76 {
77 fprintf(stderr, "kmk_builtin: Invalid command prefix '%s'!\n", pszCmd);
78 return 1;
79 }
80
81 /*
82 * Parse arguments.
83 */
84 rc = 0;
85 argc = 0;
86 argv = NULL;
87 pszzCmd = pszDst = (char *)strdup(pszCmd);
88 if (!pszDst)
89 {
90 fprintf(stderr, "kmk_builtin: out of memory. argc=%d\n", argc);
91 return 1;
92 }
93 do
94 {
95 const char * const pszSrcStart = pszCmd;
96 char ch;
97 char chQuote;
98
99 /*
100 * Start new argument.
101 */
102 if (!(argc % 16))
103 {
104 void *pv = realloc(argv, sizeof(char *) * (argc + 17));
105 if (!pv)
106 {
107 fprintf(stderr, "kmk_builtin: out of memory. argc=%d\n", argc);
108 rc = 1;
109 break;
110 }
111 argv = (char **)pv;
112 }
113 argv[argc++] = pszDst;
114 argv[argc] = NULL;
115
116 if (!fOldStyle)
117 {
118 /*
119 * Process the next argument, bourne style.
120 */
121 chQuote = 0;
122 ch = *pszCmd++;
123 do
124 {
125 /* Unquoted mode? */
126 if (chQuote == 0)
127 {
128 if (ch != '\'' && ch != '"')
129 {
130 if (!isspace(ch))
131 {
132 if (ch != '\\')
133 *pszDst++ = ch;
134 else
135 {
136 ch = *pszCmd++;
137 if (ch == '\n') /* escaped end-of-line */
138 break;
139 if (ch == '\r' && *pszCmd == '\n') /* escaped end-of-line */
140 {
141 pszCmd++;
142 break;
143 }
144 if (ch)
145 *pszDst++ = ch;
146 else
147 {
148 fprintf(stderr, "kmk_builtin: Incomplete escape sequence in argument %d: %s\n",
149 argc, pszSrcStart);
150 rc = 1;
151 break;
152 }
153 }
154 }
155 else
156 break;
157 }
158 else
159 chQuote = ch;
160 }
161 /* Quoted mode */
162 else if (ch != chQuote)
163 {
164 if ( ch != '\\'
165 || chQuote == '\'')
166 *pszDst++ = ch;
167 else
168 {
169 ch = *pszCmd++;
170 if (ch)
171 {
172 if ( ch != '\\'
173 && ch != '"'
174 && ch != '`'
175 && ch != '$'
176 && ch != '\n')
177 *pszDst++ = '\\';
178 *pszDst++ = ch;
179 }
180 else
181 {
182 fprintf(stderr, "kmk_builtin: Unbalanced quote in argument %d: %s\n", argc, pszSrcStart);
183 rc = 1;
184 break;
185 }
186 }
187 }
188 else
189 chQuote = 0;
190 } while ((ch = *pszCmd++) != '\0');
191 }
192 else
193 {
194 /*
195 * Old style in case we ever need it.
196 */
197 ch = *pszCmd++;
198 if (ch != '"' && ch != '\'')
199 {
200 do
201 *pszDst++ = ch;
202 while ((ch = *pszCmd++) != '\0' && !isspace(ch));
203 }
204 else
205 {
206 chQuote = ch;
207 for (;;)
208 {
209 char *pszEnd = strchr(pszCmd, chQuote);
210 if (pszEnd)
211 {
212 fprintf(stderr, "kmk_builtin: Unbalanced quote in argument %d: %s\n", argc, pszSrcStart);
213 rc = 1;
214 break;
215 }
216 memcpy(pszDst, pszCmd, pszEnd - pszCmd);
217 pszDst += pszEnd - pszCmd;
218 if (pszEnd[1] != chQuote)
219 break;
220 *pszDst++ = chQuote;
221 }
222 }
223 }
224 *pszDst++ = '\0';
225
226 /*
227 * Skip argument separators (IFS=space() for now). Check for EOS.
228 */
229 if (ch != 0)
230 while ( (ch = *pszCmd)
231 && ( isspace(ch)
232 || (ch == '\\' && (pszCmd[1] == '\n' || (pszCmd[1] == '\r' && pszCmd[2] == '\n')))))
233 pszCmd++;
234 if (ch == 0)
235 break;
236 } while (rc == 0);
237
238 /*
239 * Execute the command if parsing was successful.
240 */
241 if (rc == 0)
242 rc = kmk_builtin_command_parsed(argc, argv, pChild, ppapszArgvToSpawn, pPidSpawned);
243
244 /* clean up and return. */
245 free(argv);
246 free(pszzCmd);
247 return rc;
248}
249
250
251/**
252 * kmk built command.
253 */
254static const KMKBUILTINENTRY g_aBuiltIns[] =
255{
256#define BUILTIN_ENTRY(a_fn, a_sz, a_uFnSignature, fMtSafe, fNeedEnv) \
257 { { { sizeof(a_sz) - 1, a_sz, } }, { (uintptr_t)a_fn }, a_uFnSignature, fMtSafe, fNeedEnv }
258
259 /* More frequently used commands: */
260 BUILTIN_ENTRY(kmk_builtin_append, "append", FN_SIG_MAIN_SPAWNS, 0, 0),
261 BUILTIN_ENTRY(kmk_builtin_printf, "printf", FN_SIG_MAIN, 0, 0),
262 BUILTIN_ENTRY(kmk_builtin_echo, "echo", FN_SIG_MAIN, 0, 0),
263 BUILTIN_ENTRY(kmk_builtin_install, "install", FN_SIG_MAIN, 1, 0),
264 BUILTIN_ENTRY(kmk_builtin_kDepObj, "kDepObj", FN_SIG_MAIN, 1, 0),
265#ifdef KBUILD_OS_WINDOWS
266 BUILTIN_ENTRY(kmk_builtin_kSubmit, "kSubmit", FN_SIG_MAIN_SPAWNS, 0, 1),
267#endif
268 BUILTIN_ENTRY(kmk_builtin_mkdir, "mkdir", FN_SIG_MAIN, 0, 0),
269 BUILTIN_ENTRY(kmk_builtin_mv, "mv", FN_SIG_MAIN, 0, 0),
270 BUILTIN_ENTRY(kmk_builtin_redirect, "redirect", FN_SIG_MAIN_SPAWNS, 1, 1),
271 BUILTIN_ENTRY(kmk_builtin_rm, "rm", FN_SIG_MAIN, 1, 1),
272 BUILTIN_ENTRY(kmk_builtin_rmdir, "rmdir", FN_SIG_MAIN, 0, 0),
273 BUILTIN_ENTRY(kmk_builtin_test, "test", FN_SIG_MAIN_TO_SPAWN, 0, 0),
274 /* Less frequently used commands: */
275 BUILTIN_ENTRY(kmk_builtin_kDepIDB, "kDepIDB", FN_SIG_MAIN, 0, 0),
276 BUILTIN_ENTRY(kmk_builtin_chmod, "chmod", FN_SIG_MAIN, 0, 0),
277 BUILTIN_ENTRY(kmk_builtin_cp, "cp", FN_SIG_MAIN, 1, 1),
278 BUILTIN_ENTRY(kmk_builtin_expr, "expr", FN_SIG_MAIN, 0, 0),
279 BUILTIN_ENTRY(kmk_builtin_ln, "ln", FN_SIG_MAIN, 0, 0),
280 BUILTIN_ENTRY(kmk_builtin_md5sum, "md5sum", FN_SIG_MAIN, 1, 0),
281 BUILTIN_ENTRY(kmk_builtin_cmp, "cmp", FN_SIG_MAIN, 0, 0),
282 BUILTIN_ENTRY(kmk_builtin_cat, "cat", FN_SIG_MAIN, 0, 0),
283 BUILTIN_ENTRY(kmk_builtin_touch, "touch", FN_SIG_MAIN, 0, 0),
284 BUILTIN_ENTRY(kmk_builtin_sleep, "sleep", FN_SIG_MAIN, 1, 0),
285 BUILTIN_ENTRY(kmk_builtin_dircache, "dircache", FN_SIG_MAIN, 0, 0),
286};
287
288#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
289/** Statistics running in parallel to g_aBuiltIns. */
290struct
291{
292 big_int cNs;
293 unsigned cTimes;
294 unsigned cAsyncTimes;
295} g_aBuiltInStats[sizeof(g_aBuiltIns) / sizeof(g_aBuiltIns[0])];
296#endif
297
298
299int kmk_builtin_command_parsed(int argc, char **argv, struct child *pChild, char ***ppapszArgvToSpawn, pid_t *pPidSpawned)
300{
301 /*
302 * Check and skip the prefix.
303 */
304 static const char s_szPrefix[] = "kmk_builtin_";
305 const char *pszCmd = argv[0];
306 if (strncmp(pszCmd, s_szPrefix, sizeof(s_szPrefix) - 1) == 0)
307 {
308 struct KMKBUILTINENTRY const *pEntry;
309 size_t cchAndStart;
310#if K_ENDIAN == K_ENDIAN_BIG
311 size_t cch;
312#endif
313 int cLeft;
314
315 pszCmd += sizeof(s_szPrefix) - 1;
316
317 /*
318 * Calc the length and start word to avoid calling memcmp/strcmp on each entry.
319 */
320#if K_ARCH_BITS != 64 && K_ARCH_BITS != 32
321# error "PORT ME!"
322#endif
323 cchAndStart = strlen(pszCmd);
324#if K_ENDIAN == K_ENDIAN_BIG
325 cch = cchAndStart;
326 cchAndStart <<= K_ARCH_BITS - 8;
327 switch (cch)
328 {
329 default: /* fall thru */
330# if K_ARCH_BITS >= 64
331 case 7: cchAndStart |= (size_t)pszCmd[6]; /* fall thru */
332 case 6: cchAndStart |= (size_t)pszCmd[5] << (K_ARCH_BITS - 56); /* fall thru */
333 case 5: cchAndStart |= (size_t)pszCmd[4] << (K_ARCH_BITS - 48); /* fall thru */
334 case 4: cchAndStart |= (size_t)pszCmd[3] << (K_ARCH_BITS - 40); /* fall thru */
335# endif
336 /* fall thru - gcc 8.2.0 is confused by # endif */
337 case 3: cchAndStart |= (size_t)pszCmd[2] << (K_ARCH_BITS - 32); /* fall thru */
338 case 2: cchAndStart |= (size_t)pszCmd[1] << (K_ARCH_BITS - 24); /* fall thru */
339 case 1: cchAndStart |= (size_t)pszCmd[0] << (K_ARCH_BITS - 16); /* fall thru */
340 case 0: break;
341 }
342#else
343 switch (cchAndStart)
344 {
345 default: /* fall thru */
346# if K_ARCH_BITS >= 64
347 case 7: cchAndStart |= (size_t)pszCmd[6] << 56; /* fall thru */
348 case 6: cchAndStart |= (size_t)pszCmd[5] << 48; /* fall thru */
349 case 5: cchAndStart |= (size_t)pszCmd[4] << 40; /* fall thru */
350 case 4: cchAndStart |= (size_t)pszCmd[3] << 32; /* fall thru */
351# endif
352 /* fall thru - gcc 8.2.0 is confused by # endif */
353 case 3: cchAndStart |= (size_t)pszCmd[2] << 24; /* fall thru */
354 case 2: cchAndStart |= (size_t)pszCmd[1] << 16; /* fall thru */
355 case 1: cchAndStart |= (size_t)pszCmd[0] << 8; /* fall thru */
356 case 0: break;
357 }
358#endif
359
360 /*
361 * Look up the builtin command in the table.
362 */
363 pEntry = &g_aBuiltIns[0];
364 cLeft = sizeof(g_aBuiltIns) / sizeof(g_aBuiltIns[0]);
365 while (cLeft-- > 0)
366 if ( pEntry->uName.cchAndStart != cchAndStart
367 || ( pEntry->uName.s.cch >= sizeof(cchAndStart)
368 && memcmp(pEntry->uName.s.sz, pszCmd, pEntry->uName.s.cch) != 0) )
369 pEntry++;
370 else
371 {
372 /*
373 * That's a match!
374 *
375 * First get the environment if it is actually needed. This is
376 * especially important when we run on a worker thread as it must
377 * not under any circumstances do stuff like target_environment.
378 */
379 int rc;
380 char **papszEnvVars = NULL;
381 if (pEntry->fNeedEnv)
382 {
383 papszEnvVars = pChild->environment;
384 if (!papszEnvVars)
385 pChild->environment = papszEnvVars = target_environment(pChild->file);
386 }
387
388#if defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
389 /*
390 * If the built-in is multi thread safe, we will run it on a job slot thread.
391 */
392 if (pEntry->fMtSafe)
393 {
394 rc = MkWinChildCreateBuiltIn(pEntry, argc, argv, papszEnvVars, pChild, pPidSpawned);
395# ifdef CONFIG_WITH_KMK_BUILTIN_STATS
396 g_aBuiltInStats[pEntry - &g_aBuiltIns[0]].cAsyncTimes++;
397# endif
398 }
399 else
400#endif
401 {
402 /*
403 * Call the worker function, making sure to preserve umask.
404 */
405#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
406 big_int nsStart = print_stats_flag ? nano_timestamp() : 0;
407#endif
408 KMKBUILTINCTX Ctx;
409 int const iUmask = umask(0); /* save umask */
410 umask(iUmask);
411
412 Ctx.pszProgName = pEntry->uName.s.sz;
413 Ctx.pOut = pChild ? &pChild->output : NULL;
414
415 if (pEntry->uFnSignature == FN_SIG_MAIN)
416 rc = pEntry->u.pfnMain(argc, argv, papszEnvVars, &Ctx);
417 else if (pEntry->uFnSignature == FN_SIG_MAIN_SPAWNS)
418 rc = pEntry->u.pfnMainSpawns(argc, argv, papszEnvVars, &Ctx, pChild, pPidSpawned);
419 else if (pEntry->uFnSignature == FN_SIG_MAIN_TO_SPAWN)
420 {
421 /*
422 * When we got something to execute, check if the child is a kmk_builtin thing.
423 * We recurse here, both because I'm lazy and because it's easier to debug a
424 * problem then (the call stack shows what's been going on).
425 */
426 rc = pEntry->u.pfnMainToSpawn(argc, argv, papszEnvVars, &Ctx, ppapszArgvToSpawn);
427 if ( !rc
428 && *ppapszArgvToSpawn
429 && !strncmp(**ppapszArgvToSpawn, s_szPrefix, sizeof(s_szPrefix) - 1))
430 {
431 char **argv_new = *ppapszArgvToSpawn;
432 int argc_new = 1;
433 while (argv_new[argc_new])
434 argc_new++;
435
436 assert(argv_new[0] != argv[0]);
437 assert(!*pPidSpawned);
438
439 *ppapszArgvToSpawn = NULL;
440 rc = kmk_builtin_command_parsed(argc_new, argv_new, pChild, ppapszArgvToSpawn, pPidSpawned);
441
442 free(argv_new[0]);
443 free(argv_new);
444 }
445 }
446 else
447 rc = 99;
448
449 umask(iUmask); /* restore it */
450
451#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
452 if (print_stats_flag)
453 {
454 uintptr_t iEntry = pEntry - &g_aBuiltIns[0];
455 g_aBuiltInStats[iEntry].cTimes++;
456 g_aBuiltInStats[iEntry].cNs += nano_timestamp() - nsStart;
457 }
458#endif
459 }
460 return rc;
461 }
462
463 /*
464 * No match! :-(
465 */
466 fprintf(stderr, "kmk_builtin: Unknown command '%s%s'!\n", s_szPrefix, pszCmd);
467 }
468 else
469 fprintf(stderr, "kmk_builtin: Invalid command prefix '%s'!\n", pszCmd);
470 return 1;
471}
472
473#ifndef KBUILD_OS_WINDOWS
474/** Dummy. */
475int kmk_builtin_dircache(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
476{
477 (void)argc; (void)argv; (void)envp; (void)pCtx;
478 return 0;
479}
480#endif
481
482#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
483/**
484 * Prints the statistiscs to the given output stream.
485 */
486extern void kmk_builtin_print_stats(FILE *pOutput, const char *pszPrefix)
487{
488 const unsigned cEntries = sizeof(g_aBuiltInStats) / sizeof(g_aBuiltInStats[0]);
489 unsigned i;
490 assert(print_stats_flag);
491 fprintf(pOutput, "\n%skmk built-in command statistics:\n", pszPrefix);
492 for (i = 0; i < cEntries; i++)
493 if (g_aBuiltInStats[i].cTimes > 0)
494 {
495 char szTotal[64];
496 char szAvg[64];
497 format_elapsed_nano(szTotal, sizeof(szTotal), g_aBuiltInStats[i].cNs);
498 format_elapsed_nano(szAvg, sizeof(szAvg), g_aBuiltInStats[i].cNs / g_aBuiltInStats[i].cTimes);
499 fprintf(pOutput, "%s kmk_builtin_%-9s: %4u times, %9s total, %9s/call\n",
500 pszPrefix, g_aBuiltIns[i].uName.s.sz, g_aBuiltInStats[i].cTimes, szTotal, szAvg);
501 }
502 else if (g_aBuiltInStats[i].cAsyncTimes > 0)
503 fprintf(pOutput, "%s kmk_builtin_%-9s: %4u times in worker thread\n",
504 pszPrefix, g_aBuiltIns[i].uName.s.sz, g_aBuiltInStats[i].cAsyncTimes);
505}
506#endif
507
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use