source: trunk/src/kObjCache/kObjCache.c@ 1041

Last change on this file since 1041 was 1041, checked in by bird, 18 years ago

memset not memcmp.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 107.7 KB
Line 
1/* $Id: kObjCache.c 1041 2007-06-08 18:36:57Z bird $ */
2/** @file
3 *
4 * kObjCache - Object Cache.
5 *
6 * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net>
7 *
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 2 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, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include <string.h>
32#include <stdlib.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <errno.h>
36#include <assert.h>
37#include <sys/stat.h>
38#include <fcntl.h>
39#include <limits.h>
40#include <ctype.h>
41#ifndef PATH_MAX
42# define PATH_MAX _MAX_PATH /* windows */
43#endif
44#if defined(__OS2__) || defined(__WIN__)
45# include <process.h>
46# include <io.h>
47# ifdef __OS2__
48# include <unistd.h>
49# endif
50# if defined(_MSC_VER)
51 typedef intptr_t pid_t;
52# endif
53#else
54# include <unistd.h>
55# include <sys/wait.h>
56# ifndef O_BINARY
57# define O_BINARY 0
58# endif
59#endif
60#if defined(__WIN__)
61# include <Windows.h>
62#endif
63
64#include "crc32.h"
65#include "md5.h"
66
67
68/*******************************************************************************
69* Defined Constants And Macros *
70*******************************************************************************/
71/** The max line length in a cache file. */
72#define KOBJCACHE_MAX_LINE_LEN 16384
73#if defined(__WIN__)
74# define PATH_SLASH '\\'
75#else
76# define PATH_SLASH '/'
77#endif
78#if defined(__OS2__) || defined(__WIN__)
79# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
80# define IS_SLASH_DRV(ch) ((ch) == '/' || (ch) == '\\' || (ch) == ':')
81#else
82# define IS_SLASH(ch) ((ch) == '/')
83# define IS_SLASH_DRV(ch) ((ch) == '/')
84#endif
85
86
87/*******************************************************************************
88* Global Variables *
89*******************************************************************************/
90/** Whether verbose output is enabled. */
91static unsigned g_cVerbosityLevel = 0;
92/** What to prefix the errors with. */
93static char g_szErrorPrefix[128];
94
95/** Read buffer shared by the cache components. */
96static char g_szLine[KOBJCACHE_MAX_LINE_LEN + 16];
97
98
99/*******************************************************************************
100* Internal Functions *
101*******************************************************************************/
102static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir);
103static char *CalcRelativeName(const char *pszPath, const char *pszDir);
104static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode);
105static int UnlinkFileInDir(const char *pszName, const char *pszDir);
106static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir);
107static int DoesFileInDirExist(const char *pszName, const char *pszDir);
108static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile);
109
110
111void FatalMsg(const char *pszFormat, ...)
112{
113 va_list va;
114
115 if (g_szErrorPrefix[0])
116 fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix);
117 else
118 fprintf(stderr, "fatal error: ");
119
120 va_start(va, pszFormat);
121 vfprintf(stderr, pszFormat, va);
122 va_end(va);
123}
124
125
126void FatalDie(const char *pszFormat, ...)
127{
128 va_list va;
129
130 if (g_szErrorPrefix[0])
131 fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix);
132 else
133 fprintf(stderr, "fatal error: ");
134
135 va_start(va, pszFormat);
136 vfprintf(stderr, pszFormat, va);
137 va_end(va);
138
139 exit(1);
140}
141
142
143static void ErrorMsg(const char *pszFormat, ...)
144{
145 va_list va;
146
147 if (g_szErrorPrefix[0])
148 fprintf(stderr, "%s - error: ", g_szErrorPrefix);
149 else
150 fprintf(stderr, "error: ");
151
152 va_start(va, pszFormat);
153 vfprintf(stderr, pszFormat, va);
154 va_end(va);
155}
156
157
158static void InfoMsg(unsigned uLevel, const char *pszFormat, ...)
159{
160 if (uLevel <= g_cVerbosityLevel)
161 {
162 va_list va;
163
164 if (g_szErrorPrefix[0])
165 fprintf(stderr, "%s - info: ", g_szErrorPrefix);
166 else
167 fprintf(stderr, "info: ");
168
169 va_start(va, pszFormat);
170 vfprintf(stderr, pszFormat, va);
171 va_end(va);
172 }
173}
174
175
176static void SetErrorPrefix(const char *pszPrefix, ...)
177{
178 int cch;
179 va_list va;
180
181 va_start(va, pszPrefix);
182#if defined(_MSC_VER) || defined(__sun__)
183 cch = vsprintf(g_szErrorPrefix, pszPrefix, va);
184 if (cch >= sizeof(g_szErrorPrefix))
185 FatalDie("Buffer overflow setting error prefix!\n");
186#else
187 vsnprintf(g_szErrorPrefix, sizeof(g_szErrorPrefix), pszPrefix, va);
188#endif
189 va_end(va);
190 (void)cch;
191}
192
193
194void *xmalloc(size_t cb)
195{
196 void *pv = malloc(cb);
197 if (!pv)
198 FatalDie(NULL, "out of memory (%d)\n", (int)cb);
199 return pv;
200}
201
202
203void *xmallocz(size_t cb)
204{
205 void *pv = xmalloc(cb);
206 memset(pv, 0, cb);
207 return pv;
208}
209
210
211void *xrealloc(void *pvOld, size_t cb)
212{
213 void *pv = realloc(pvOld, cb);
214 if (!pv)
215 FatalDie(NULL, "out of memory (%d)\n", (int)cb);
216 return pv;
217}
218
219
220char *xstrdup(const char *pszIn)
221{
222 char *psz = strdup(pszIn);
223 if (!psz)
224 FatalDie(NULL, "out of memory (%d)\n", (int)strlen(pszIn));
225 return psz;
226}
227
228
229
230/**
231 * Gets the absolute path
232 *
233 * @returns A new heap buffer containing the absolute path.
234 * @param pszPath The path to make absolute. (Readonly)
235 */
236static char *AbsPath(const char *pszPath)
237{
238 char szTmp[PATH_MAX];
239#if defined(__OS2__) || defined(__WIN__)
240 if (!_fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp)))
241 return xstrdup(pszPath);
242#else
243 if (!realpath(pszPath, szTmp))
244 return xstrdup(pszPath);
245#endif
246 return xstrdup(szTmp);
247}
248
249
250/**
251 * Utility function that finds the filename part in a path.
252 *
253 * @returns Pointer to the file name part (this may be "").
254 * @param pszPath The path to parse.
255 */
256static const char *FindFilenameInPath(const char *pszPath)
257{
258 const char *pszFilename = strchr(pszPath, '\0') - 1;
259 while ( pszFilename > pszPath
260 && !IS_SLASH_DRV(pszFilename[-1]))
261 pszFilename--;
262 return pszFilename;
263}
264
265
266/**
267 * Utility function that combines a filename and a directory into a path.
268 *
269 * @returns malloced buffer containing the result.
270 * @param pszName The file name.
271 * @param pszDir The directory path.
272 */
273static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir)
274{
275 size_t cchName = strlen(pszName);
276 size_t cchDir = strlen(pszDir);
277 char *pszBuf = xmalloc(cchName + cchDir + 2);
278 memcpy(pszBuf, pszDir, cchDir);
279 if (cchDir > 0 && !IS_SLASH_DRV(pszDir[cchDir - 1]))
280 pszBuf[cchDir++] = PATH_SLASH;
281 memcpy(pszBuf + cchDir, pszName, cchName + 1);
282 return pszBuf;
283}
284
285
286/**
287 * Compares two path strings to see if they are identical.
288 *
289 * This doesn't do anything fancy, just the case ignoring and
290 * slash unification.
291 *
292 * @returns 1 if equal, 0 otherwise.
293 * @param pszPath1 The first path.
294 * @param pszPath2 The second path.
295 * @param cch The number of characters to compare.
296 */
297static int ArePathsIdentical(const char *pszPath1, const char *pszPath2, size_t cch)
298{
299#if defined(__OS2__) || defined(__WIN__)
300 if (strnicmp(pszPath1, pszPath2, cch))
301 {
302 /* Slashes may differ, compare char by char. */
303 const char *psz1 = pszPath1;
304 const char *psz2 = pszPath2;
305 for (;cch; psz1++, psz2++, cch--)
306 {
307 if (*psz1 != *psz2)
308 {
309 if ( tolower(*psz1) != tolower(*psz2)
310 && toupper(*psz1) != toupper(*psz2)
311 && *psz1 != '/'
312 && *psz1 != '\\'
313 && *psz2 != '/'
314 && *psz2 != '\\')
315 return 0;
316 }
317 }
318 }
319 return 1;
320#else
321 return !strncmp(pszPath1, pszPath2, cch);
322#endif
323}
324
325
326/**
327 * Calculate how to get to pszPath from pszDir.
328 *
329 * @returns The relative path from pszDir to path pszPath.
330 * @param pszPath The path to the object.
331 * @param pszDir The directory it shall be relative to.
332 */
333static char *CalcRelativeName(const char *pszPath, const char *pszDir)
334{
335 char *pszRet = NULL;
336 char *pszAbsPath = NULL;
337 size_t cchDir = strlen(pszDir);
338
339 /*
340 * This is indeed a bit tricky, so we'll try the easy way first...
341 */
342 if (ArePathsIdentical(pszPath, pszDir, cchDir))
343 {
344 if (pszPath[cchDir])
345 pszRet = (char *)pszPath + cchDir;
346 else
347 pszRet = "./";
348 }
349 else
350 {
351 pszAbsPath = AbsPath(pszPath);
352 if (ArePathsIdentical(pszAbsPath, pszDir, cchDir))
353 {
354 if (pszPath[cchDir])
355 pszRet = pszAbsPath + cchDir;
356 else
357 pszRet = "./";
358 }
359 }
360 if (pszRet)
361 {
362 while (IS_SLASH_DRV(*pszRet))
363 pszRet++;
364 pszRet = xstrdup(pszRet);
365 free(pszAbsPath);
366 return pszRet;
367 }
368
369 /*
370 * Damn, it's gonna be complicated. Deal with that later.
371 */
372 FatalDie("complicated relative path stuff isn't implemented yet. sorry.\n");
373 return NULL;
374}
375
376
377/**
378 * Utility function that combines a filename and directory and passes it onto fopen.
379 *
380 * @returns fopen return value.
381 * @param pszName The file name.
382 * @param pszDir The directory path.
383 * @param pszMode The fopen mode string.
384 */
385static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode)
386{
387 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
388 FILE *pFile = fopen(pszPath, pszMode);
389 free(pszPath);
390 return pFile;
391}
392
393
394/**
395 * Utility function that combines a filename and directory and passes it onto open.
396 *
397 * @returns open return value.
398 * @param pszName The file name.
399 * @param pszDir The directory path.
400 * @param fFlags The open flags.
401 * @param fCreateMode The file creation mode.
402 */
403static int OpenFileInDir(const char *pszName, const char *pszDir, int fFlags, int fCreateMode)
404{
405 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
406 int fd = open(pszPath, fFlags, fCreateMode);
407 free(pszPath);
408 return fd;
409}
410
411
412
413/**
414 * Deletes a file in a directory.
415 *
416 * @returns whatever unlink returns.
417 * @param pszName The file name.
418 * @param pszDir The directory path.
419 */
420static int UnlinkFileInDir(const char *pszName, const char *pszDir)
421{
422 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
423 int rc = unlink(pszPath);
424 free(pszPath);
425 return rc;
426}
427
428
429/**
430 * Renames a file in a directory.
431 *
432 * @returns whatever rename returns.
433 * @param pszOldName The new file name.
434 * @param pszNewName The old file name.
435 * @param pszDir The directory path.
436 */
437static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir)
438{
439 char *pszOldPath = MakePathFromDirAndFile(pszOldName, pszDir);
440 char *pszNewPath = MakePathFromDirAndFile(pszNewName, pszDir);
441 int rc = rename(pszOldPath, pszNewPath);
442 free(pszOldPath);
443 free(pszNewPath);
444 return rc;
445}
446
447
448/**
449 * Check if a (regular) file exists in a directory.
450 *
451 * @returns 1 if it exists and is a regular file, 0 if not.
452 * @param pszName The file name.
453 * @param pszDir The directory path.
454 */
455static int DoesFileInDirExist(const char *pszName, const char *pszDir)
456{
457 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
458 struct stat st;
459 int rc = stat(pszPath, &st);
460 free(pszPath);
461#ifdef S_ISREG
462 return !rc && S_ISREG(st.st_mode);
463#elif defined(_MSC_VER)
464 return !rc && (st.st_mode & _S_IFMT) == _S_IFREG;
465#else
466#error "Port me"
467#endif
468}
469
470
471/**
472 * Reads into memory an entire file.
473 *
474 * @returns Pointer to the heap allocation containing the file.
475 * On failure NULL and errno is returned.
476 * @param pszName The file.
477 * @param pszDir The directory the file resides in.
478 * @param pcbFile Where to store the file size.
479 */
480static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile)
481{
482 int SavedErrno;
483 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
484 int fd = open(pszPath, O_RDONLY | O_BINARY);
485 if (fd >= 0)
486 {
487 off_t cbFile = lseek(fd, 0, SEEK_END);
488 if ( cbFile >= 0
489 && lseek(fd, 0, SEEK_SET) == 0)
490 {
491 char *pb = malloc(cbFile + 1);
492 if (pb)
493 {
494 if (read(fd, pb, cbFile) == cbFile)
495 {
496 close(fd);
497 pb[cbFile] = '\0';
498 *pcbFile = (size_t)cbFile;
499 return pb;
500 }
501 SavedErrno = errno;
502 free(pb);
503 }
504 else
505 SavedErrno = ENOMEM;
506 }
507 else
508 SavedErrno = errno;
509 close(fd);
510 }
511 else
512 SavedErrno = errno;
513 free(pszPath);
514 errno = SavedErrno;
515 return NULL;
516}
517
518
519/**
520 * Creates a directory including all necessary parent directories.
521 *
522 * @returns 0 on success, -1 + errno on failure.
523 * @param pszDir The directory.
524 */
525static int MakePath(const char *pszPath)
526{
527 /** @todo implement me */
528 return 0;
529}
530
531
532/**
533 * Adds the arguments found in the pszCmdLine string to argument vector.
534 *
535 * The parsing of the pszCmdLine string isn't very sophisticated, no
536 * escaping or quotes.
537 *
538 * @param pcArgs Pointer to the argument counter.
539 * @param ppapszArgs Pointer to the argument vector pointer.
540 * @param pszCmdLine The command line to parse and append.
541 * @param pszWedgeArg Argument to put infront of anything found in pszCmdLine.
542 */
543static void AppendArgs(int *pcArgs, char ***ppapszArgs, const char *pszCmdLine, const char *pszWedgeArg)
544{
545 int i;
546 int cExtraArgs;
547 const char *psz;
548 char **papszArgs;
549
550 /*
551 * Count the new arguments.
552 */
553 cExtraArgs = 0;
554 psz = pszCmdLine;
555 while (*psz)
556 {
557 while (isspace(*psz))
558 psz++;
559 if (!psz)
560 break;
561 cExtraArgs++;
562 while (!isspace(*psz) && *psz)
563 psz++;
564 }
565 if (!cExtraArgs)
566 return;
567
568 /*
569 * Allocate a new vector that can hold the arguments.
570 * (Reallocating might not work since the argv might not be allocated
571 * from the heap but off the stack or somewhere... )
572 */
573 i = *pcArgs;
574 *pcArgs = i + cExtraArgs + 1 + !!pszWedgeArg;
575 papszArgs = xmalloc(*pcArgs * sizeof(char *));
576 *ppapszArgs = memcpy(papszArgs, *ppapszArgs, i * sizeof(char *));
577
578 if (pszWedgeArg)
579 papszArgs[i++] = xstrdup(pszWedgeArg);
580
581 psz = pszCmdLine;
582 while (*psz)
583 {
584 const char *pszEnd;
585 while (isspace(*psz))
586 psz++;
587 if (!psz)
588 break;
589 pszEnd = psz;
590 while (!isspace(*pszEnd) && *pszEnd)
591 pszEnd++;
592
593 papszArgs[i] = xmalloc(psz - pszEnd + 1);
594 memcpy(papszArgs[i], psz, psz - pszEnd);
595 papszArgs[i][psz - pszEnd] = '\0';
596 i++;
597 }
598}
599
600
601
602
603
604/** A checksum list entry.
605 * We keep a list checksums (of precompiler output) that matches, The planned
606 * matching algorithm doesn't require the precompiler output to be indentical,
607 * only to produce the same object files.
608 */
609typedef struct KOCSUM
610{
611 /** The next checksum. */
612 struct KOCSUM *pNext;
613 /** The crc32 checksum. */
614 uint32_t crc32;
615 /** The MD5 digest. */
616 unsigned char md5[16];
617 /** Valid or not. */
618 unsigned fUsed;
619} KOCSUM;
620/** Pointer to a KOCSUM. */
621typedef KOCSUM *PKOCSUM;
622/** Pointer to a const KOCSUM. */
623typedef const KOCSUM *PCKOCSUM;
624
625
626/**
627 * Temporary context record used when calculating
628 * the checksum of some data.
629 */
630typedef struct KOCSUMCTX
631{
632 /** The MD5 context. */
633 struct MD5Context MD5Ctx;
634} KOCSUMCTX;
635/** Pointer to a check context record. */
636typedef KOCSUMCTX *PKOCSUMCTX;
637
638
639
640/**
641 * Initializes a checksum object with an associated context.
642 *
643 * @param pSum The checksum object.
644 * @param pCtx The checksum context.
645 */
646static void kOCSumInitWithCtx(PKOCSUM pSum, PKOCSUMCTX pCtx)
647{
648 memset(pSum, 0, sizeof(*pSum));
649 MD5Init(&pCtx->MD5Ctx);
650}
651
652
653/**
654 * Updates the checksum calculation.
655 *
656 * @param pSum The checksum.
657 * @param pCtx The checksum calcuation context.
658 * @param pvBuf The input data to checksum.
659 * @param cbBuf The size of the input data.
660 */
661static void kOCSumUpdate(PKOCSUM pSum, PKOCSUMCTX pCtx, const void *pvBuf, size_t cbBuf)
662{
663 /*
664 * Take in relativly small chunks to try keep it in the cache.
665 */
666 const unsigned char *pb = (const unsigned char *)pvBuf;
667 while (cbBuf > 0)
668 {
669 size_t cb = cbBuf >= 128*1024 ? 128*1024 : cbBuf;
670 pSum->crc32 = crc32(pSum->crc32, pb, cb);
671 MD5Update(&pCtx->MD5Ctx, pb, cb);
672 cbBuf -= cb;
673 }
674}
675
676
677/**
678 * Finalizes a checksum calculation.
679 *
680 * @param pSum The checksum.
681 * @param pCtx The checksum calcuation context.
682 */
683static void kOCSumFinalize(PKOCSUM pSum, PKOCSUMCTX pCtx)
684{
685 MD5Final(&pSum->md5[0], &pCtx->MD5Ctx);
686}
687
688
689/**
690 * Init a check sum chain head.
691 *
692 * @param pSumHead The checksum head to init.
693 */
694static void kOCSumInit(PKOCSUM pSumHead)
695{
696 memset(pSumHead, 0, sizeof(*pSumHead));
697}
698
699
700/**
701 * Parses the given string into a checksum head object.
702 *
703 * @returns 0 on success, -1 on format error.
704 * @param pSumHead The checksum head to init.
705 * @param pszVal The string to initialized it from.
706 */
707static int kOCSumInitFromString(PKOCSUM pSumHead, const char *pszVal)
708{
709 unsigned i;
710 char *pszNext;
711 char *pszMD5;
712
713 memset(pSumHead, 0, sizeof(*pSumHead));
714
715 pszMD5 = strchr(pszVal, ':');
716 if (pszMD5 == NULL)
717 return -1;
718 *pszMD5++ = '\0';
719
720 /* crc32 */
721 pSumHead->crc32 = (uint32_t)strtoul(pszVal, &pszNext, 16);
722 if (pszNext && *pszNext)
723 return -1;
724
725 /* md5 */
726 for (i = 0; i < sizeof(pSumHead->md5) * 2; i++)
727 {
728 unsigned char ch = pszMD5[i];
729 int x;
730 if ((unsigned char)(ch - '0') <= 9)
731 x = ch - '0';
732 else if ((unsigned char)(ch - 'a') <= 5)
733 x = ch - 'a' + 10;
734 else if ((unsigned char)(ch - 'A') <= 5)
735 x = ch - 'A' + 10;
736 else
737 return -1;
738 if (!(i & 1))
739 pSumHead->md5[i >> 1] = x << 4;
740 else
741 pSumHead->md5[i >> 1] |= x;
742 }
743
744 pSumHead->fUsed = 1;
745 return 0;
746}
747
748
749/**
750 * Delete a check sum chain.
751 *
752 * @param pSumHead The head of the checksum chain.
753 */
754static void kOCSumDeleteChain(PKOCSUM pSumHead)
755{
756 void *pv;
757 while ((pv = pSumHead->pNext))
758 {
759 pSumHead = pSumHead->pNext;
760 free(pv);
761 }
762 memset(pSumHead, 0, sizeof(*pSumHead));
763}
764
765
766/**
767 * Insert a check sum into the chain.
768 *
769 * @param pSumHead The head of the checksum list.
770 * @param pSumAdd The checksum to add (duplicate).
771 */
772static void kOCSumAdd(PKOCSUM pSumHead, PCKOCSUM pSumAdd)
773{
774 if (pSumHead->fUsed)
775 {
776 PKOCSUM pNew = xmalloc(sizeof(*pNew));
777 *pNew = *pSumAdd;
778 pNew->pNext = pSumHead->pNext;
779 pNew->fUsed = 1;
780 pSumHead->pNext = pNew;
781 }
782 else
783 {
784 *pSumHead = *pSumAdd;
785 pSumHead->pNext = NULL;
786 pSumHead->fUsed = 1;
787 }
788}
789
790
791/**
792 * Inserts an entrie chain into the given check sum chain.
793 *
794 * @param pSumHead The head of the checksum list.
795 * @param pSumHeadAdd The head of the checksum list to be added.
796 */
797static void kOCSumAddChain(PKOCSUM pSumHead, PCKOCSUM pSumHeadAdd)
798{
799 while (pSumHeadAdd)
800 {
801 kOCSumAdd(pSumHead, pSumHeadAdd);
802 pSumHeadAdd = pSumHeadAdd->pNext;
803 }
804}
805
806
807
808/**
809 * Prints the checksum to the specified stream.
810 *
811 * @param pSum The checksum.
812 * @param pFile The output file stream
813 */
814static void kOCSumFPrintf(PCKOCSUM pSum, FILE *pFile)
815{
816 fprintf(pFile, "%#x:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
817 pSum->crc32,
818 pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3],
819 pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7],
820 pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11],
821 pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]);
822}
823
824
825/**
826 * Displays the checksum (not chain!) using the InfoMsg() method.
827 *
828 * @param pSum The checksum.
829 * @param uLevel The info message level.
830 * @param pszMsg Message to prefix the info message with.
831 */
832static void kOCSumInfo(PCKOCSUM pSum, unsigned uLevel, const char *pszMsg)
833{
834 InfoMsg(uLevel,
835 "%s: crc32=%#010x md5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
836 pszMsg,
837 pSum->crc32,
838 pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3],
839 pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7],
840 pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11],
841 pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]);
842}
843
844
845/**
846 * Compares two check sum entries.
847 *
848 * @returns 1 if equal, 0 if not equal.
849 *
850 * @param pSum1 The first checksum.
851 * @param pSum2 The second checksum.
852 */
853static int kOCSumIsEqual(PCKOCSUM pSum1, PCKOCSUM pSum2)
854{
855 if (pSum1 == pSum2)
856 return 1;
857 if (!pSum1 || !pSum2)
858 return 0;
859 if (pSum1->crc32 != pSum2->crc32)
860 return 0;
861 if (memcmp(&pSum1->md5[0], &pSum2->md5[0], sizeof(pSum1->md5)))
862 return 0;
863 return 1;
864}
865
866
867/**
868 * Checks if the specified checksum equals one of the
869 * checksums in the chain.
870 *
871 * @returns 1 if equals one of them, 0 if not.
872 *
873 * @param pSumHead The checksum chain too look in.
874 * @param pSum The checksum to look for.
875 * @todo ugly name. fix.
876 */
877static int kOCSumHasEqualInChain(PCKOCSUM pSumHead, PCKOCSUM pSum)
878{
879 for (; pSumHead; pSumHead = pSumHead->pNext)
880 {
881 if (pSumHead == pSum)
882 return 1;
883 if (pSumHead->crc32 != pSum->crc32)
884 continue;
885 if (memcmp(&pSumHead->md5[0], &pSum->md5[0], sizeof(pSumHead->md5)))
886 continue;
887 return 1;
888 }
889 return 0;
890}
891
892
893/**
894 * Checks if the checksum (chain) empty.
895 *
896 * @returns 1 if empty, 0 if it there is one or more checksums.
897 * @param pSum The checksum to test.
898 */
899static int kOCSumIsEmpty(PCKOCSUM pSum)
900{
901 return !pSum->fUsed;
902}
903
904
905
906
907
908
909/**
910 * The representation of a cache entry.
911 */
912typedef struct KOCENTRY
913{
914 /** The name of the cache entry. */
915 const char *pszName;
916 /** The dir that all other names are relative to. */
917 char *pszDir;
918 /** The absolute path. */
919 char *pszAbsPath;
920 /** Set if the object needs to be (re)compiled. */
921 unsigned fNeedCompiling;
922 /** Whether the precompiler runs in piped mode. If clear it's file
923 * mode (it could be redirected stdout, but that's essentially the
924 * same from our point of view). */
925 unsigned fPipedPreComp;
926 /** Whether the compiler runs in piped mode (precompiler output on stdin). */
927 unsigned fPipedCompile;
928 /** Cache entry key that's used for some quick digest validation. */
929 uint32_t uKey;
930
931 /** The file data. */
932 struct KOCENTRYDATA
933 {
934 /** The name of file containing the precompiler output. */
935 char *pszCppName;
936 /** Pointer to the precompiler output. */
937 char *pszCppMapping;
938 /** The size of the precompiler output. 0 if not determined. */
939 size_t cbCpp;
940 /** The precompiler output checksums that will produce the cached object. */
941 KOCSUM SumHead;
942 /** The object filename (relative to the cache file). */
943 char *pszObjName;
944 /** The compile argument vector used to build the object. */
945 char **papszArgvCompile;
946 /** The size of the compile */
947 unsigned cArgvCompile;
948 /** The checksum of the compiler argument vector. */
949 KOCSUM SumCompArgv;
950 /** The target os/arch identifier. */
951 char *pszTarget;
952 }
953 /** The old data.*/
954 Old,
955 /** The new data. */
956 New;
957} KOCENTRY;
958/** Pointer to a KOCENTRY. */
959typedef KOCENTRY *PKOCENTRY;
960/** Pointer to a const KOCENTRY. */
961typedef const KOCENTRY *PCKOCENTRY;
962
963
964/**
965 * Creates a cache entry for the given cache file name.
966 *
967 * @returns Pointer to a cache entry.
968 * @param pszFilename The cache file name.
969 */
970static PKOCENTRY kOCEntryCreate(const char *pszFilename)
971{
972 PKOCENTRY pEntry;
973 size_t off;
974
975 /*
976 * Allocate an empty entry.
977 */
978 pEntry = xmallocz(sizeof(*pEntry));
979
980 kOCSumInit(&pEntry->New.SumHead);
981 kOCSumInit(&pEntry->Old.SumHead);
982
983 kOCSumInit(&pEntry->New.SumCompArgv);
984 kOCSumInit(&pEntry->Old.SumCompArgv);
985
986 /*
987 * Setup the directory and cache file name.
988 */
989 pEntry->pszAbsPath = AbsPath(pszFilename);
990 pEntry->pszName = FindFilenameInPath(pEntry->pszAbsPath);
991 off = pEntry->pszName - pEntry->pszAbsPath;
992 if (!off)
993 FatalDie("Failed to find abs path for '%s'!\n", pszFilename);
994 pEntry->pszDir = xmalloc(off - 1);
995 memcpy(pEntry->pszDir, pEntry->pszAbsPath, off);
996 pEntry->pszDir[off - 1] = '\0';
997
998 return pEntry;
999}
1000
1001
1002/**
1003 * Destroys the cache entry freeing up all it's resources.
1004 *
1005 * @param pEntry The entry to free.
1006 */
1007static void kOCEntryDestroy(PKOCENTRY pEntry)
1008{
1009 free(pEntry->pszDir);
1010 free(pEntry->pszAbsPath);
1011
1012 kOCSumDeleteChain(&pEntry->New.SumHead);
1013 kOCSumDeleteChain(&pEntry->Old.SumHead);
1014
1015 kOCSumDeleteChain(&pEntry->New.SumCompArgv);
1016 kOCSumDeleteChain(&pEntry->Old.SumCompArgv);
1017
1018 free(pEntry->New.pszCppName);
1019 free(pEntry->Old.pszCppName);
1020
1021 free(pEntry->New.pszCppMapping);
1022 free(pEntry->Old.pszCppMapping);
1023
1024 free(pEntry->New.pszObjName);
1025 free(pEntry->Old.pszObjName);
1026
1027 while (pEntry->New.cArgvCompile > 0)
1028 free(pEntry->New.papszArgvCompile[--pEntry->New.cArgvCompile]);
1029 while (pEntry->Old.cArgvCompile > 0)
1030 free(pEntry->Old.papszArgvCompile[--pEntry->Old.cArgvCompile]);
1031
1032 free(pEntry->New.papszArgvCompile);
1033 free(pEntry->Old.papszArgvCompile);
1034
1035 free(pEntry);
1036}
1037
1038
1039/**
1040 * Reads and parses the cache file.
1041 *
1042 * @param pEntry The entry to read it into.
1043 */
1044static void kOCEntryRead(PKOCENTRY pEntry)
1045{
1046 FILE *pFile;
1047 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "rb");
1048 if (pFile)
1049 {
1050 InfoMsg(1, "reading cache file...\n");
1051
1052 /*
1053 * Check the magic.
1054 */
1055 if ( !fgets(g_szLine, sizeof(g_szLine), pFile)
1056 || strcmp(g_szLine, "magic=kObjCache-1\n"))
1057 {
1058 InfoMsg(1, "bad cache file (magic)\n");
1059 pEntry->fNeedCompiling = 1;
1060 }
1061 else
1062 {
1063 /*
1064 * Parse the rest of the file (relaxed order).
1065 */
1066 unsigned i;
1067 int fBad = 0;
1068 int fBadBeforeMissing = 1;
1069 int fFirstSum = 1;
1070 while (fgets(g_szLine, sizeof(g_szLine), pFile))
1071 {
1072 char *pszNl;
1073 char *pszVal;
1074
1075 /* Split the line and drop the trailing newline. */
1076 pszVal = strchr(g_szLine, '=');
1077 if ((fBad = pszVal == NULL))
1078 break;
1079 *pszVal++ = '\0';
1080
1081 pszNl = strchr(pszVal, '\n');
1082 if (pszNl)
1083 *pszNl = '\0';
1084
1085 /* string case on variable name */
1086 if (!strcmp(g_szLine, "obj"))
1087 {
1088 if ((fBad = pEntry->Old.pszObjName != NULL))
1089 break;
1090 pEntry->Old.pszObjName = xstrdup(pszVal);
1091 }
1092 else if (!strcmp(g_szLine, "cpp"))
1093 {
1094 if ((fBad = pEntry->Old.pszCppName != NULL))
1095 break;
1096 pEntry->Old.pszCppName = xstrdup(pszVal);
1097 }
1098 else if (!strcmp(g_szLine, "cpp-size"))
1099 {
1100 char *pszNext;
1101 if ((fBad = pEntry->Old.cbCpp != 0))
1102 break;
1103 pEntry->Old.cbCpp = strtoul(pszVal, &pszNext, 0);
1104 if ((fBad = pszNext && *pszNext))
1105 break;
1106 }
1107 else if (!strcmp(g_szLine, "cc-argc"))
1108 {
1109 if ((fBad = pEntry->Old.papszArgvCompile != NULL))
1110 break;
1111 pEntry->Old.cArgvCompile = atoi(pszVal); /* if wrong, we'll fail below. */
1112 pEntry->Old.papszArgvCompile = xmallocz((pEntry->Old.cArgvCompile + 1) * sizeof(pEntry->Old.papszArgvCompile[0]));
1113 }
1114 else if (!strncmp(g_szLine, "cc-argv-#", sizeof("cc-argv-#") - 1))
1115 {
1116 char *pszNext;
1117 unsigned i = strtoul(&g_szLine[sizeof("cc-argv-#") - 1], &pszNext, 0);
1118 if ((fBad = i >= pEntry->Old.cArgvCompile || pEntry->Old.papszArgvCompile[i] || (pszNext && *pszNext)))
1119 break;
1120 pEntry->Old.papszArgvCompile[i] = xstrdup(pszVal);
1121 }
1122 else if (!strcmp(g_szLine, "sum"))
1123 {
1124 KOCSUM Sum;
1125 if ((fBad = kOCSumInitFromString(&Sum, pszVal)))
1126 break;
1127 kOCSumAdd(&pEntry->Old.SumHead, &Sum);
1128 }
1129 else if (!strcmp(g_szLine, "target"))
1130 {
1131 if ((fBad = pEntry->Old.pszTarget != NULL))
1132 break;
1133 pEntry->Old.pszTarget = xstrdup(pszVal);
1134 }
1135 else if (!strcmp(g_szLine, "the-end"))
1136 {
1137 fBadBeforeMissing = fBad = !strcmp(pszVal, "fine");
1138 break;
1139 }
1140 else
1141 {
1142 fBad = 1;
1143 break;
1144 }
1145 } /* parse loop */
1146
1147 /*
1148 * Did we find everything and does it add up correctly?
1149 */
1150 if (!fBad && fBadBeforeMissing)
1151 {
1152 InfoMsg(1, "bad cache file (no end)\n");
1153 fBad = 1;
1154 }
1155 else
1156 {
1157 fBadBeforeMissing = fBad;
1158 if ( !fBad
1159 && ( !pEntry->Old.papszArgvCompile
1160 || !pEntry->Old.pszObjName
1161 || !pEntry->Old.pszCppName
1162 || fFirstSum))
1163 fBad = 1;
1164 if (!fBad)
1165 {
1166 KOCSUMCTX Ctx;
1167 KOCSUM Sum;
1168
1169 kOCSumInitWithCtx(&Sum, &Ctx);
1170 for (i = 0; i < pEntry->Old.cArgvCompile; i++)
1171 {
1172 if ((fBad = !pEntry->Old.papszArgvCompile[i]))
1173 break;
1174 kOCSumUpdate(&Sum, &Ctx, pEntry->Old.papszArgvCompile[i], strlen(pEntry->Old.papszArgvCompile[i]) + 1);
1175 }
1176 kOCSumFinalize(&Sum, &Ctx);
1177 if (!fBad)
1178 fBad = !kOCSumIsEqual(&pEntry->Old.SumCompArgv, &Sum);
1179 }
1180 if (fBad)
1181 InfoMsg(1, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff");
1182 else if (ferror(pFile))
1183 {
1184 InfoMsg(1, "cache file read error\n");
1185 fBad = 1;
1186 }
1187
1188 /*
1189 * Verify the existance of the object file.
1190 */
1191 if (!fBad)
1192 {
1193 struct stat st;
1194 char *pszPath = MakePathFromDirAndFile(pEntry->Old.pszObjName, pEntry->pszDir);
1195 if (stat(pszPath, &st) != 0)
1196 {
1197 InfoMsg(1, "failed to stat object file: %s\n", strerror(errno));
1198 fBad = 1;
1199 }
1200 else
1201 {
1202 /** @todo verify size and the timestamp. */
1203 }
1204 }
1205 }
1206 pEntry->fNeedCompiling = fBad;
1207 }
1208 fclose(pFile);
1209 }
1210 else
1211 {
1212 InfoMsg(1, "no cache file\n");
1213 pEntry->fNeedCompiling = 1;
1214 }
1215}
1216
1217
1218/**
1219 * Writes the cache file.
1220 *
1221 * @param pEntry The entry to write.
1222 */
1223static void kOCEntryWrite(PKOCENTRY pEntry)
1224{
1225 FILE *pFile;
1226 PCKOCSUM pSum;
1227 unsigned i;
1228
1229 InfoMsg(1, "writing cache file...\n");
1230 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "wb");
1231 if (!pFile)
1232 FatalDie("Failed to open '%s' in '%s': %s\n",
1233 pEntry->pszName, pEntry->pszDir, strerror(errno));
1234
1235#define CHECK_LEN(expr) \
1236 do { int cch = expr; if (cch >= KOBJCACHE_MAX_LINE_LEN) FatalDie("Line too long: %d (max %d)\nexpr: %s\n", cch, KOBJCACHE_MAX_LINE_LEN, #expr); } while (0)
1237
1238 fprintf(pFile, "magic=kObjCache-1\n");
1239 CHECK_LEN(fprintf(pFile, "target=%s\n", pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget));
1240 CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->New.pszObjName ? pEntry->New.pszObjName : pEntry->Old.pszObjName));
1241 CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->New.pszCppName ? pEntry->New.pszCppName : pEntry->Old.pszCppName));
1242 CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n", pEntry->New.pszCppName ? pEntry->New.cbCpp : pEntry->Old.cbCpp));
1243
1244 if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv))
1245 {
1246 CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->New.cArgvCompile));
1247 for (i = 0; i < pEntry->New.cArgvCompile; i++)
1248 CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->New.papszArgvCompile[i]));
1249 fprintf(pFile, "cc-argv-sum=");
1250 kOCSumFPrintf(&pEntry->New.SumCompArgv, pFile);
1251 }
1252 else
1253 {
1254 CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->Old.cArgvCompile));
1255 for (i = 0; i < pEntry->Old.cArgvCompile; i++)
1256 CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->Old.papszArgvCompile[i]));
1257 fprintf(pFile, "cc-argv-sum=");
1258 kOCSumFPrintf(&pEntry->Old.SumCompArgv, pFile);
1259 }
1260
1261
1262 for (pSum = kOCSumIsEmpty(&pEntry->New.SumHead) ? &pEntry->New.SumHead : &pEntry->Old.SumHead;
1263 pSum;
1264 pSum = pSum->pNext)
1265 {
1266 fprintf(pFile, "sum=");
1267 kOCSumFPrintf(pSum, pFile);
1268 }
1269
1270 fprintf(pFile, "the-end=fine\n");
1271
1272#undef CHECK_LEN
1273
1274 /*
1275 * Flush the file and check for errors.
1276 * On failure delete the file so we won't be seeing any invalid
1277 * files the next time or upset make with new timestamps.
1278 */
1279 if ( fflush(pFile) < 0
1280 || ferror(pFile))
1281 {
1282 int iErr = errno;
1283 fclose(pFile);
1284 UnlinkFileInDir(pEntry->pszName, pEntry->pszDir);
1285 FatalDie("Stream error occured while writing '%s' in '%s': %s\n",
1286 pEntry->pszName, pEntry->pszDir, strerror(iErr));
1287 }
1288 fclose(pFile);
1289}
1290
1291
1292/**
1293 * Checks that the read cache entry is valid.
1294 * It sets fNeedCompiling if it isn't.
1295 *
1296 * @returns 1 valid, 0 invalid.
1297 * @param pEntry The cache entry.
1298 */
1299static int kOCEntryCheck(PKOCENTRY pEntry)
1300{
1301 return pEntry->fNeedCompiling;
1302}
1303
1304
1305/**
1306 * Set the new compiler args, calc their checksum, and comparing them with any old ones.
1307 *
1308 * @param pEntry The cache entry.
1309 * @param papszArgvCompile The new argument vector for compilation.
1310 * @param cArgvCompile The number of arguments in the vector.
1311 */
1312static void kOCEntrySetCompileArgv(PKOCENTRY pEntry, const char * const *papszArgvCompile, unsigned cArgvCompile)
1313{
1314 KOCSUMCTX Ctx;
1315 unsigned i;
1316
1317 /* call me only once! */
1318 assert(!pEntry->New.cArgvCompile);
1319
1320 /*
1321 * Copy the argument vector and calculate the checksum while doing so.
1322 */
1323 pEntry->New.cArgvCompile = cArgvCompile;
1324 pEntry->New.papszArgvCompile = xmalloc(cArgvCompile + 1);
1325 kOCSumInitWithCtx(&pEntry->New.SumCompArgv, &Ctx);
1326 for (i = 0; i < cArgvCompile; i++)
1327 {
1328 pEntry->New.papszArgvCompile[i] = xstrdup(papszArgvCompile[i]);
1329 kOCSumUpdate(&pEntry->New.SumCompArgv, &Ctx, papszArgvCompile[i], strlen(papszArgvCompile[i]));
1330 }
1331 kOCSumFinalize(&pEntry->New.SumCompArgv, &Ctx);
1332 pEntry->New.papszArgvCompile[i] = NULL; /* for exev/spawnv */
1333
1334 /*
1335 * Compare with the old argument vector.
1336 */
1337 if ( !pEntry->fNeedCompiling
1338 && !kOCSumIsEqual(&pEntry->New.SumCompArgv, &pEntry->Old.SumCompArgv))
1339 {
1340 InfoMsg(1, "compiler args differs\n");
1341 pEntry->fNeedCompiling = 1;
1342 }
1343}
1344
1345
1346/**
1347 * Sets the object name and compares it with the old name if present.
1348 *
1349 * @param pEntry The cache entry.
1350 * @param pszObjName The new object name.
1351 */
1352static void kOCEntrySetCompileObjName(PKOCENTRY pEntry, const char *pszObjName)
1353{
1354 assert(!pEntry->New.pszObjName);
1355 pEntry->New.pszObjName = CalcRelativeName(pszObjName, pEntry->pszDir);
1356
1357 if ( !pEntry->fNeedCompiling
1358 && ( !pEntry->Old.pszObjName
1359 || strcmp(pEntry->New.pszObjName, pEntry->Old.pszObjName)))
1360 {
1361 InfoMsg(1, "object file name differs\n");
1362 pEntry->fNeedCompiling = 1;
1363 }
1364
1365 if ( !pEntry->fNeedCompiling
1366 && !DoesFileInDirExist(pEntry->New.pszObjName, pEntry->pszDir))
1367 {
1368 InfoMsg(1, "object file doesn't exist\n");
1369 pEntry->fNeedCompiling = 1;
1370 }
1371}
1372
1373
1374/**
1375 * Sets the arch/os target and compares it with the old name if present.
1376 *
1377 * @param pEntry The cache entry.
1378 * @param pszObjName The new object name.
1379 */
1380static void kOCEntrySetTarget(PKOCENTRY pEntry, const char *pszTarget)
1381{
1382 assert(!pEntry->New.pszTarget);
1383 pEntry->New.pszTarget = xstrdup(pszTarget);
1384
1385 if ( !pEntry->fNeedCompiling
1386 && ( !pEntry->Old.pszTarget
1387 || strcmp(pEntry->New.pszTarget, pEntry->Old.pszTarget)))
1388 {
1389 InfoMsg(1, "target differs\n");
1390 pEntry->fNeedCompiling = 1;
1391 }
1392}
1393
1394
1395/**
1396 * Sets the precompiler output filename.
1397 * We don't generally care if this matches the old name or not.
1398 *
1399 * @param pEntry The cache entry.
1400 * @param pszCppName The precompiler output filename.
1401 */
1402static void kOCEntrySetCppName(PKOCENTRY pEntry, const char *pszCppName)
1403{
1404 assert(!pEntry->New.pszCppName);
1405 pEntry->New.pszCppName = CalcRelativeName(pszCppName, pEntry->pszDir);
1406}
1407
1408
1409/**
1410 * Sets the piped mode of the precompiler and compiler.
1411 *
1412 * @param pEntry The cache entry.
1413 * @param fRedirPreCompStdOut Whether the precompiler is in piped mode.
1414 * @param fRedirCompileStdIn Whether the compiler is in piped mode.
1415 */
1416static void kOCEntrySetPipedMode(PKOCENTRY pEntry, int fRedirPreCompStdOut, int fRedirCompileStdIn)
1417{
1418 pEntry->fPipedPreComp = fRedirPreCompStdOut;
1419 pEntry->fPipedCompile = fRedirCompileStdIn;
1420}
1421
1422
1423/**
1424 * Spawns a child in a synchronous fashion.
1425 * Terminating on failure.
1426 *
1427 * @param papszArgv Argument vector. The cArgv element is NULL.
1428 * @param cArgv The number of arguments in the vector.
1429 */
1430static void kOCEntrySpawn(PCKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut)
1431{
1432#if defined(__OS2__) || defined(__WIN__)
1433 intptr_t rc;
1434 int fdStdOut = -1;
1435 if (pszStdOut)
1436 {
1437 int fdReDir;
1438 fdStdOut = dup(1); /* dup2(1,-1) doesn't work right on windows */
1439 close(1);
1440 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666);
1441 if (fdReDir < 0)
1442 FatalDie("%s - failed to create stdout redirection file '%s': %s\n",
1443 pszMsg, pszStdOut, strerror(errno));
1444
1445 if (fdReDir != 1)
1446 {
1447 if (dup2(fdReDir, 1) < 0)
1448 FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno));
1449 close(fdReDir);
1450 }
1451 }
1452
1453 errno = 0;
1454 rc = _spawnvp(_P_WAIT, papszArgv[0], papszArgv);
1455 if (rc < 0)
1456 FatalDie("%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno));
1457 if (rc > 0)
1458 FatalDie("%s - failed rc=%d\n", pszMsg, (int)rc);
1459 if (fdStdOut)
1460 {
1461 close(1);
1462 fdStdOut = dup2(fdStdOut, 1);
1463 close(fdStdOut);
1464 }
1465
1466#else
1467 int iStatus;
1468 pid_t pidWait;
1469 pid_t pid = fork();
1470 if (!pid)
1471 {
1472 if (pszStdOut)
1473 {
1474 int fdReDir;
1475
1476 close(1);
1477 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666);
1478 if (fdReDir < 0)
1479 FatalDie("%s - failed to create stdout redirection file '%s': %s\n",
1480 pszMsg, pszStdOut, strerror(errno));
1481 if (fdReDir != 1)
1482 {
1483 if (dup2(fdReDir, 1) < 0)
1484 FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno));
1485 close(fdReDir);
1486 }
1487 }
1488
1489 execvp(papszArgv[0], (char **)papszArgv);
1490 FatalDie("%s - execvp failed: %s\n",
1491 pszMsg, strerror(errno));
1492 }
1493 if (pid == -1)
1494 FatalDie("%s - fork() failed: %s\n", pszMsg, strerror(errno));
1495
1496 pidWait = waitpid(pid, &iStatus, 0);
1497 while (pidWait < 0 && errno == EINTR)
1498 pidWait = waitpid(pid, &iStatus, 0);
1499 if (pidWait != pid)
1500 FatalDie("%s - waitpid failed rc=%d: %s\n",
1501 pszMsg, pidWait, strerror(errno));
1502 if (!WIFEXITED(iStatus))
1503 FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
1504 if (WEXITSTATUS(iStatus))
1505 FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
1506#endif
1507 (void)cArgv;
1508}
1509
1510
1511/**
1512 * Spawns child with optional redirection of stdin and stdout.
1513 *
1514 * @param pEntry The cache entry.
1515 * @param papszArgv Argument vector. The cArgv element is NULL.
1516 * @param cArgv The number of arguments in the vector.
1517 * @param fdStdIn Child stdin, -1 if it should inherit our stdin. Will be closed.
1518 * @param fdStdOut Child stdout, -1 if it should inherit our stdout. Will be closed.
1519 * @param pszMsg Message to start the info/error messages with.
1520 */
1521static pid_t kOCEntrySpawnChild(PCKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, int fdStdIn, int fdStdOut, const char *pszMsg)
1522{
1523 pid_t pid;
1524 int fdSavedStdOut = -1;
1525 int fdSavedStdIn = -1;
1526
1527 /*
1528 * Setup redirection.
1529 */
1530 if (fdStdOut != -1)
1531 {
1532 fdSavedStdOut = dup(1 /* stdout */);
1533 if (dup2(fdStdOut, 1 /* stdout */) < 0)
1534 FatalDie("%s - dup2(,1) failed: %s\n", pszMsg, strerror(errno));
1535 close(fdStdOut);
1536#ifndef __WIN__
1537 fcntl(fdSavedStdOut, F_SETFD, FD_CLOEXEC);
1538#endif
1539 }
1540 if (fdStdIn != -1)
1541 {
1542 fdSavedStdIn = dup(0 /* stdin */);
1543 if (dup2(fdStdOut, 0 /* stdin */) < 0)
1544 FatalDie("%s - dup2(,0) failed: %s\n", pszMsg, strerror(errno));
1545 close(fdStdIn);
1546#ifndef __WIN__
1547 fcntl(fdSavedStdIn, F_SETFD, FD_CLOEXEC);
1548#endif
1549 }
1550
1551 /*
1552 * Create the child process.
1553 */
1554#if defined(__OS2__) || defined(__WIN__)
1555 errno = 0;
1556 pid = _spawnvp(_P_NOWAIT, papszArgv[0], papszArgv);
1557 if (pid == -1)
1558 FatalDie("precompile - _spawnvp failed: %s\n", strerror(errno));
1559
1560#else
1561 pid = fork();
1562 if (!pid)
1563 {
1564 execvp(papszArgv[0], (char **)papszArgv);
1565 FatalDie("precompile - execvp failed: %s\n", strerror(errno));
1566 }
1567 if (pid == -1)
1568 FatalDie("precompile - fork() failed: %s\n", strerror(errno));
1569#endif
1570
1571 /*
1572 * Restore stdout & stdin.
1573 */
1574 if (fdSavedStdIn)
1575 {
1576 close(0 /* stdin */);
1577 dup2(fdStdOut, 0 /* stdin */);
1578 close(fdSavedStdIn);
1579 }
1580 if (fdSavedStdOut)
1581 {
1582 close(1 /* stdout */);
1583 dup2(fdSavedStdOut, 1 /* stdout */);
1584 close(fdSavedStdOut);
1585 }
1586
1587 (void)cArgv;
1588 (void)pEntry;
1589 return pid;
1590}
1591
1592
1593/**
1594 * Waits for a child and exits fatally if the child failed in any way.
1595 *
1596 * @param pEntry The cache entry.
1597 * @param pid The child to wait for.
1598 * @param pszMsg Message to start the info/error messages with.
1599 */
1600static void kOCEntryWaitChild(PCKOCENTRY pEntry, pid_t pid, const char *pszMsg)
1601{
1602 int iStatus = -1;
1603 pid_t pidWait;
1604#ifdef __WIN__
1605 pidWait = _cwait(&iStatus, pid, _WAIT_CHILD);
1606 if (pidWait == -1)
1607 FatalDie("%s - waitpid failed: %s\n", pszMsg, strerror(errno));
1608 if (iStatus)
1609 FatalDie("%s - failed with rc %d\n", pszMsg, iStatus);
1610#else
1611 pidWait = waitpid(pid, &iStatus, 0);
1612 while (pidWait < 0 && errno == EINTR)
1613 pidWait = waitpid(pid, &iStatus, 0);
1614 if (pidWait != pid)
1615 FatalDie("%s - waitpid failed rc=%d: %s\n", pidWait, strerror(errno));
1616 if (!WIFEXITED(iStatus))
1617 FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
1618 if (WEXITSTATUS(iStatus))
1619 FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
1620#endif
1621 (void)pEntry;
1622}
1623
1624
1625/**
1626 * Creates a pipe for setting up redirected stdin/stdout.
1627 *
1628 * @param pEntry The cache entry.
1629 * @param pFDs Where to store the two file descriptors.
1630 * @param pszMsg The operation message for info/error messages.
1631 */
1632static void kOCEntryCreatePipe(PKOCENTRY pEntry, int *pFDs, const char *pszMsg)
1633{
1634#if defined(__WIN__)
1635 if (_pipe(pFDs, 0, _O_NOINHERIT | _O_BINARY) < 0)
1636#else
1637 if (pipe(pFDs) < 0)
1638#endif
1639 FatalDie("%s - pipe failed: %s\n", pszMsg, strerror(errno));
1640#if !defined(__WIN__)
1641 fcntl(pFDs[0], F_SETFD, FD_CLOEXEC);
1642 fcntl(pFDs[1], F_SETFD, FD_CLOEXEC);
1643#endif
1644}
1645
1646
1647/**
1648 * Spawns a child that produces output to stdout.
1649 *
1650 * @param papszArgv Argument vector. The cArgv element is NULL.
1651 * @param cArgv The number of arguments in the vector.
1652 * @param pszMsg The operation message for info/error messages.
1653 * @param pfnConsumer Pointer to a consumer callback function that is responsible
1654 * for servicing the child output and closing the pipe.
1655 */
1656static void kOCEntrySpawnProducer(PKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg,
1657 void (*pfnConsumer)(PKOCENTRY, int))
1658{
1659 int fds[2];
1660 pid_t pid;
1661
1662 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1663 pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, -1, fds[1 /* write */], pszMsg);
1664
1665 pfnConsumer(pEntry, fds[0 /* read */]);
1666
1667 kOCEntryWaitChild(pEntry, pid, pszMsg);
1668}
1669
1670
1671/**
1672 * Spawns a child that consumes input on stdin.
1673 *
1674 * @param papszArgv Argument vector. The cArgv element is NULL.
1675 * @param cArgv The number of arguments in the vector.
1676 * @param pszMsg The operation message for info/error messages.
1677 * @param pfnProducer Pointer to a producer callback function that is responsible
1678 * for serving the child input and closing the pipe.
1679 */
1680static void kOCEntrySpawnConsumer(PKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg,
1681 void (*pfnProducer)(PKOCENTRY, int))
1682{
1683 int fds[2];
1684 pid_t pid;
1685
1686 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1687 pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, fds[0 /* read */], -1, pszMsg);
1688
1689 pfnProducer(pEntry, fds[1 /* write */]);
1690
1691 kOCEntryWaitChild(pEntry, pid, pszMsg);
1692}
1693
1694
1695/**
1696 * Spawns two child processes, one producing output and one consuming.
1697 * Terminating on failure.
1698 *
1699 * @param papszArgv Argument vector. The cArgv element is NULL.
1700 * @param cArgv The number of arguments in the vector.
1701 * @param pszMsg The operation message for info/error messages.
1702 * @param pfnConsumer Pointer to a consumer callback function that is responsible
1703 * for servicing the child output and closing the pipe.
1704 */
1705static void kOCEntrySpawnTee(PKOCENTRY pEntry, const char **papszProdArgv, unsigned cProdArgv,
1706 const char **papszConsArgv, unsigned cConsArgv,
1707 const char *pszMsg, void (*pfnTeeConsumer)(PKOCENTRY, int, int))
1708{
1709 int fds[2];
1710 int fdIn, fdOut;
1711 pid_t pidProducer, pidConsumer;
1712
1713 /*
1714 * The producer.
1715 */
1716 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1717 pidConsumer = kOCEntrySpawnChild(pEntry, papszProdArgv, cProdArgv, -1, fds[1 /* write */], pszMsg);
1718 fdIn = fds[0 /* read */];
1719
1720 /*
1721 * The consumer.
1722 */
1723 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1724 pidProducer = kOCEntrySpawnChild(pEntry, papszProdArgv, cProdArgv, fds[0 /* read */], -1, pszMsg);
1725 fdOut = fds[1 /* write */];
1726
1727 /*
1728 * Hand it on to the tee consumer.
1729 */
1730 pfnTeeConsumer(pEntry, fdIn, fdOut);
1731
1732 /*
1733 * Reap the children.
1734 */
1735 kOCEntryWaitChild(pEntry, pidProducer, pszMsg);
1736 kOCEntryWaitChild(pEntry, pidConsumer, pszMsg);
1737}
1738
1739
1740/**
1741 * Reads the output from the precompiler.
1742 *
1743 * @param pEntry The cache entry. New.cbCpp and New.pszCppMapping will be updated.
1744 * @param pWhich Specifies what to read (old/new).
1745 * @param fNonFatal Whether failure is fatal or not.
1746 */
1747static int kOCEntryReadCppOutput(PKOCENTRY pEntry, struct KOCENTRYDATA *pWhich, int fNonFatal)
1748{
1749 pWhich->pszCppMapping = ReadFileInDir(pWhich->pszCppName, pEntry->pszDir, &pWhich->cbCpp);
1750 if (!pWhich->pszCppMapping)
1751 {
1752 if (!fNonFatal)
1753 FatalDie("failed to open/read '%s' in '%s': %s\n",
1754 pWhich->pszCppName, pEntry->pszDir, strerror(errno));
1755 InfoMsg(1, "failed to open/read '%s' in '%s': %s\n",
1756 pWhich->pszCppName, pEntry->pszDir, strerror(errno));
1757 return -1;
1758 }
1759
1760 InfoMsg(1, "precompiled file is %lu bytes long\n", (unsigned long)pWhich->cbCpp);
1761 return 0;
1762}
1763
1764
1765/**
1766 * Worker for kOCEntryPreCompile and calculates the checksum of
1767 * the precompiler output.
1768 *
1769 * @param pEntry The cache entry. NewSum will be updated.
1770 */
1771static void kOCEntryCalcChecksum(PKOCENTRY pEntry)
1772{
1773 KOCSUMCTX Ctx;
1774 kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
1775 kOCSumUpdate(&pEntry->New.SumHead, &Ctx, pEntry->New.pszCppMapping, pEntry->New.cbCpp);
1776 kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
1777 kOCSumInfo(&pEntry->New.SumHead, 1, "");
1778}
1779
1780
1781/**
1782 * This consumes the precompiler output and checksums it.
1783 *
1784 * @param pEntry The cache entry.
1785 * @param fdIn The precompiler output pipe.
1786 * @param fdOut The compiler input pipe, -1 if no compiler.
1787 */
1788static void kOCEntryPreCompileConsumer(PKOCENTRY pEntry, int fdIn)
1789{
1790 KOCSUMCTX Ctx;
1791 long cbLeft;
1792 long cbAlloc;
1793 char *psz;
1794
1795 kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
1796 cbAlloc = pEntry->Old.cbCpp ? (pEntry->Old.cbCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
1797 cbLeft = cbAlloc;
1798 pEntry->New.pszCppMapping = psz = xmalloc(cbAlloc);
1799 for (;;)
1800 {
1801 /*
1802 * Read data from the pipe.
1803 */
1804 long cbRead = read(fdIn, psz, cbLeft - 1);
1805 if (!cbRead)
1806 break;
1807 if (cbRead < 0)
1808 {
1809 if (errno == EINTR)
1810 continue;
1811 FatalDie("precompile - read(%d,,%ld) failed: %s\n",
1812 fdIn, (long)cbLeft, strerror(errno));
1813 }
1814
1815 /*
1816 * Process the data.
1817 */
1818 psz[cbRead] = '\0';
1819 kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead);
1820
1821 /*
1822 * Advance.
1823 */
1824 psz += cbRead;
1825 cbLeft -= cbRead;
1826 if (cbLeft <= 1)
1827 {
1828 size_t off = psz - pEntry->New.pszCppMapping;
1829 assert(off == cbAlloc);
1830 cbLeft = 4*1024*1024;
1831 cbAlloc += cbLeft;
1832 pEntry->New.pszCppMapping = xrealloc(pEntry->New.pszCppMapping, cbAlloc);
1833 psz = pEntry->New.pszCppMapping + off;
1834 }
1835 }
1836
1837 close(fdIn);
1838 pEntry->New.cbCpp = cbAlloc - cbLeft;
1839 kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
1840}
1841
1842
1843
1844
1845/**
1846 * Run the precompiler and calculate the checksum of the output.
1847 *
1848 * @param pEntry The cache entry.
1849 * @param papszArgvPreComp The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
1850 * @param cArgvPreComp The number of arguments.
1851 */
1852static void kOCEntryPreCompile(PKOCENTRY pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp)
1853{
1854 /*
1855 * If we're executing the precompiler in piped mode, it's relatively simple.
1856 */
1857 if (pEntry->fPipedPreComp)
1858 kOCEntrySpawnProducer(pEntry, papszArgvPreComp, cArgvPreComp, "precompile",
1859 kOCEntryPreCompileConsumer);
1860 else
1861 {
1862 /*
1863 * Rename the old precompiled output to '-old' so the precompiler won't
1864 * overwrite it when we execute it.
1865 */
1866 if ( pEntry->Old.pszCppName
1867 && DoesFileInDirExist(pEntry->Old.pszCppName, pEntry->pszDir))
1868 {
1869 size_t cch = strlen(pEntry->Old.pszCppName);
1870 char *psz = xmalloc(cch + sizeof("-old"));
1871 memcpy(psz, pEntry->Old.pszCppName, cch);
1872 memcpy(psz + cch, "-old", sizeof("-old"));
1873
1874 InfoMsg(1, "renaming '%s' to '%s' in '%s'\n", pEntry->Old.pszCppName, psz, pEntry->pszDir);
1875 UnlinkFileInDir(psz, pEntry->pszDir);
1876 if (RenameFileInDir(pEntry->Old.pszCppName, psz, pEntry->pszDir))
1877 FatalDie("failed to rename '%s' -> '%s' in '%s': %s\n",
1878 pEntry->Old.pszCppName, psz, pEntry->pszDir, strerror(errno));
1879 free(pEntry->Old.pszCppName);
1880 pEntry->Old.pszCppName = psz;
1881 }
1882
1883 /*
1884 * Precompile it and calculate the checksum on the output.
1885 */
1886 InfoMsg(1, "precompiling -> '%s'...\n", pEntry->New.pszCppName);
1887 kOCEntrySpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
1888 kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
1889 kOCEntryCalcChecksum(pEntry);
1890 }
1891}
1892
1893
1894/**
1895 * Worker function for kOCEntryTeeConsumer and kOCEntryCompileIt that
1896 * writes the precompiler output to disk.
1897 *
1898 * @param pEntry The cache entry.
1899 * @param fFreeIt Whether we can free it after writing it or not.
1900 */
1901static void kOCEntryWriteCppOutput(PKOCENTRY pEntry, int fFreeIt)
1902{
1903 /*
1904 * Remove old files.
1905 */
1906 if (pEntry->Old.pszCppName)
1907 UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir);
1908 if (pEntry->New.pszCppName)
1909 UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
1910
1911 /*
1912 * Write it to disk if we've got a file name.
1913 */
1914 if (pEntry->New.pszCppName)
1915 {
1916 long cbLeft;
1917 char *psz;
1918 int fd = OpenFileInDir(pEntry->New.pszCppName, pEntry->pszDir, O_WRONLY | O_TRUNC | O_BINARY, 0666);
1919 if (fd == -1)
1920 FatalDie("Failed to create '%s' in '%s': %s\n",
1921 pEntry->New.pszCppName, pEntry->pszDir, strerror(errno));
1922 psz = pEntry->New.pszCppMapping;
1923 cbLeft = pEntry->New.cbCpp;
1924 while (cbLeft > 0)
1925 {
1926 long cbWritten = write(fd, psz, cbLeft);
1927 if (cbWritten < 0)
1928 {
1929 int iErr = errno;
1930 if (iErr == EINTR)
1931 continue;
1932 close(fd);
1933 UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
1934 FatalDie("error writing '%s' in '%s': %s\n",
1935 pEntry->New.pszCppName, pEntry->pszDir, strerror(iErr));
1936 }
1937 }
1938 close(fd);
1939 }
1940
1941 /*
1942 * Free it.
1943 */
1944 if (fFreeIt)
1945 {
1946 free(pEntry->New.pszCppMapping);
1947 pEntry->New.pszCppMapping = NULL;
1948 }
1949}
1950
1951
1952/**
1953 * kOCEntrySpawnConsumer callback that passes the precompiler
1954 * output to the compiler and writes it to the disk (latter only when necesary).
1955 *
1956 * @param pEntry The cache entry.
1957 * @param fdOut The pipe handle connected to the childs stdin.
1958 */
1959static void kOCEntryCompileProducer(PKOCENTRY pEntry, int fdOut)
1960{
1961 const char *psz = pEntry->New.pszCppMapping;
1962 long cbLeft = pEntry->New.cbCpp;
1963 while (cbLeft > 0)
1964 {
1965 long cbWritten = write(fdOut, psz, cbLeft);
1966 if (cbWritten < 0)
1967 {
1968 if (errno == EINTR)
1969 continue;
1970 FatalDie("compile - write(%d,,%ld) failed: %s\n", fdOut, cbLeft, strerror(errno));
1971 }
1972 psz += cbWritten;
1973 cbLeft -= cbWritten;
1974 }
1975
1976 if (pEntry->fPipedPreComp)
1977 kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
1978}
1979
1980
1981/**
1982 * Does the actual compiling.
1983 *
1984 * @param pEntry The cache entry.
1985 */
1986static void kOCEntryCompileIt(PKOCENTRY pEntry)
1987{
1988 /*
1989 * Delete the object files and free old cpp output that's no longer needed.
1990 */
1991 if (pEntry->Old.pszObjName)
1992 UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir);
1993 UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir);
1994
1995 free(pEntry->Old.pszCppMapping);
1996 pEntry->Old.pszCppMapping = NULL;
1997 if (!pEntry->fPipedPreComp && !pEntry->fPipedCompile)
1998 {
1999 free(pEntry->New.pszCppMapping);
2000 pEntry->New.pszCppMapping = NULL;
2001 }
2002
2003 /*
2004 * Do the (re-)compile job.
2005 */
2006 if (pEntry->fPipedCompile)
2007 {
2008 if ( !pEntry->fPipedPreComp
2009 && !pEntry->New.pszCppMapping)
2010 kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
2011 InfoMsg(1, "compiling -> '%s'...\n", pEntry->New.pszObjName);
2012 kOCEntrySpawnConsumer(pEntry, pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile,
2013 "compile", kOCEntryCompileProducer);
2014 }
2015 else
2016 {
2017 if (pEntry->fPipedPreComp)
2018 kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
2019 InfoMsg(1, "compiling -> '%s'...\n", pEntry->New.pszObjName);
2020 kOCEntrySpawn(pEntry, pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile, "compile", NULL);
2021 }
2022}
2023
2024
2025/**
2026 * kOCEntrySpawnTee callback that works sort of like 'tee'.
2027 *
2028 * It will calculate the precompiled output checksum and
2029 * write it to disk while the compiler is busy compiling it.
2030 *
2031 * @param pEntry The cache entry.
2032 * @param fdIn The input handle (connected to the precompiler).
2033 * @param fdOut The output handle (connected to the compiler).
2034 */
2035static void kOCEntryTeeConsumer(PKOCENTRY pEntry, int fdIn, int fdOut)
2036{
2037 KOCSUMCTX Ctx;
2038 long cbLeft;
2039 long cbAlloc;
2040 char *psz;
2041
2042 kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
2043 cbAlloc = pEntry->Old.cbCpp ? (pEntry->Old.cbCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
2044 cbLeft = cbAlloc;
2045 pEntry->New.pszCppMapping = psz = xmalloc(cbAlloc);
2046 for (;;)
2047 {
2048 /*
2049 * Read data from the pipe.
2050 */
2051 long cbRead = read(fdIn, psz, cbLeft - 1);
2052 if (!cbRead)
2053 break;
2054 if (cbRead < 0)
2055 {
2056 if (errno == EINTR)
2057 continue;
2058 FatalDie("precompile|compile - read(%d,,%ld) failed: %s\n",
2059 fdIn, (long)cbLeft, strerror(errno));
2060 }
2061
2062 /*
2063 * Process the data.
2064 */
2065 psz[cbRead] = '\0';
2066 kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead);
2067 do
2068 {
2069 long cbWritten = write(fdOut, psz, cbRead);
2070 if (cbWritten < 0)
2071 {
2072 if (errno == EINTR)
2073 continue;
2074 FatalDie("precompile|compile - write(%d,,%ld) failed: %s\n", fdOut, cbRead, strerror(errno));
2075 }
2076 psz += cbWritten;
2077 cbRead -= cbWritten;
2078 cbLeft -= cbWritten;
2079 } while (cbRead > 0);
2080
2081 /*
2082 * Expand the buffer?
2083 */
2084 if (cbLeft <= 1)
2085 {
2086 size_t off = psz - pEntry->New.pszCppMapping;
2087 assert(off == cbAlloc);
2088 cbLeft = 4*1024*1024;
2089 cbAlloc += cbLeft;
2090 pEntry->New.pszCppMapping = xrealloc(pEntry->New.pszCppMapping, cbAlloc);
2091 psz = pEntry->New.pszCppMapping + off;
2092 }
2093 }
2094
2095 close(fdIn);
2096 close(fdOut);
2097 pEntry->New.cbCpp = cbAlloc - cbLeft;
2098 kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
2099
2100 /*
2101 * Write the precompiler output to disk and free the memory it
2102 * occupies while the compiler is busy compiling.
2103 */
2104 kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
2105}
2106
2107
2108/**
2109 * Performs pre-compile and compile in one go (typical clean build scenario).
2110 *
2111 * @param pEntry The cache entry.
2112 * @param papszArgvPreComp The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
2113 * @param cArgvPreComp The number of arguments.
2114 */
2115static void kOCEntryPreCompileAndCompile(PKOCENTRY pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp)
2116{
2117 if ( pEntry->fPipedCompile
2118 && pEntry->fPipedPreComp)
2119 {
2120 /*
2121 * Clean up old stuff first.
2122 */
2123 if (pEntry->Old.pszObjName)
2124 UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir);
2125 if (pEntry->New.pszObjName)
2126 UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir);
2127 if (pEntry->Old.pszCppName)
2128 UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir);
2129 if (pEntry->New.pszCppName)
2130 UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
2131
2132 /*
2133 * Do the actual compile and write the precompiler output to disk.
2134 */
2135 kOCEntrySpawnTee(pEntry, papszArgvPreComp, cArgvPreComp,
2136 pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile,
2137 "precompile|compile", kOCEntryTeeConsumer);
2138 }
2139 else
2140 {
2141 kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
2142 kOCEntryCompileIt(pEntry);
2143 }
2144}
2145
2146
2147/**
2148 * Check whether the string is a '#line' statement.
2149 *
2150 * @returns 1 if it is, 0 if it isn't.
2151 * @param psz The line to examin.
2152 * @parma piLine Where to store the line number.
2153 * @parma ppszFile Where to store the start of the filename.
2154 */
2155static int kOCEntryIsLineStatement(const char *psz, unsigned *piLine, const char **ppszFile)
2156{
2157 unsigned iLine;
2158
2159 /* Expect a hash. */
2160 if (*psz++ != '#')
2161 return 0;
2162
2163 /* Skip blanks between '#' and the line / number */
2164 while (*psz == ' ' || *psz == '\t')
2165 psz++;
2166
2167 /* Skip the 'line' if present. */
2168 if (!strncmp(psz, "line", sizeof("line") - 1))
2169 psz += sizeof("line");
2170
2171 /* Expect a line number now. */
2172 if ((unsigned char)(*psz - '0') > 9)
2173 return 0;
2174 iLine = 0;
2175 do
2176 {
2177 iLine *= 10;
2178 iLine += (*psz - '0');
2179 psz++;
2180 }
2181 while ((unsigned char)(*psz - '0') <= 9);
2182
2183 /* Expect one or more space now. */
2184 if (*psz != ' ' && *psz != '\t')
2185 return 0;
2186 do psz++;
2187 while (*psz == ' ' || *psz == '\t');
2188
2189 /* that's good enough. */
2190 *piLine = iLine;
2191 *ppszFile = psz;
2192 return 1;
2193}
2194
2195
2196/**
2197 * Scan backwards for the previous #line statement.
2198 *
2199 * @returns The filename in the previous statement.
2200 * @param pszStart Where to start.
2201 * @param pszStop Where to stop. Less than pszStart.
2202 * @param piLine The line number count to adjust.
2203 */
2204static const char *kOCEntryFindFileStatement(const char *pszStart, const char *pszStop, unsigned *piLine)
2205{
2206 unsigned iLine = *piLine;
2207 assert(pszStart >= pszStop);
2208 while (pszStart >= pszStop)
2209 {
2210 if (*pszStart == '\n')
2211 iLine++;
2212 else if (*pszStart == '#')
2213 {
2214 unsigned iLineTmp;
2215 const char *pszFile;
2216 const char *psz = pszStart - 1;
2217 while (psz >= pszStop && (*psz == ' ' || *psz =='\t'))
2218 psz--;
2219 if ( (psz < pszStop || *psz == '\n')
2220 && kOCEntryIsLineStatement(pszStart, &iLineTmp, &pszFile))
2221 {
2222 *piLine = iLine + iLineTmp - 1;
2223 return pszFile;
2224 }
2225 }
2226 pszStart--;
2227 }
2228 return NULL;
2229}
2230
2231
2232/**
2233 * Worker for kOCEntryCompareOldAndNewOutput() that compares the
2234 * precompiled output using a fast but not very good method.
2235 *
2236 * @returns 1 if matching, 0 if not matching.
2237 * @param pEntry The entry containing the names of the files to compare.
2238 * The entry is not updated in any way.
2239 */
2240static int kOCEntryCompareFast(PCKOCENTRY pEntry)
2241{
2242 const char * psz1 = pEntry->New.pszCppMapping;
2243 const char * const pszEnd1 = psz1 + pEntry->New.cbCpp;
2244 const char * psz2 = pEntry->Old.pszCppMapping;
2245 const char * const pszEnd2 = psz2 + pEntry->Old.cbCpp;
2246
2247 assert(*pszEnd1 == '\0');
2248 assert(*pszEnd2 == '\0');
2249
2250 /*
2251 * Iterate block by block and backtrack when we find a difference.
2252 */
2253 for (;;)
2254 {
2255 size_t cch = pszEnd1 - psz1;
2256 if (cch > (size_t)(pszEnd2 - psz2))
2257 cch = pszEnd2 - psz2;
2258 if (cch > 4096)
2259 cch = 4096;
2260 if ( cch
2261 && !memcmp(psz1, psz2, cch))
2262 {
2263 /* no differences */
2264 psz1 += cch;
2265 psz2 += cch;
2266 }
2267 else
2268 {
2269 /*
2270 * Pinpoint the difference exactly and the try find the start
2271 * of that line. Then skip forward until we find something to
2272 * work on that isn't spaces, #line statements or closing curly
2273 * braces.
2274 *
2275 * The closing curly braces are ignored because they are frequently
2276 * found at the end of header files (__END_DECLS) and the worst
2277 * thing that may happen if it isn't one of these braces we're
2278 * ignoring is that the final line in a function block is a little
2279 * bit off in the debug info.
2280 *
2281 * Since we might be skipping a few new empty headers, it is
2282 * possible that we will omit this header from the dependencies
2283 * when using VCC. This might not be a problem, since it seems
2284 * we'll have to use the precompiler output to generate the deps
2285 * anyway.
2286 */
2287 const char *psz;
2288 const char *pszMismatch1;
2289 const char *pszFile1 = NULL;
2290 unsigned iLine1 = 0;
2291 unsigned cCurlyBraces1 = 0;
2292 const char *pszMismatch2;
2293 const char *pszFile2 = NULL;
2294 unsigned iLine2 = 0;
2295 unsigned cCurlyBraces2 = 0;
2296
2297 /* locate the difference. */
2298 while (cch >= 512 && !memcmp(psz1, psz2, 512))
2299 psz1 += 512, psz2 += 512, cch -= 512;
2300 while (cch >= 64 && !memcmp(psz1, psz2, 64))
2301 psz1 += 64, psz2 += 64, cch -= 64;
2302 while (*psz1 == *psz2 && cch > 0)
2303 psz1++, psz2++, cch--;
2304
2305 /* locate the start of that line. */
2306 psz = psz1;
2307 while ( psz > pEntry->New.pszCppMapping
2308 && psz[-1] != '\n')
2309 psz--;
2310 psz2 -= (psz1 - psz);
2311 pszMismatch2 = psz2;
2312 pszMismatch1 = psz1 = psz;
2313
2314 /* Parse the 1st file line by line. */
2315 while (psz1 < pszEnd1)
2316 {
2317 if (*psz1 == '\n')
2318 {
2319 psz1++;
2320 iLine1++;
2321 }
2322 else
2323 {
2324 psz = psz1;
2325 while (isspace(*psz) && *psz != '\n')
2326 psz++;
2327 if (*psz == '\n')
2328 {
2329 psz1 = psz + 1;
2330 iLine1++;
2331 }
2332 else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine1, &pszFile1))
2333 {
2334 psz1 = memchr(psz, '\n', pszEnd1 - psz);
2335 if (!psz1++)
2336 psz1 = pszEnd1;
2337 }
2338 else if (*psz == '}')
2339 {
2340 do psz++;
2341 while (isspace(*psz) && *psz != '\n');
2342 if (*psz == '\n')
2343 iLine1++;
2344 else if (psz != pszEnd1)
2345 break;
2346 cCurlyBraces1++;
2347 psz1 = psz;
2348 }
2349 else if (psz == pszEnd1)
2350 psz1 = psz;
2351 else /* found something that can be compared. */
2352 break;
2353 }
2354 }
2355
2356 /* Ditto for the 2nd file. */
2357 while (psz2 < pszEnd2)
2358 {
2359 if (*psz2 == '\n')
2360 {
2361 psz2++;
2362 iLine2++;
2363 }
2364 else
2365 {
2366 psz = psz2;
2367 while (isspace(*psz) && *psz != '\n')
2368 psz++;
2369 if (*psz == '\n')
2370 {
2371 psz2 = psz + 1;
2372 iLine2++;
2373 }
2374 else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine2, &pszFile2))
2375 {
2376 psz2 = memchr(psz, '\n', pszEnd2 - psz);
2377 if (!psz2++)
2378 psz2 = pszEnd2;
2379 }
2380 else if (*psz == '}')
2381 {
2382 do psz++;
2383 while (isspace(*psz) && *psz != '\n');
2384 if (*psz == '\n')
2385 iLine2++;
2386 else if (psz != pszEnd2)
2387 break;
2388 cCurlyBraces2++;
2389 psz2 = psz;
2390 }
2391 else if (psz == pszEnd2)
2392 psz2 = psz;
2393 else /* found something that can be compared. */
2394 break;
2395 }
2396 }
2397
2398 /* Match the number of ignored closing curly braces. */
2399 if (cCurlyBraces1 != cCurlyBraces2)
2400 return 0;
2401
2402 /* Reaching the end of any of them means the return statement can decide. */
2403 if ( psz1 == pszEnd1
2404 || psz2 == pszEnd2)
2405 break;
2406
2407 /* Match the current line. */
2408 psz = memchr(psz1, '\n', pszEnd1 - psz1);
2409 if (!psz)
2410 psz = pszEnd1;
2411 cch = psz - psz1;
2412 if (psz2 + cch > pszEnd2)
2413 break;
2414 if (memcmp(psz1, psz2, cch))
2415 break;
2416
2417 /* Check that we're at the same location now. */
2418 if (!pszFile1)
2419 pszFile1 = kOCEntryFindFileStatement(pszMismatch1, pEntry->New.pszCppMapping, &iLine1);
2420 if (!pszFile2)
2421 pszFile2 = kOCEntryFindFileStatement(pszMismatch2, pEntry->Old.pszCppMapping, &iLine2);
2422 if (pszFile1 && pszFile2)
2423 {
2424 if (iLine1 != iLine2)
2425 break;
2426 while (*pszFile1 == *pszFile2 && *pszFile1 != '\n' && *pszFile1)
2427 pszFile1++, pszFile2++;
2428 if (*pszFile1 != *pszFile2)
2429 break;
2430 }
2431 else if (pszFile1 || pszFile2)
2432 {
2433 assert(0); /* this shouldn't happen. */
2434 break;
2435 }
2436
2437 /* Try align psz1 on 8 or 4 bytes so at least one of the buffers are aligned. */
2438 psz1 += cch;
2439 psz2 += cch;
2440 if (cch >= ((uintptr_t)psz1 & 7))
2441 {
2442 psz2 -= ((uintptr_t)psz1 & 7);
2443 psz1 -= ((uintptr_t)psz1 & 7);
2444 }
2445 else if (cch >= ((uintptr_t)psz1 & 3))
2446 {
2447 psz2 -= ((uintptr_t)psz1 & 3);
2448 psz1 -= ((uintptr_t)psz1 & 3);
2449 }
2450 }
2451 }
2452
2453 return psz1 == pszEnd1
2454 && psz2 == pszEnd2;
2455}
2456
2457
2458/**
2459 * Worker for kOCEntryCompileIfNeeded that compares the
2460 * precompiled output.
2461 *
2462 * @returns 1 if matching, 0 if not matching.
2463 * @param pEntry The entry containing the names of the files to compare.
2464 * This will load the old cpp output (changing pszOldCppName and Old.cbCpp).
2465 */
2466static int kOCEntryCompareOldAndNewOutput(PKOCENTRY pEntry)
2467{
2468 /*
2469 * I may implement a more sophisticated alternative method later... maybe.
2470 */
2471 if (kOCEntryReadCppOutput(pEntry, &pEntry->Old, 1 /* nonfatal */) == -1)
2472 return 0;
2473 //if ()
2474 // return kOCEntryCompareBest(pEntry);
2475 return kOCEntryCompareFast(pEntry);
2476}
2477
2478
2479/**
2480 * Check if re-compilation is required.
2481 * This sets the fNeedCompile flag.
2482 *
2483 * @param pEntry The cache entry.
2484 */
2485static void kOCEntryCalcRecompile(PKOCENTRY pEntry)
2486{
2487 if (pEntry->fNeedCompiling)
2488 return;
2489
2490 /*
2491 * Check if the precompiler output differ in any significant way?
2492 */
2493 if (!kOCSumHasEqualInChain(&pEntry->Old.SumHead, &pEntry->New.SumHead))
2494 {
2495 InfoMsg(1, "no checksum match - comparing output\n");
2496 if (!kOCEntryCompareOldAndNewOutput(pEntry))
2497 pEntry->fNeedCompiling = 1;
2498 else
2499 kOCSumAddChain(&pEntry->New.SumHead, &pEntry->Old.SumHead);
2500 }
2501}
2502
2503
2504/**
2505 * Does this cache entry need compiling or what?
2506 *
2507 * @returns 1 if it does, 0 if it doesn't.
2508 * @param pEntry The cache entry in question.
2509 */
2510static int kOCEntryNeedsCompiling(PCKOCENTRY pEntry)
2511{
2512 return pEntry->fNeedCompiling;
2513}
2514
2515
2516/**
2517 * Worker function for kOCEntryCopy.
2518 *
2519 * @param pEntry The entry we're coping to, which pszTo is relative to.
2520 * @param pszTo The destination.
2521 * @param pszFrom The source. This path will be freed.
2522 */
2523static void kOCEntryCopyFile(PCKOCENTRY pEntry, const char *pszTo, char *pszSrc)
2524{
2525 char *pszDst = MakePathFromDirAndFile(pszTo, pEntry->pszDir);
2526 char *pszBuf = xmalloc(256 * 1024);
2527 char *psz;
2528 int fdSrc;
2529 int fdDst;
2530
2531 /*
2532 * Open the files.
2533 */
2534 fdSrc = open(pszSrc, O_RDONLY | O_BINARY);
2535 if (fdSrc == -1)
2536 FatalDie("failed to open '%s': %s\n", pszSrc, strerror(errno));
2537
2538 unlink(pszDst);
2539 fdDst = open(pszDst, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
2540 if (fdDst == -1)
2541 FatalDie("failed to create '%s': %s\n", pszDst, strerror(errno));
2542
2543 /*
2544 * Copy them.
2545 */
2546 for (;;)
2547 {
2548 /* read a chunk. */
2549 long cbRead = read(fdSrc, pszBuf, 256*1024);
2550 if (cbRead < 0)
2551 {
2552 if (errno == EINTR)
2553 continue;
2554 if (eof(fdSrc))
2555 break;
2556 FatalDie("read '%s' failed: %s\n", pszSrc, strerror(errno));
2557 }
2558
2559 /* write the chunk. */
2560 psz = pszBuf;
2561 do
2562 {
2563 long cbWritten = write(fdDst, psz, cbRead);
2564 if (cbWritten < 0)
2565 {
2566 if (errno == EINTR)
2567 continue;
2568 FatalDie("write '%s' failed: %s\n", pszSrc, strerror(errno));
2569 }
2570 psz += cbWritten;
2571 cbRead -= cbWritten;
2572 } while (cbRead > 0);
2573 }
2574
2575 /* cleanup */
2576 if (close(fdDst) != 0)
2577 FatalDie("closing '%s' failed: %s\n", pszDst, strerror(errno));
2578 close(fdSrc);
2579 free(pszBuf);
2580 free(pszDst);
2581 free(pszSrc);
2582}
2583
2584
2585/**
2586 * Copies the object (and whatever else) from one cache entry to another.
2587 *
2588 * This is called when a matching cache entry has been found and we don't
2589 * need to recompile anything.
2590 *
2591 * @param pEntry The entry to copy to.
2592 * @param pFrom The entry to copy from.
2593 */
2594static void kOCEntryCopy(PKOCENTRY pEntry, PCKOCENTRY pFrom)
2595{
2596 kOCEntryCopyFile(pEntry, pEntry->New.pszObjName,
2597 MakePathFromDirAndFile(pFrom->New.pszObjName
2598 ? pFrom->New.pszObjName : pFrom->Old.pszObjName,
2599 pFrom->pszDir));
2600}
2601
2602
2603/**
2604 * Gets the absolute path to the cache entry.
2605 *
2606 * @returns absolute path to the cache entry.
2607 * @param pEntry The cache entry in question.
2608 */
2609static const char *kOCEntryAbsPath(PCKOCENTRY pEntry)
2610{
2611 return pEntry->pszAbsPath;
2612}
2613
2614
2615
2616
2617
2618
2619/**
2620 * Digest of one cache entry.
2621 *
2622 * This contains all the information required to find a matching
2623 * cache entry without having to open each of the files.
2624 */
2625typedef struct KOCDIGEST
2626{
2627 /** The relative path to the entry. Optional if pszAbsPath is set. */
2628 char *pszRelPath;
2629 /** The absolute path to the entry. Optional if pszRelPath is set. */
2630 char *pszAbsPath;
2631 /** The target os/arch identifier. */
2632 char *pszTarget;
2633 /** A unique number assigned to the entry when it's (re)-inserted
2634 * into the cache. This is used for simple consitency checking. */
2635 uint32_t uKey;
2636 /** The checksum of the compile argument vector. */
2637 KOCSUM SumCompArgv;
2638 /** The list of precompiler output checksums that's . */
2639 KOCSUM SumHead;
2640} KOCDIGEST;
2641/** Pointer to a file digest. */
2642typedef KOCDIGEST *PKOCDIGEST;
2643/** Pointer to a const file digest. */
2644typedef KOCDIGEST *PCKOCDIGEST;
2645
2646
2647/**
2648 * Initializes the specified digest.
2649 *
2650 * @param pDigest The digest.
2651 */
2652static void kOCDigestInit(PKOCDIGEST pDigest)
2653{
2654 memset(pDigest, 0, sizeof(*pDigest));
2655 kOCSumInit(&pDigest->SumHead);
2656}
2657
2658
2659/**
2660 * Initializes the digest for the specified entry.
2661 *
2662 * @param pDigest The (uninitialized) digest.
2663 * @param pEntry The entry.
2664 */
2665static void kOCDigestInitFromEntry(PKOCDIGEST pDigest, PCKOCENTRY pEntry)
2666{
2667 kOCDigestInit(pDigest);
2668
2669 pDigest->uKey = pEntry->uKey;
2670
2671 kOCSumInit(&pDigest->SumCompArgv);
2672 if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv))
2673 kOCSumAdd(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv);
2674 else
2675 kOCSumAdd(&pDigest->SumCompArgv, &pEntry->Old.SumCompArgv);
2676
2677 kOCSumInit(&pDigest->SumHead);
2678 if (!kOCSumIsEmpty(&pEntry->New.SumHead))
2679 kOCSumAddChain(&pDigest->SumHead, &pEntry->New.SumHead);
2680 else
2681 kOCSumAddChain(&pDigest->SumHead, &pEntry->Old.SumHead);
2682
2683 /** @todo implement selective relative path support. */
2684 pDigest->pszRelPath = NULL;
2685 pDigest->pszAbsPath = xstrdup(kOCEntryAbsPath(pEntry));
2686}
2687
2688
2689/**
2690 * Purges a digest, freeing all resources and returning
2691 * it to the initial state.
2692 *
2693 * @param pDigest The digest.
2694 */
2695static void kOCDigestPurge(PKOCDIGEST pDigest)
2696{
2697 free(pDigest->pszRelPath);
2698 free(pDigest->pszAbsPath);
2699 free(pDigest->pszTarget);
2700 pDigest->pszTarget = pDigest->pszAbsPath = pDigest->pszRelPath = NULL;
2701 pDigest->uKey = 0;
2702 kOCSumDeleteChain(&pDigest->SumCompArgv);
2703 kOCSumDeleteChain(&pDigest->SumHead);
2704}
2705
2706
2707/**
2708 * Returns the absolute path to the entry, calculating
2709 * the path if necessary.
2710 *
2711 * @returns absolute path.
2712 * @param pDigest The digest.
2713 * @param pszDir The cache directory that it might be relative to.
2714 */
2715static const char *kOCDigestAbsPath(PCKOCDIGEST pDigest, const char *pszDir)
2716{
2717 if (!pDigest->pszAbsPath)
2718 {
2719 char *pszPath = MakePathFromDirAndFile(pDigest->pszRelPath, pszDir);
2720 ((PKOCDIGEST)pDigest)->pszAbsPath = AbsPath(pszPath);
2721 free(pszPath);
2722 }
2723 return pDigest->pszAbsPath;
2724}
2725
2726
2727/**
2728 * Checks that the digest matches the
2729 *
2730 * @returns 1 if valid, 0 if invalid in some way.
2731 *
2732 * @param pDigest The digest to validate.
2733 * @param pEntry What to validate it against.
2734 */
2735static int kOCDigestIsValid(PCKOCDIGEST pDigest, PCKOCENTRY pEntry)
2736{
2737 PCKOCSUM pSum;
2738 PCKOCSUM pSumEntry;
2739
2740 if (pDigest->uKey != pEntry->uKey)
2741 return 0;
2742
2743 if (!kOCSumIsEqual(&pDigest->SumCompArgv,
2744 kOCSumIsEmpty(&pEntry->New.SumCompArgv)
2745 ? &pEntry->Old.SumCompArgv : &pEntry->New.SumCompArgv))
2746 return 0;
2747
2748 if (strcmp(pDigest->pszTarget, pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget))
2749 return 0;
2750
2751 /* match the checksums */
2752 pSumEntry = kOCSumIsEmpty(&pEntry->New.SumHead)
2753 ? &pEntry->Old.SumHead : &pEntry->New.SumHead;
2754 for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext)
2755 if (!kOCSumHasEqualInChain(pSumEntry, pSum))
2756 return 0;
2757
2758 return 1;
2759}
2760
2761
2762
2763
2764
2765/**
2766 * The structure for the central cache entry.
2767 */
2768typedef struct KOBJCACHE
2769{
2770 /** The entry name. */
2771 const char *pszName;
2772 /** The dir that relative names in the digest are relative to. */
2773 char *pszDir;
2774 /** The absolute path. */
2775 char *pszAbsPath;
2776
2777 /** The cache file descriptor. */
2778 int fd;
2779 /** Whether it's currently locked or not. */
2780 unsigned fLocked;
2781 /** Whether the cache file is dirty and needs writing back. */
2782 unsigned fDirty;
2783 /** Whether this is a new cache or not. */
2784 unsigned fNewCache;
2785
2786 /** The cache file generation. */
2787 uint32_t uGeneration;
2788 /** The next valid key. (Determin at load time.) */
2789 uint32_t uNextKey;
2790
2791 /** Number of digests in paDigests. */
2792 unsigned cDigests;
2793 /** Array of digests for the KOCENTRY objects in the cache. */
2794 PKOCDIGEST paDigests;
2795
2796} KOBJCACHE;
2797/** Pointer to a cache. */
2798typedef KOBJCACHE *PKOBJCACHE;
2799/** Pointer to a const cache. */
2800typedef KOBJCACHE const *PCKOBJCACHE;
2801
2802
2803/**
2804 * Creates an empty cache.
2805 *
2806 * This doesn't touch the file system, it just create the data structure.
2807 *
2808 * @returns Pointer to a cache.
2809 * @param pszCacheFile The cache file.
2810 */
2811static PKOBJCACHE kObjCacheCreate(const char *pszCacheFile)
2812{
2813 PKOBJCACHE pCache;
2814 size_t off;
2815
2816 /*
2817 * Allocate an empty entry.
2818 */
2819 pCache = xmallocz(sizeof(*pCache));
2820 pCache->fd = -1;
2821
2822 /*
2823 * Setup the directory and cache file name.
2824 */
2825 pCache->pszAbsPath = AbsPath(pszCacheFile);
2826 pCache->pszName = FindFilenameInPath(pCache->pszAbsPath);
2827 off = pCache->pszName - pCache->pszAbsPath;
2828 if (!off)
2829 FatalDie("Failed to find abs path for '%s'!\n", pszCacheFile);
2830 pCache->pszDir = xmalloc(off - 1);
2831 memcpy(pCache->pszDir, pCache->pszAbsPath, off);
2832 pCache->pszDir[off - 1] = '\0';
2833
2834 return pCache;
2835}
2836
2837
2838/**
2839 * Purges the data in the cache object.
2840 *
2841 * @param pCache The cache object.
2842 */
2843static void kObjCachePurge(PKOBJCACHE pCache)
2844{
2845 while (pCache->cDigests > 0)
2846 kOCDigestPurge(&pCache->paDigests[--pCache->cDigests]);
2847 free(pCache->paDigests);
2848 pCache->paDigests = NULL;
2849 pCache->uGeneration = 0;
2850 pCache->uNextKey = 0;
2851}
2852
2853
2854/**
2855 * (Re-)reads the file.
2856 *
2857 * @param pCache The cache to (re)-read.
2858 */
2859static void kObjCacheRead(PKOBJCACHE pCache)
2860{
2861 FILE *pFile;
2862 unsigned i;
2863 int fBad = 0;
2864
2865 /*
2866 * Rewind the file and associate it with a buffered file stream.
2867 */
2868 if (lseek(pCache->fd, 0, SEEK_SET) == -1)
2869 FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno));
2870 pFile = fdopen(pCache->fd, "rb");
2871 if (!pFile)
2872 FatalDie("fdopen(cache-fd,rb) failed: %s\n", strerror(errno));
2873
2874 /*
2875 * Read magic and generation.
2876 */
2877 if ( !fgets(g_szLine, sizeof(g_szLine), pFile)
2878 || strcmp(g_szLine, "magic=kObjCache-v0.1.0\n"))
2879 {
2880 InfoMsg(1, "bad cache file (magic)\n");
2881 fBad = 1;
2882 }
2883 else if ( !fgets(g_szLine, sizeof(g_szLine), pFile)
2884 || strncmp(g_szLine, "generation=", sizeof("generation=") - 1))
2885 {
2886 InfoMsg(1, "bad cache file (generation)\n");
2887 fBad = 1;
2888 }
2889 else if ( pCache->uGeneration
2890 && pCache->uGeneration == atol(&g_szLine[sizeof("generation=") - 1]))
2891 {
2892 InfoMsg(1, "cache file unchanged\n");
2893 fBad = 0;
2894 }
2895 else
2896 {
2897 int fBadBeforeMissing;
2898
2899 /*
2900 * Read everything (anew).
2901 */
2902 kObjCachePurge(pCache);
2903 do
2904 {
2905 PKOCDIGEST pDigest;
2906 char *pszNl;
2907 char *pszVal;
2908 char *psz;
2909
2910 /* Split the line and drop the trailing newline. */
2911 pszVal = strchr(g_szLine, '=');
2912 if ((fBad = pszVal == NULL))
2913 break;
2914 *pszVal++ = '\0';
2915
2916 pszNl = strchr(pszVal, '\n');
2917 if (pszNl)
2918 *pszNl = '\0';
2919
2920 /* digest '#'? */
2921 psz = strchr(g_szLine, '#');
2922 if (psz)
2923 {
2924 i = strtoul(psz, &psz, 0);
2925 if ((fBad = psz && *psz))
2926 break;
2927 if ((fBad = i >= pCache->cDigests))
2928 break;
2929 pDigest = &pCache->paDigests[i];
2930 }
2931 else
2932 pDigest = NULL;
2933
2934
2935 /* string case on value name. */
2936 if (!strncmp(g_szLine, "sum-#", sizeof("sum-sum-#") - 1))
2937 {
2938 KOCSUM Sum;
2939 if ((fBad = kOCSumInitFromString(&Sum, pszVal) != 0))
2940 break;
2941 kOCSumAdd(&pDigest->SumHead, &Sum);
2942 }
2943 else if (!strncmp(g_szLine, "digest-abs-#", sizeof("digest-abs-#") - 1))
2944 {
2945 if ((fBad = pDigest->pszAbsPath != NULL))
2946 break;
2947 pDigest->pszAbsPath = xstrdup(pszVal);
2948 }
2949 else if (!strncmp(g_szLine, "digest-rel-#", sizeof("digest-rel-#") - 1))
2950 {
2951 if ((fBad = pDigest->pszRelPath != NULL))
2952 break;
2953 pDigest->pszRelPath = xstrdup(pszVal);
2954 }
2955 else if (!strncmp(g_szLine, "key-#", sizeof("key-#") - 1))
2956 {
2957 if ((fBad = pDigest->uKey != 0))
2958 break;
2959 pDigest->uKey = strtoul(pszVal, &psz, 0);
2960 if ((fBad = psz && *psz))
2961 break;
2962 if (pDigest->uKey >= pCache->uNextKey)
2963 pCache->uNextKey = pDigest->uKey + 1;
2964 }
2965 else if (!strncmp(g_szLine, "comp-argv-sum-#", sizeof("comp-argv-sum-#") - 1))
2966 {
2967 if ((fBad = !kOCSumIsEmpty(&pDigest->SumCompArgv)))
2968 break;
2969 if ((fBad = kOCSumInitFromString(&pDigest->SumCompArgv, pszVal) != 0))
2970 break;
2971 }
2972 else if (!strncmp(g_szLine, "target-#", sizeof("target-#") - 1))
2973 {
2974 if ((fBad = pDigest->pszTarget != NULL))
2975 break;
2976 pDigest->pszTarget = xstrdup(pszVal);
2977 }
2978 else if (!strcmp(g_szLine, "digests"))
2979 {
2980 if ((fBad = pCache->paDigests != NULL))
2981 break;
2982 pCache->cDigests = strtoul(pszVal, &psz, 0);
2983 if ((fBad = psz && *psz))
2984 break;
2985 i = (pCache->cDigests + 4) & ~3;
2986 pCache->paDigests = xmalloc(i * sizeof(pCache->paDigests[0]));
2987 for (i = 0; i < pCache->cDigests; i++)
2988 kOCDigestInit(&pCache->paDigests[i]);
2989 }
2990 else if (!strcmp(g_szLine, "generation"))
2991 {
2992 if ((fBad = pCache->uGeneration != 0))
2993 break;
2994 pCache->uGeneration = strtoul(pszVal, &psz, 0);
2995 if ((fBad = psz && *psz))
2996 break;
2997 }
2998 else if (!strcmp(g_szLine, "the-end"))
2999 {
3000 fBad = strcmp(pszVal, "fine");
3001 break;
3002 }
3003 else
3004 {
3005 fBad = 1;
3006 break;
3007 }
3008 } while (fgets(g_szLine, sizeof(g_szLine), pFile));
3009
3010 /*
3011 * Did we find everything?
3012 */
3013 fBadBeforeMissing = fBad;
3014 if ( !fBad
3015 && !pCache->uGeneration)
3016 fBad = 1;
3017 if (!fBad)
3018 for (i = 0; i < pCache->cDigests; i++)
3019 {
3020 if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumCompArgv)))
3021 break;
3022 if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumHead)))
3023 break;
3024 if ((fBad = pCache->paDigests[i].uKey == 0))
3025 break;
3026 if ((fBad = pCache->paDigests[i].pszAbsPath == NULL
3027 && pCache->paDigests[i].pszRelPath == NULL))
3028 break;
3029 if ((fBad = pCache->paDigests[i].pszTarget == NULL))
3030 break;
3031 }
3032 if (fBad)
3033 InfoMsg(1, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff");
3034 else if (ferror(pFile))
3035 {
3036 InfoMsg(1, "cache file read error\n");
3037 fBad = 1;
3038 }
3039 }
3040 if (fBad)
3041 {
3042 kObjCachePurge(pCache);
3043 pCache->fNewCache = 1;
3044 }
3045
3046 /*
3047 * Close the stream.
3048 */
3049 fclose(pFile);
3050}
3051
3052
3053/**
3054 * Re-writes the cache file.
3055 *
3056 * @param pCache The cache to commit and unlock.
3057 */
3058static void kObjCacheWrite(PKOBJCACHE pCache)
3059{
3060 FILE *pFile;
3061 unsigned i;
3062 off_t cb;
3063 assert(pCache->fLocked);
3064 assert(pCache->fDirty);
3065
3066 /*
3067 * Rewind the file and associate it with a buffered file stream.
3068 */
3069 if (lseek(pCache->fd, 0, SEEK_SET) == -1)
3070 FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno));
3071 pFile = fdopen(pCache->fd, "w+b");
3072 if (!pFile)
3073 FatalDie("fdopen(cache-fd,w+b) failed: %s\n", strerror(errno));
3074
3075 /*
3076 * Write the header.
3077 */
3078 pCache->uGeneration++;
3079 fprintf(pFile,
3080 "magic=kObjCache-v0.1.0\n"
3081 "generation=%d\n"
3082 "digests=%d\n",
3083 pCache->uGeneration,
3084 pCache->cDigests);
3085
3086 /*
3087 * Write the digests.
3088 */
3089 for (i = 0; pCache->cDigests; i++)
3090 {
3091 PCKOCDIGEST pDigest = &pCache->paDigests[i];
3092 PKOCSUM pSum;
3093
3094 if (pDigest->pszAbsPath)
3095 fprintf(pFile, "digest-abs-#%u=%s\n", i, pDigest->pszAbsPath);
3096 if (pDigest->pszRelPath)
3097 fprintf(pFile, "digest-rel-#%u=%s\n", i, pDigest->pszRelPath);
3098 fprintf(pFile, "key-#%u=%u\n", i, pDigest->uKey);
3099 fprintf(pFile, "target-#%u=%s\n", i, pDigest->pszTarget);
3100 fprintf(pFile, "comp-argv-sum-#%u=", i);
3101 kOCSumFPrintf(&pDigest->SumCompArgv, pFile);
3102 for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext)
3103 {
3104 fprintf(pFile, "sum-#%u=", i);
3105 kOCSumFPrintf(pSum, pFile);
3106 }
3107 }
3108
3109 /*
3110 * Close the stream and unlock fhe file.
3111 * (Closing the stream shouldn't close the file handle IIRC...)
3112 */
3113 fprintf(pFile, "the-end=fine\n");
3114 fflush(pFile);
3115 if ( fflush(pFile) < 0
3116 || ferror(pFile))
3117 {
3118 int iErr = errno;
3119 fclose(pFile);
3120 UnlinkFileInDir(pCache->pszName, pCache->pszDir);
3121 FatalDie("Stream error occured while writing '%s' in '%s': %s\n",
3122 pCache->pszName, pCache->pszDir, strerror(iErr));
3123 }
3124 fclose(pFile);
3125
3126 cb = lseek(pCache->fd, 0, SEEK_CUR);
3127 if (cb == -1)
3128 FatalDie("lseek(cache-file,0,CUR) failed: %s\n", strerror(errno));
3129#if defined(__WIN__)
3130 if (_chsize(pCache->fd, cb) == -1)
3131#else
3132 if (ftruncate(pCache->fd, cb) == -1)
3133#endif
3134 FatalDie("file truncation failed: %s\n", strerror(errno));
3135 InfoMsg(1, "Wrote '%s' in '%s', %d bytes\n", pCache->pszName, pCache->pszDir, cb);
3136}
3137
3138
3139/**
3140 * Cleans out all invalid digests.s
3141 *
3142 * This is done periodically from the unlock routine to make
3143 * sure we don't accidentally accumulate stale digests.
3144 *
3145 * @param pCache The cache to chek.
3146 */
3147static void kObjCacheClean(PKOBJCACHE pCache)
3148{
3149 unsigned i = pCache->cDigests;
3150 while (i-- > 0)
3151 {
3152 /*
3153 * Try open it and purge it if it's bad.
3154 * (We don't kill the entry file because that's kmk clean's job.)
3155 */
3156 PCKOCDIGEST pDigest = &pCache->paDigests[i];
3157 PKOCENTRY pEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir));
3158 kOCEntryRead(pEntry);
3159 if ( !kOCEntryCheck(pEntry)
3160 || !kOCDigestIsValid(pDigest, pEntry))
3161 {
3162 unsigned cLeft;
3163 kOCDigestPurge(pDigest);
3164
3165 pCache->cDigests--;
3166 cLeft = pCache->cDigests - i;
3167 if (cLeft)
3168 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3169
3170 pCache->fDirty = 1;
3171 }
3172 kOCEntryDestroy(pEntry);
3173 }
3174}
3175
3176
3177/**
3178 * Locks the cache for exclusive access.
3179 *
3180 * This will open the file if necessary and lock the entire file
3181 * using the best suitable platform API (tricky).
3182 *
3183 * @param pCache The cache to lock.
3184 */
3185static void kObjCacheLock(PKOBJCACHE pCache)
3186{
3187 struct stat st;
3188 assert(!pCache->fLocked);
3189
3190 /*
3191 * Open it?
3192 */
3193 if (pCache->fd < 0)
3194 {
3195 pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666);
3196 if (pCache->fd == -1)
3197 {
3198 MakePath(pCache->pszDir);
3199 pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666);
3200 if (pCache->fd == -1)
3201 FatalDie("Failed to create '%s' in '%s': %s\n", pCache->pszName, pCache->pszDir, strerror(errno));
3202 }
3203 }
3204
3205 /*
3206 * Lock it.
3207 */
3208#if defined(__WIN__)
3209 if (!LockFileEx((HANDLE)_get_osfhandle(pCache->fd), LOCKFILE_EXCLUSIVE_LOCK, 0, ~0, 0, NULL))
3210 FatalDie("Failed to lock the cache file: Windows Error %d\n", GetLastError());
3211#else
3212# error port me....
3213#endif
3214 pCache->fLocked = 1;
3215
3216 /*
3217 * Check for new cache and read it it's an existing cache.
3218 *
3219 * There is no point in initializing a new cache until we've finished
3220 * compiling and has something to put into it, so we'll leave it as a
3221 * 0 byte file.
3222 */
3223 if (fstat(pCache->fd, &st) == -1)
3224 FatalDie("fstat(cache-fd) failed: %s\n", strerror(errno));
3225 if (st.st_size)
3226 kObjCacheRead(pCache);
3227 else
3228 pCache->fNewCache = 1;
3229}
3230
3231
3232/**
3233 * Unlocks the cache (without writing anything back).
3234 *
3235 * @param pCache The cache to unlock.
3236 */
3237static void kObjCacheUnlock(PKOBJCACHE pCache)
3238{
3239 assert(pCache->fLocked);
3240
3241 /*
3242 * Write it back if it's dirty.
3243 */
3244 if (pCache->fDirty)
3245 {
3246 if ( pCache->cDigests >= 16
3247 && (pCache->uGeneration % 19) == 19)
3248 kObjCacheClean(pCache);
3249 kObjCacheWrite(pCache);
3250 pCache->fDirty = 0;
3251 }
3252
3253 /*
3254 * Lock it.
3255 */
3256#if defined(__WIN__)
3257 if (!UnlockFileEx((HANDLE)_get_osfhandle(pCache->fd), 0, ~0U, 0, NULL))
3258 FatalDie("Failed to unlock the cache file: Windows Error %d\n", GetLastError());
3259#else
3260# error port me....
3261#endif
3262 pCache->fLocked = 0;
3263}
3264
3265
3266/**
3267 * Removes the entry from the cache.
3268 *
3269 * The entry doesn't need to be in the cache.
3270 * The cache entry (file) itself is not touched.
3271 *
3272 * @param pCache The cache.
3273 * @param pEntry The entry.
3274 */
3275static void kObjCacheRemoveEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
3276{
3277 unsigned i = pCache->cDigests;
3278 while (i-- > 0)
3279 {
3280 PKOCDIGEST pDigest = &pCache->paDigests[i];
3281 if (ArePathsIdentical(kOCDigestAbsPath(pDigest, pCache->pszDir),
3282 kOCEntryAbsPath(pEntry), ~0U))
3283 {
3284 unsigned cLeft;
3285 kOCDigestPurge(pDigest);
3286
3287 pCache->cDigests--;
3288 cLeft = pCache->cDigests - i;
3289 if (cLeft)
3290 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3291
3292 pCache->fDirty = 1;
3293 }
3294 }
3295}
3296
3297
3298/**
3299 * Inserts the entry into the cache.
3300 *
3301 * The cache entry (file) itself is not touched by this operation,
3302 * the pEntry object otoh is.
3303 *
3304 * @param pCache The cache.
3305 * @param pEntry The entry.
3306 */
3307static void kObjCacheInsertEntry(PKOBJCACHE pCache, PKOCENTRY pEntry)
3308{
3309 unsigned i;
3310
3311 /*
3312 * Find a new key.
3313 */
3314 pEntry->uKey = pCache->uNextKey++;
3315 i = pCache->cDigests;
3316 while (i-- > 0)
3317 if (pCache->paDigests[i].uKey == pEntry->uKey)
3318 {
3319 pEntry->uKey = pCache->uNextKey++;
3320 if (!pEntry->uKey)
3321 pEntry->uKey = pCache->uNextKey++;
3322 i = pCache->cDigests;
3323 }
3324
3325 /*
3326 * Reallocate the digest array?
3327 */
3328 if ( !(pCache->cDigests & 3)
3329 && (pCache->cDigests || !pCache->paDigests))
3330 pCache->paDigests = xrealloc(pCache->paDigests, sizeof(pCache->paDigests[0]) * pCache->cDigests + 4);
3331
3332 /*
3333 * Create a new digest.
3334 */
3335 i = pCache->cDigests++;
3336 kOCDigestInitFromEntry(&pCache->paDigests[i], pEntry);
3337
3338 pCache->fDirty = 1;
3339}
3340
3341
3342/**
3343 * Find a matching cache entry.
3344 */
3345static PKOCENTRY kObjCacheFindMatchingEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
3346{
3347 unsigned i = pCache->cDigests;
3348
3349 assert(pEntry->fNeedCompiling);
3350 assert(!kOCSumIsEmpty(&pEntry->New.SumCompArgv));
3351 assert(!kOCSumIsEmpty(&pEntry->New.SumHead));
3352
3353 while (i-- > 0)
3354 {
3355 /*
3356 * Matching?
3357 */
3358 PCKOCDIGEST pDigest = &pCache->paDigests[i];
3359 if ( kOCSumIsEqual(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv)
3360 && kOCSumHasEqualInChain(&pDigest->SumHead, &pEntry->New.SumHead))
3361 {
3362 /*
3363 * Try open it.
3364 */
3365 unsigned cLeft;
3366 PKOCENTRY pRetEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir));
3367 kOCEntryRead(pRetEntry);
3368 if ( kOCEntryCheck(pRetEntry)
3369 && kOCDigestIsValid(pDigest, pRetEntry))
3370 return pRetEntry;
3371 kOCEntryDestroy(pRetEntry);
3372
3373 /* bad entry, purge it. */
3374 kOCDigestPurge(pDigest);
3375
3376 pCache->cDigests--;
3377 cLeft = pCache->cDigests - i;
3378 if (cLeft)
3379 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3380
3381 pCache->fDirty = 1;
3382 }
3383 }
3384
3385 return NULL;
3386}
3387
3388
3389/**
3390 * Is this a new cache?
3391 *
3392 * @returns 1 if new, 0 if not new.
3393 * @param pEntry The entry.
3394 */
3395static int kObjCacheIsNew(PKOBJCACHE pCache)
3396{
3397 return pCache->fNewCache;
3398}
3399
3400
3401/**
3402 * Prints a syntax error and returns the appropriate exit code
3403 *
3404 * @returns approriate exit code.
3405 * @param pszFormat The syntax error message.
3406 * @param ... Message args.
3407 */
3408static int SyntaxError(const char *pszFormat, ...)
3409{
3410 va_list va;
3411 fprintf(stderr, "kObjCache: syntax error: ");
3412 va_start(va, pszFormat);
3413 vfprintf(stderr, pszFormat, va);
3414 va_end(va);
3415 return 1;
3416}
3417
3418
3419/**
3420 * Prints the usage.
3421 * @returns 0.
3422 */
3423static int usage(void)
3424{
3425 printf("syntax: kObjCache [--kObjCache-options] [-v|--verbose]\n"
3426 " < [-c|--cache-file <cache-file>]\n"
3427 " | [-n|--name <name-in-cache>] [[-d|--cache-dir <cache-dir>]] >\n"
3428 " <-f|--file <local-cache-file>>\n"
3429 " <-t|--target <target-name>>\n"
3430 " [-r|--redir-stdout] [-p|--passthru]\n"
3431 " --kObjCache-cpp <filename> <precompiler + args>\n"
3432 " --kObjCache-cc <object> <compiler + args>\n"
3433 " [--kObjCache-both [args]]\n"
3434 " [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
3435 " kObjCache <-V|--version>\n"
3436 " kObjCache [-?|/?|-h|/h|--help|/help]\n"
3437 "\n"
3438 "The env.var. KOBJCACHE_DIR sets the default cache diretory (-d).\n"
3439 "The env.var. KOBJCACHE_OPTS allow you to specifie additional options\n"
3440 "without having to mess with the makefiles. These are appended with "
3441 "a --kObjCache-options between them and the command args.\n"
3442 "\n");
3443 return 0;
3444}
3445
3446
3447int main(int argc, char **argv)
3448{
3449 PKOBJCACHE pCache;
3450 PKOCENTRY pEntry;
3451
3452 const char *pszCacheDir = getenv("KOBJCACHE_DIR");
3453 const char *pszCacheName = NULL;
3454 const char *pszCacheFile = NULL;
3455 const char *pszEntryFile = NULL;
3456
3457 const char **papszArgvPreComp = NULL;
3458 unsigned cArgvPreComp = 0;
3459 const char *pszPreCompName = NULL;
3460 int fRedirPreCompStdOut = 0;
3461
3462 const char **papszArgvCompile = NULL;
3463 unsigned cArgvCompile = 0;
3464 const char *pszObjName = NULL;
3465 int fRedirCompileStdIn = 0;
3466
3467 const char *pszTarget = NULL;
3468
3469 enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
3470
3471 char *psz;
3472 int i;
3473
3474 SetErrorPrefix("kObjCache");
3475
3476 /*
3477 * Arguments passed in the environmnet?
3478 */
3479 psz = getenv("KOBJCACHE_OPTS");
3480 if (psz)
3481 AppendArgs(&argc, &argv, psz, "--kObjCache-options");
3482
3483 /*
3484 * Parse the arguments.
3485 */
3486 if (argc <= 1)
3487 return usage();
3488 for (i = 1; i < argc; i++)
3489 {
3490 if (!strcmp(argv[i], "--kObjCache-cpp"))
3491 {
3492 enmMode = kOC_CppArgv;
3493 if (!pszPreCompName)
3494 {
3495 if (++i >= argc)
3496 return SyntaxError("--kObjCache-cpp requires an object filename!\n");
3497 pszPreCompName = argv[i];
3498 }
3499 }
3500 else if (!strcmp(argv[i], "--kObjCache-cc"))
3501 {
3502 enmMode = kOC_CcArgv;
3503 if (!pszObjName)
3504 {
3505 if (++i >= argc)
3506 return SyntaxError("--kObjCache-cc requires an precompiler output filename!\n");
3507 pszObjName = argv[i];
3508 }
3509 }
3510 else if (!strcmp(argv[i], "--kObjCache-both"))
3511 enmMode = kOC_BothArgv;
3512 else if (!strcmp(argv[i], "--kObjCache-options"))
3513 enmMode = kOC_Options;
3514 else if (!strcmp(argv[i], "--help"))
3515 return usage();
3516 else if (enmMode != kOC_Options)
3517 {
3518 if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv)
3519 {
3520 if (!(cArgvPreComp % 16))
3521 papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0]));
3522 papszArgvPreComp[cArgvPreComp++] = argv[i];
3523 papszArgvPreComp[cArgvPreComp] = NULL;
3524 }
3525 if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv)
3526 {
3527 if (!(cArgvCompile % 16))
3528 papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0]));
3529 papszArgvCompile[cArgvCompile++] = argv[i];
3530 papszArgvCompile[cArgvCompile] = NULL;
3531 }
3532 }
3533 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--entry-file"))
3534 {
3535 if (i + 1 >= argc)
3536 return SyntaxError("%s requires a cache entry filename!\n", argv[i]);
3537 pszEntryFile = argv[++i];
3538 }
3539 else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache-file"))
3540 {
3541 if (i + 1 >= argc)
3542 return SyntaxError("%s requires a cache filename!\n", argv[i]);
3543 pszCacheFile = argv[++i];
3544 }
3545 else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--name"))
3546 {
3547 if (i + 1 >= argc)
3548 return SyntaxError("%s requires a cache name!\n", argv[i]);
3549 pszCacheName = argv[++i];
3550 }
3551 else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--cache-dir"))
3552 {
3553 if (i + 1 >= argc)
3554 return SyntaxError("%s requires a cache directory!\n", argv[i]);
3555 pszCacheDir = argv[++i];
3556 }
3557 else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--target"))
3558 {
3559 if (i + 1 >= argc)
3560 return SyntaxError("%s requires a target platform/arch name!\n", argv[i]);
3561 pszTarget = argv[++i];
3562 }
3563 else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--passthru"))
3564 fRedirPreCompStdOut = fRedirCompileStdIn = 1;
3565 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
3566 fRedirPreCompStdOut = 1;
3567 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
3568 g_cVerbosityLevel++;
3569 else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
3570 g_cVerbosityLevel = 0;
3571 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?")
3572 || !strcmp(argv[i], "/h") || !strcmp(argv[i], "/?") || !strcmp(argv[i], "/help"))
3573 return usage();
3574 else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version"))
3575 {
3576 printf("kObjCache v0.0.0 ($Revision: 1041 $)\n");
3577 return 0;
3578 }
3579 else
3580 return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
3581 }
3582 if (!pszEntryFile)
3583 return SyntaxError("No cache entry filename (-f)!\n");
3584 if (!pszTarget)
3585 return SyntaxError("No target name (-t)!\n");
3586 if (!cArgvCompile)
3587 return SyntaxError("No compiler arguments (--kObjCache-cc)!\n");
3588 if (!cArgvPreComp)
3589 return SyntaxError("No precompiler arguments (--kObjCache-cc)!\n");
3590
3591 /*
3592 * Calc the cache file name.
3593 */
3594 if (!pszCacheFile)
3595 {
3596 if (!pszCacheDir)
3597 return SyntaxError("No cache dir (-d / KOBJCACHE_DIR) and no cache filename!\n");
3598 if (!pszCacheName)
3599 {
3600 pszCacheName = FindFilenameInPath(pszCacheFile);
3601 if (!*pszCacheName)
3602 return SyntaxError("The cache file (-f) specifies a directory / nothing!\n");
3603 pszCacheName = xstrdup(pszCacheName);
3604 psz = strrchr(pszCacheName, '.');
3605 if (psz > pszCacheName)
3606 *psz = '\0';
3607 }
3608 pszCacheFile = MakePathFromDirAndFile(pszCacheDir, pszCacheName); /* harmless leak. */
3609 }
3610
3611 /*
3612 * Create and initialize the two bojects we'll be working on.
3613 *
3614 * We're supposed to be the only ones actually writing to the local file,
3615 * so it's perfectly fine to read it here before we lock it. This simplifies
3616 * the detection of object name and compiler argument changes.
3617 */
3618 SetErrorPrefix("kObjCache - %s", FindFilenameInPath(pszCacheFile));
3619 pCache = kObjCacheCreate(pszCacheFile);
3620
3621 pEntry = kOCEntryCreate(pszCacheFile);
3622 kOCEntryRead(pEntry);
3623 kOCEntrySetCompileArgv(pEntry, papszArgvCompile, cArgvCompile);
3624 kOCEntrySetCompileObjName(pEntry, pszObjName);
3625 kOCEntrySetTarget(pEntry, pszTarget);
3626 kOCEntrySetCppName(pEntry, pszPreCompName);
3627 kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn);
3628
3629 /*
3630 * Open (& lock) the two files and do validity checks and such.
3631 */
3632 kObjCacheLock(pCache);
3633 if ( kObjCacheIsNew(pCache)
3634 && kOCEntryNeedsCompiling(pEntry))
3635 {
3636 /*
3637 * Both files are missing/invalid.
3638 * Optimize this path as it is frequently used when making a clean build.
3639 */
3640 kOCEntryPreCompileAndCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3641 }
3642 else
3643 {
3644 /*
3645 * Do the precompile (don't need to lock the cache file for this).
3646 */
3647 kObjCacheUnlock(pCache);
3648 kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3649
3650 /*
3651 * Check if we need to recompile. If we do, try see if the is a cache entry first.
3652 */
3653 kOCEntryCalcRecompile(pEntry);
3654 if (kOCEntryNeedsCompiling(pEntry))
3655 {
3656 PKOCENTRY pUseEntry;
3657 kObjCacheLock(pCache);
3658 kObjCacheRemoveEntry(pCache, pEntry);
3659 pUseEntry = kObjCacheFindMatchingEntry(pCache, pEntry);
3660 if (pUseEntry)
3661 {
3662 kOCEntryCopy(pEntry, pUseEntry);
3663 kOCEntryDestroy(pUseEntry);
3664 }
3665 else
3666 {
3667 kObjCacheUnlock(pCache);
3668 kOCEntryCompileIt(pEntry);
3669 kObjCacheLock(pCache);
3670 }
3671 }
3672 }
3673
3674 /*
3675 * Update the cache files.
3676 */
3677 kObjCacheInsertEntry(pCache, pEntry);
3678 kOCEntryWrite(pEntry);
3679 kObjCacheUnlock(pCache);
3680 return 0;
3681}
3682
3683
3684/** @page kObjCache Benchmarks.
3685 *
3686 * 2007-06-02 - 21-23:00:
3687 * Mac OS X debug -j 3 clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
3688 * real 10m26.077s
3689 * user 13m13.291s
3690 * sys 2m58.193s
3691 *
3692 * Mac OS X debug -j 3 depend build (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
3693 * real 3m55.275s
3694 * user 4m11.852s
3695 * sys 0m54.931s
3696 *
3697 * Mac OS X debug -j 3 cached clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
3698 * real 11m42.513s
3699 * user 14m27.736s
3700 * sys 3m39.512s
3701 *
3702 * Mac OS X debug -j 3 cached depend build (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
3703 * real 1m17.445s
3704 * user 1m13.410s
3705 * sys 0m22.789s
3706 *
3707 * Mac OS X debug -j3 cached depend build (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
3708 * real 1m29.315s
3709 * user 1m31.391s
3710 * sys 0m32.748s
3711 *
3712 */
Note: See TracBrowser for help on using the repository browser.