source: trunk/src/kmk/kmkbuiltin.c@ 3170

Last change on this file since 3170 was 3170, checked in by bird, 7 years ago

kmkbuiltin: stats

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