VirtualBox

source: kBuild/trunk/src/kObjCache/kObjCache.c@ 1043

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

made it build on darwin.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette