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

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

Fixed a number of bugs.

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