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

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

Extending the cache to. The code doesn't work (haven't even run it yet) and is therefore disabled.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 107.4 KB
Line 
1/* $Id: kObjCache.c 1040 2007-06-08 02:08:10Z 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 memcmp(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 memcmp(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 memcmp(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 " [-d|--cache-dir <cache-dir>] [-n|--name <name-in-cache>]\n"
3427 " [-f|--file <local-cache-file>] [-t|--target <target-name>\n"
3428 " [-r|--redir-stdout] [-p|--passthru]\n"
3429 " --kObjCache-cpp <filename> <precompiler + args>\n"
3430 " --kObjCache-cc <object> <compiler + args>\n"
3431 " [--kObjCache-both [args]]\n"
3432 " [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
3433 " kObjCache <-V|--version>\n"
3434 " kObjCache [-?|-h|--help]\n"
3435 "\n"
3436 "The env.var. KOBJCACHE_DIR sets the default cache diretory (-d).\n"
3437 "The env.var. KOBJCACHE_OPTS allow you to specifie additional options\n"
3438 "without having to mess with the makefiles. These are appended with "
3439 "a --kObjCache-options between them and the command args.\n"
3440 "\n");
3441 return 0;
3442}
3443
3444
3445int main(int argc, char **argv)
3446{
3447 PKOBJCACHE pCache;
3448 PKOCENTRY pEntry;
3449
3450 const char *pszCacheDir = getenv("KOBJCACHE_DIR");
3451 const char *pszCacheName = NULL;
3452 const char *pszCacheFile = NULL;
3453 const char *pszEntryFile = NULL;
3454
3455 const char **papszArgvPreComp = NULL;
3456 unsigned cArgvPreComp = 0;
3457 const char *pszPreCompName = NULL;
3458 int fRedirPreCompStdOut = 0;
3459
3460 const char **papszArgvCompile = NULL;
3461 unsigned cArgvCompile = 0;
3462 const char *pszObjName = NULL;
3463 int fRedirCompileStdIn = 0;
3464
3465 const char *pszTarget = NULL;
3466
3467 enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
3468
3469 char *psz;
3470 int i;
3471
3472 SetErrorPrefix("kObjCache");
3473
3474 /*
3475 * Arguments passed in the environmnet?
3476 */
3477 psz = getenv("KOBJCACHE_OPTS");
3478 if (psz)
3479 AppendArgs(&argc, &argv, psz, "--kObjCache-options");
3480
3481 /*
3482 * Parse the arguments.
3483 */
3484 for (i = 1; i < argc; i++)
3485 {
3486 if (!strcmp(argv[i], "--kObjCache-cpp"))
3487 {
3488 enmMode = kOC_CppArgv;
3489 if (!pszPreCompName)
3490 {
3491 if (++i >= argc)
3492 return SyntaxError("--kObjCache-cpp requires an object filename!\n");
3493 pszPreCompName = argv[i];
3494 }
3495 }
3496 else if (!strcmp(argv[i], "--kObjCache-cc"))
3497 {
3498 enmMode = kOC_CcArgv;
3499 if (!pszObjName)
3500 {
3501 if (++i >= argc)
3502 return SyntaxError("--kObjCache-cc requires an precompiler output filename!\n");
3503 pszObjName = argv[i];
3504 }
3505 }
3506 else if (!strcmp(argv[i], "--kObjCache-both"))
3507 enmMode = kOC_BothArgv;
3508 else if (!strcmp(argv[i], "--kObjCache-options"))
3509 enmMode = kOC_Options;
3510 else if (!strcmp(argv[i], "--help"))
3511 return usage();
3512 else if (enmMode != kOC_Options)
3513 {
3514 if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv)
3515 {
3516 if (!(cArgvPreComp % 16))
3517 papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0]));
3518 papszArgvPreComp[cArgvPreComp++] = argv[i];
3519 papszArgvPreComp[cArgvPreComp] = NULL;
3520 }
3521 if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv)
3522 {
3523 if (!(cArgvCompile % 16))
3524 papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0]));
3525 papszArgvCompile[cArgvCompile++] = argv[i];
3526 papszArgvCompile[cArgvCompile] = NULL;
3527 }
3528 }
3529 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--entry-file"))
3530 {
3531 if (i + 1 >= argc)
3532 return SyntaxError("%s requires a cache entry filename!\n", argv[i]);
3533 pszEntryFile = argv[++i];
3534 }
3535 else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache-file"))
3536 {
3537 if (i + 1 >= argc)
3538 return SyntaxError("%s requires a cache filename!\n", argv[i]);
3539 pszCacheFile = argv[++i];
3540 }
3541 else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--name"))
3542 {
3543 if (i + 1 >= argc)
3544 return SyntaxError("%s requires a cache name!\n", argv[i]);
3545 pszCacheName = argv[++i];
3546 }
3547 else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--cache-dir"))
3548 {
3549 if (i + 1 >= argc)
3550 return SyntaxError("%s requires a cache directory!\n", argv[i]);
3551 pszCacheDir = argv[++i];
3552 }
3553 else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--target"))
3554 {
3555 if (i + 1 >= argc)
3556 return SyntaxError("%s requires a target platform/arch name!\n", argv[i]);
3557 pszTarget = argv[++i];
3558 }
3559 else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--passthru"))
3560 fRedirPreCompStdOut = fRedirCompileStdIn = 1;
3561 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
3562 fRedirPreCompStdOut = 1;
3563 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
3564 g_cVerbosityLevel++;
3565 else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
3566 g_cVerbosityLevel = 0;
3567 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?"))
3568 return usage();
3569 else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version"))
3570 {
3571 printf("kObjCache v0.0.0 ($Revision: 1040 $)\n");
3572 return 0;
3573 }
3574 else
3575 return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
3576 }
3577 if (!pszEntryFile)
3578 return SyntaxError("No cache entry filename (-f)!\n");
3579 if (!cArgvCompile)
3580 return SyntaxError("No compiler arguments (--kObjCache-cc)!\n");
3581 if (!cArgvPreComp)
3582 return SyntaxError("No precompiler arguments (--kObjCache-cc)!\n");
3583
3584 /*
3585 * Calc the cache file name.
3586 */
3587 if (!pszCacheFile)
3588 {
3589 if (!pszCacheDir)
3590 return SyntaxError("No cache dir (-d / KOBJCACHE_DIR) and no cache filename!\n");
3591 if (!pszCacheName)
3592 {
3593 pszCacheName = FindFilenameInPath(pszCacheFile);
3594 if (!*pszCacheName)
3595 return SyntaxError("The cache file (-f) specifies a directory / nothing!\n");
3596 pszCacheName = xstrdup(pszCacheName);
3597 psz = strrchr(pszCacheName, '.');
3598 if (psz > pszCacheName)
3599 *psz = '\0';
3600 }
3601 pszCacheFile = MakePathFromDirAndFile(pszCacheDir, pszCacheName); /* harmless leak. */
3602 }
3603
3604 /*
3605 * Create and initialize the two bojects we'll be working on.
3606 *
3607 * We're supposed to be the only ones actually writing to the local file,
3608 * so it's perfectly fine to read it here before we lock it. This simplifies
3609 * the detection of object name and compiler argument changes.
3610 */
3611 SetErrorPrefix("kObjCache - %s", FindFilenameInPath(pszCacheFile));
3612 pCache = kObjCacheCreate(pszCacheFile);
3613
3614 pEntry = kOCEntryCreate(pszCacheFile);
3615 kOCEntryRead(pEntry);
3616 kOCEntrySetCompileArgv(pEntry, papszArgvCompile, cArgvCompile);
3617 kOCEntrySetCompileObjName(pEntry, pszObjName);
3618 kOCEntrySetTarget(pEntry, pszTarget);
3619 kOCEntrySetCppName(pEntry, pszPreCompName);
3620 kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn);
3621
3622 /*
3623 * Open (& lock) the two files and do validity checks and such.
3624 */
3625 kObjCacheLock(pCache);
3626 if ( kObjCacheIsNew(pCache)
3627 && kOCEntryNeedsCompiling(pEntry))
3628 {
3629 /*
3630 * Both files are missing/invalid.
3631 * Optimize this path as it is frequently used when making a clean build.
3632 */
3633 kOCEntryPreCompileAndCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3634 }
3635 else
3636 {
3637 /*
3638 * Do the precompile (don't need to lock the cache file for this).
3639 */
3640 kObjCacheUnlock(pCache);
3641 kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3642
3643 /*
3644 * Check if we need to recompile. If we do, try see if the is a cache entry first.
3645 */
3646 kOCEntryCalcRecompile(pEntry);
3647 if (kOCEntryNeedsCompiling(pEntry))
3648 {
3649 PKOCENTRY pUseEntry;
3650 kObjCacheLock(pCache);
3651 kObjCacheRemoveEntry(pCache, pEntry);
3652 pUseEntry = kObjCacheFindMatchingEntry(pCache, pEntry);
3653 if (pUseEntry)
3654 {
3655 kOCEntryCopy(pEntry, pUseEntry);
3656 kOCEntryDestroy(pUseEntry);
3657 }
3658 else
3659 {
3660 kObjCacheUnlock(pCache);
3661 kOCEntryCompileIt(pEntry);
3662 kObjCacheLock(pCache);
3663 }
3664 }
3665 }
3666
3667 /*
3668 * Update the cache files.
3669 */
3670 kObjCacheInsertEntry(pCache, pEntry);
3671 kOCEntryWrite(pEntry);
3672 kObjCacheUnlock(pCache);
3673 return 0;
3674}
3675
3676
3677/** @page kObjCache Benchmarks.
3678 *
3679 * 2007-06-02 - 21-23:00:
3680 * Mac OS X debug -j 3 clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
3681 * real 10m26.077s
3682 * user 13m13.291s
3683 * sys 2m58.193s
3684 *
3685 * Mac OS X debug -j 3 depend build (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
3686 * real 3m55.275s
3687 * user 4m11.852s
3688 * sys 0m54.931s
3689 *
3690 * 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):
3691 * real 11m42.513s
3692 * user 14m27.736s
3693 * sys 3m39.512s
3694 *
3695 * 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):
3696 * real 1m17.445s
3697 * user 1m13.410s
3698 * sys 0m22.789s
3699 *
3700 * 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):
3701 * real 1m29.315s
3702 * user 1m31.391s
3703 * sys 0m32.748s
3704 *
3705 */
Note: See TracBrowser for help on using the repository browser.