VirtualBox

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

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

Version 0.1.0 for now.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 115.8 KB
Line 
1/* $Id: kObjCache.c 1053 2007-06-11 09:32:07Z bird $ */
2/** @file
3 *
4 * kObjCache - Object Cache.
5 *
6 * Copyright (c) 2007 knut st. osmundsen <[email protected]>
7 *
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#if 0
32# define ELECTRIC_HEAP
33# include "../kmk/electric.h"
34#endif
35#include <string.h>
36#include <stdlib.h>
37#include <stdarg.h>
38#include <stdio.h>
39#include <errno.h>
40#include <assert.h>
41#include <sys/stat.h>
42#include <fcntl.h>
43#include <limits.h>
44#include <ctype.h>
45#ifndef PATH_MAX
46# define PATH_MAX _MAX_PATH /* windows */
47#endif
48#if defined(__OS2__) || defined(__WIN__)
49# include <process.h>
50# include <io.h>
51# ifdef __OS2__
52# include <unistd.h>
53# endif
54# if defined(_MSC_VER)
55 typedef intptr_t pid_t;
56# endif
57#else
58# include <unistd.h>
59# include <sys/wait.h>
60# ifndef O_BINARY
61# define O_BINARY 0
62# endif
63#endif
64#if defined(__WIN__)
65# include <Windows.h>
66#endif
67
68#include "crc32.h"
69#include "md5.h"
70
71
72/*******************************************************************************
73* Defined Constants And Macros *
74*******************************************************************************/
75/** The max line length in a cache file. */
76#define KOBJCACHE_MAX_LINE_LEN 16384
77#if defined(__WIN__)
78# define PATH_SLASH '\\'
79#else
80# define PATH_SLASH '/'
81#endif
82#if defined(__OS2__) || defined(__WIN__)
83# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
84# define IS_SLASH_DRV(ch) ((ch) == '/' || (ch) == '\\' || (ch) == ':')
85#else
86# define IS_SLASH(ch) ((ch) == '/')
87# define IS_SLASH_DRV(ch) ((ch) == '/')
88#endif
89
90#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 free(pEntry->New.pszTarget);
1094 free(pEntry->Old.pszTarget);
1095
1096 while (pEntry->New.cArgvCompile > 0)
1097 free(pEntry->New.papszArgvCompile[--pEntry->New.cArgvCompile]);
1098 while (pEntry->Old.cArgvCompile > 0)
1099 free(pEntry->Old.papszArgvCompile[--pEntry->Old.cArgvCompile]);
1100
1101 free(pEntry->New.papszArgvCompile);
1102 free(pEntry->Old.papszArgvCompile);
1103
1104 free(pEntry);
1105}
1106
1107
1108/**
1109 * Calculates the checksum of an compiler argument vector.
1110 *
1111 * @param pEntry The cache entry.
1112 * @param papszArgv The argument vector.
1113 * @param cArgc The number of entries in the vector.
1114 * @param pszIgnorePath Path to ignore when encountered at the end of arguments.
1115 * (Not quite safe for simple file names, but what the heck.)
1116 * @param pSum Where to store the check sum.
1117 */
1118static void kOCEntryCalcArgvSum(PKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgc,
1119 const char *pszIgnorePath, PKOCSUM pSum)
1120{
1121 size_t cchIgnorePath = strlen(pszIgnorePath);
1122 KOCSUMCTX Ctx;
1123 unsigned i;
1124
1125 kOCSumInitWithCtx(pSum, &Ctx);
1126 for (i = 0; i < cArgc; i++)
1127 {
1128 size_t cch = strlen(papszArgv[i]);
1129 if ( cch < cchIgnorePath
1130 || !ArePathsIdentical(papszArgv[i] + cch - cchIgnorePath, pszIgnorePath, cch))
1131 kOCSumUpdate(pSum, &Ctx, papszArgv[i], cch + 1);
1132 }
1133 kOCSumFinalize(pSum, &Ctx);
1134}
1135
1136
1137/**
1138 * Reads and parses the cache file.
1139 *
1140 * @param pEntry The entry to read it into.
1141 */
1142static void kOCEntryRead(PKOCENTRY pEntry)
1143{
1144 FILE *pFile;
1145 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "rb");
1146 if (pFile)
1147 {
1148 InfoMsg(4, "reading cache entry...\n");
1149
1150 /*
1151 * Check the magic.
1152 */
1153 if ( !fgets(g_szLine, sizeof(g_szLine), pFile)
1154 || strcmp(g_szLine, "magic=kObjCacheEntry-v0.1.0\n"))
1155 {
1156 InfoMsg(2, "bad cache file (magic)\n");
1157 pEntry->fNeedCompiling = 1;
1158 }
1159 else
1160 {
1161 /*
1162 * Parse the rest of the file (relaxed order).
1163 */
1164 unsigned i;
1165 int fBad = 0;
1166 int fBadBeforeMissing = 1;
1167 while (fgets(g_szLine, sizeof(g_szLine), pFile))
1168 {
1169 char *pszNl;
1170 char *pszVal;
1171
1172 /* Split the line and drop the trailing newline. */
1173 pszVal = strchr(g_szLine, '=');
1174 if ((fBad = pszVal == NULL))
1175 break;
1176 *pszVal++ = '\0';
1177
1178 pszNl = strchr(pszVal, '\n');
1179 if (pszNl)
1180 *pszNl = '\0';
1181
1182 /* string case on variable name */
1183 if (!strcmp(g_szLine, "obj"))
1184 {
1185 if ((fBad = pEntry->Old.pszObjName != NULL))
1186 break;
1187 pEntry->Old.pszObjName = xstrdup(pszVal);
1188 }
1189 else if (!strcmp(g_szLine, "cpp"))
1190 {
1191 if ((fBad = pEntry->Old.pszCppName != NULL))
1192 break;
1193 pEntry->Old.pszCppName = xstrdup(pszVal);
1194 }
1195 else if (!strcmp(g_szLine, "cpp-size"))
1196 {
1197 char *pszNext;
1198 if ((fBad = pEntry->Old.cbCpp != 0))
1199 break;
1200 pEntry->Old.cbCpp = strtoul(pszVal, &pszNext, 0);
1201 if ((fBad = pszNext && *pszNext))
1202 break;
1203 }
1204 else if (!strcmp(g_szLine, "cpp-sum"))
1205 {
1206 KOCSUM Sum;
1207 if ((fBad = kOCSumInitFromString(&Sum, pszVal)))
1208 break;
1209 kOCSumAdd(&pEntry->Old.SumHead, &Sum);
1210 }
1211 else if (!strcmp(g_szLine, "cc-argc"))
1212 {
1213 if ((fBad = pEntry->Old.papszArgvCompile != NULL))
1214 break;
1215 pEntry->Old.cArgvCompile = atoi(pszVal); /* if wrong, we'll fail below. */
1216 pEntry->Old.papszArgvCompile = xmallocz((pEntry->Old.cArgvCompile + 1) * sizeof(pEntry->Old.papszArgvCompile[0]));
1217 }
1218 else if (!strncmp(g_szLine, "cc-argv-#", sizeof("cc-argv-#") - 1))
1219 {
1220 char *pszNext;
1221 unsigned i = strtoul(&g_szLine[sizeof("cc-argv-#") - 1], &pszNext, 0);
1222 if ((fBad = i >= pEntry->Old.cArgvCompile || pEntry->Old.papszArgvCompile[i] || (pszNext && *pszNext)))
1223 break;
1224 pEntry->Old.papszArgvCompile[i] = xstrdup(pszVal);
1225 }
1226 else if (!strcmp(g_szLine, "cc-argv-sum"))
1227 {
1228 if ((fBad = !kOCSumIsEmpty(&pEntry->Old.SumCompArgv)))
1229 break;
1230 if ((fBad = kOCSumInitFromString(&pEntry->Old.SumCompArgv, pszVal)))
1231 break;
1232 }
1233 else if (!strcmp(g_szLine, "target"))
1234 {
1235 if ((fBad = pEntry->Old.pszTarget != NULL))
1236 break;
1237 pEntry->Old.pszTarget = xstrdup(pszVal);
1238 }
1239 else if (!strcmp(g_szLine, "key"))
1240 {
1241 char *pszNext;
1242 if ((fBad = pEntry->uKey != 0))
1243 break;
1244 pEntry->uKey = strtoul(pszVal, &pszNext, 0);
1245 if ((fBad = pszNext && *pszNext))
1246 break;
1247 }
1248 else if (!strcmp(g_szLine, "the-end"))
1249 {
1250 fBadBeforeMissing = fBad = strcmp(pszVal, "fine");
1251 break;
1252 }
1253 else
1254 {
1255 fBad = 1;
1256 break;
1257 }
1258 } /* parse loop */
1259
1260 /*
1261 * Did we find everything and does it add up correctly?
1262 */
1263 if (!fBad && fBadBeforeMissing)
1264 {
1265 InfoMsg(2, "bad cache file (no end)\n");
1266 fBad = 1;
1267 }
1268 else
1269 {
1270 fBadBeforeMissing = fBad;
1271 if ( !fBad
1272 && ( !pEntry->Old.papszArgvCompile
1273 || !pEntry->Old.pszObjName
1274 || !pEntry->Old.pszCppName
1275 || kOCSumIsEmpty(&pEntry->Old.SumHead)))
1276 fBad = 1;
1277 if (!fBad)
1278 for (i = 0; i < pEntry->Old.cArgvCompile; i++)
1279 if ((fBad = !pEntry->Old.papszArgvCompile[i]))
1280 break;
1281 if (!fBad)
1282 {
1283 KOCSUM Sum;
1284 kOCEntryCalcArgvSum(pEntry, (const char * const *)pEntry->Old.papszArgvCompile,
1285 pEntry->Old.cArgvCompile, pEntry->Old.pszObjName, &Sum);
1286 fBad = !kOCSumIsEqual(&pEntry->Old.SumCompArgv, &Sum);
1287 }
1288 if (fBad)
1289 InfoMsg(2, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff");
1290 else if (ferror(pFile))
1291 {
1292 InfoMsg(2, "cache file read error\n");
1293 fBad = 1;
1294 }
1295
1296 /*
1297 * Verify the existance of the object file.
1298 */
1299 if (!fBad)
1300 {
1301 struct stat st;
1302 char *pszPath = MakePathFromDirAndFile(pEntry->Old.pszObjName, pEntry->pszDir);
1303 if (stat(pszPath, &st) != 0)
1304 {
1305 InfoMsg(2, "failed to stat object file: %s\n", strerror(errno));
1306 fBad = 1;
1307 }
1308 else
1309 {
1310 /** @todo verify size and the timestamp. */
1311 }
1312 }
1313 }
1314 pEntry->fNeedCompiling = fBad;
1315 }
1316 fclose(pFile);
1317 }
1318 else
1319 {
1320 InfoMsg(2, "no cache file\n");
1321 pEntry->fNeedCompiling = 1;
1322 }
1323}
1324
1325
1326/**
1327 * Writes the cache file.
1328 *
1329 * @param pEntry The entry to write.
1330 */
1331static void kOCEntryWrite(PKOCENTRY pEntry)
1332{
1333 FILE *pFile;
1334 PCKOCSUM pSum;
1335 unsigned i;
1336
1337 InfoMsg(4, "writing cache entry '%s'...\n", pEntry->pszName);
1338 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "wb");
1339 if (!pFile)
1340 FatalDie("Failed to open '%s' in '%s': %s\n",
1341 pEntry->pszName, pEntry->pszDir, strerror(errno));
1342
1343#define CHECK_LEN(expr) \
1344 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)
1345
1346 fprintf(pFile, "magic=kObjCacheEntry-v0.1.0\n");
1347 CHECK_LEN(fprintf(pFile, "target=%s\n", pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget));
1348 CHECK_LEN(fprintf(pFile, "key=%u\n", (unsigned long)pEntry->uKey));
1349 CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->New.pszObjName ? pEntry->New.pszObjName : pEntry->Old.pszObjName));
1350 CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->New.pszCppName ? pEntry->New.pszCppName : pEntry->Old.pszCppName));
1351 CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n", pEntry->New.pszCppName ? pEntry->New.cbCpp : pEntry->Old.cbCpp));
1352
1353 if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv))
1354 {
1355 CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->New.cArgvCompile));
1356 for (i = 0; i < pEntry->New.cArgvCompile; i++)
1357 CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->New.papszArgvCompile[i]));
1358 fprintf(pFile, "cc-argv-sum=");
1359 kOCSumFPrintf(&pEntry->New.SumCompArgv, pFile);
1360 }
1361 else
1362 {
1363 CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->Old.cArgvCompile));
1364 for (i = 0; i < pEntry->Old.cArgvCompile; i++)
1365 CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->Old.papszArgvCompile[i]));
1366 fprintf(pFile, "cc-argv-sum=");
1367 kOCSumFPrintf(&pEntry->Old.SumCompArgv, pFile);
1368 }
1369
1370
1371 for (pSum = !kOCSumIsEmpty(&pEntry->New.SumHead) ? &pEntry->New.SumHead : &pEntry->Old.SumHead;
1372 pSum;
1373 pSum = pSum->pNext)
1374 {
1375 fprintf(pFile, "cpp-sum=");
1376 kOCSumFPrintf(pSum, pFile);
1377 }
1378
1379 fprintf(pFile, "the-end=fine\n");
1380
1381#undef CHECK_LEN
1382
1383 /*
1384 * Flush the file and check for errors.
1385 * On failure delete the file so we won't be seeing any invalid
1386 * files the next time or upset make with new timestamps.
1387 */
1388 errno = 0;
1389 if ( fflush(pFile) < 0
1390 || ferror(pFile))
1391 {
1392 int iErr = errno;
1393 fclose(pFile);
1394 UnlinkFileInDir(pEntry->pszName, pEntry->pszDir);
1395 FatalDie("Stream error occured while writing '%s' in '%s': %s\n",
1396 pEntry->pszName, pEntry->pszDir, strerror(iErr));
1397 }
1398 fclose(pFile);
1399}
1400
1401
1402/**
1403 * Checks that the read cache entry is valid.
1404 * It sets fNeedCompiling if it isn't.
1405 *
1406 * @returns 1 valid, 0 invalid.
1407 * @param pEntry The cache entry.
1408 */
1409static int kOCEntryCheck(PKOCENTRY pEntry)
1410{
1411 return !pEntry->fNeedCompiling;
1412}
1413
1414
1415/**
1416 * Sets the object name and compares it with the old name if present.
1417 *
1418 * @param pEntry The cache entry.
1419 * @param pszObjName The new object name.
1420 */
1421static void kOCEntrySetCompileObjName(PKOCENTRY pEntry, const char *pszObjName)
1422{
1423 assert(!pEntry->New.pszObjName);
1424 pEntry->New.pszObjName = CalcRelativeName(pszObjName, pEntry->pszDir);
1425
1426 if ( !pEntry->fNeedCompiling
1427 && ( !pEntry->Old.pszObjName
1428 || strcmp(pEntry->New.pszObjName, pEntry->Old.pszObjName)))
1429 {
1430 InfoMsg(2, "object file name differs\n");
1431 pEntry->fNeedCompiling = 1;
1432 }
1433
1434 if ( !pEntry->fNeedCompiling
1435 && !DoesFileInDirExist(pEntry->New.pszObjName, pEntry->pszDir))
1436 {
1437 InfoMsg(2, "object file doesn't exist\n");
1438 pEntry->fNeedCompiling = 1;
1439 }
1440}
1441
1442
1443/**
1444 * Set the new compiler args, calc their checksum, and comparing them with any old ones.
1445 *
1446 * @param pEntry The cache entry.
1447 * @param papszArgvCompile The new argument vector for compilation.
1448 * @param cArgvCompile The number of arguments in the vector.
1449 *
1450 * @remark Must call kOCEntrySetCompileObjName before this function!
1451 */
1452static void kOCEntrySetCompileArgv(PKOCENTRY pEntry, const char * const *papszArgvCompile, unsigned cArgvCompile)
1453{
1454 unsigned i;
1455
1456 /* call me only once! */
1457 assert(!pEntry->New.cArgvCompile);
1458 /* call kOCEntrySetCompilerObjName first! */
1459 assert(pEntry->New.pszObjName);
1460
1461 /*
1462 * Copy the argument vector and calculate the checksum.
1463 */
1464 pEntry->New.cArgvCompile = cArgvCompile;
1465 pEntry->New.papszArgvCompile = xmalloc((cArgvCompile + 1) * sizeof(pEntry->New.papszArgvCompile[0]));
1466 for (i = 0; i < cArgvCompile; i++)
1467 pEntry->New.papszArgvCompile[i] = xstrdup(papszArgvCompile[i]);
1468 pEntry->New.papszArgvCompile[i] = NULL; /* for exev/spawnv */
1469
1470 kOCEntryCalcArgvSum(pEntry, papszArgvCompile, cArgvCompile, pEntry->New.pszObjName, &pEntry->New.SumCompArgv);
1471 kOCSumInfo(&pEntry->New.SumCompArgv, 4, "comp-argv");
1472
1473 /*
1474 * Compare with the old argument vector.
1475 */
1476 if ( !pEntry->fNeedCompiling
1477 && !kOCSumIsEqual(&pEntry->New.SumCompArgv, &pEntry->Old.SumCompArgv))
1478 {
1479 InfoMsg(2, "compiler args differs\n");
1480 pEntry->fNeedCompiling = 1;
1481 }
1482}
1483
1484
1485/**
1486 * Sets the arch/os target and compares it with the old name if present.
1487 *
1488 * @param pEntry The cache entry.
1489 * @param pszObjName The new object name.
1490 */
1491static void kOCEntrySetTarget(PKOCENTRY pEntry, const char *pszTarget)
1492{
1493 assert(!pEntry->New.pszTarget);
1494 pEntry->New.pszTarget = xstrdup(pszTarget);
1495
1496 if ( !pEntry->fNeedCompiling
1497 && ( !pEntry->Old.pszTarget
1498 || strcmp(pEntry->New.pszTarget, pEntry->Old.pszTarget)))
1499 {
1500 InfoMsg(2, "target differs\n");
1501 pEntry->fNeedCompiling = 1;
1502 }
1503}
1504
1505
1506/**
1507 * Sets the precompiler output filename.
1508 * We don't generally care if this matches the old name or not.
1509 *
1510 * @param pEntry The cache entry.
1511 * @param pszCppName The precompiler output filename.
1512 */
1513static void kOCEntrySetCppName(PKOCENTRY pEntry, const char *pszCppName)
1514{
1515 assert(!pEntry->New.pszCppName);
1516 pEntry->New.pszCppName = CalcRelativeName(pszCppName, pEntry->pszDir);
1517}
1518
1519
1520/**
1521 * Sets the piped mode of the precompiler and compiler.
1522 *
1523 * @param pEntry The cache entry.
1524 * @param fRedirPreCompStdOut Whether the precompiler is in piped mode.
1525 * @param fRedirCompileStdIn Whether the compiler is in piped mode.
1526 */
1527static void kOCEntrySetPipedMode(PKOCENTRY pEntry, int fRedirPreCompStdOut, int fRedirCompileStdIn)
1528{
1529 pEntry->fPipedPreComp = fRedirPreCompStdOut;
1530 pEntry->fPipedCompile = fRedirCompileStdIn;
1531}
1532
1533
1534/**
1535 * Spawns a child in a synchronous fashion.
1536 * Terminating on failure.
1537 *
1538 * @param papszArgv Argument vector. The cArgv element is NULL.
1539 * @param cArgv The number of arguments in the vector.
1540 */
1541static void kOCEntrySpawn(PCKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut)
1542{
1543#if defined(__OS2__) || defined(__WIN__)
1544 intptr_t rc;
1545 int fdStdOut = -1;
1546 if (pszStdOut)
1547 {
1548 int fdReDir;
1549 fdStdOut = dup(STDOUT_FILENO);
1550 close(STDOUT_FILENO);
1551 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666);
1552 if (fdReDir < 0)
1553 FatalDie("%s - failed to create stdout redirection file '%s': %s\n",
1554 pszMsg, pszStdOut, strerror(errno));
1555
1556 if (fdReDir != STDOUT_FILENO)
1557 {
1558 if (dup2(fdReDir, STDOUT_FILENO) < 0)
1559 FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno));
1560 close(fdReDir);
1561 }
1562 }
1563
1564 errno = 0;
1565 rc = _spawnvp(_P_WAIT, papszArgv[0], papszArgv);
1566 if (rc < 0)
1567 FatalDie("%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno));
1568 if (rc > 0)
1569 FatalDie("%s - failed rc=%d\n", pszMsg, (int)rc);
1570 if (fdStdOut)
1571 {
1572 close(STDOUT_FILENO);
1573 fdStdOut = dup2(fdStdOut, STDOUT_FILENO);
1574 close(fdStdOut);
1575 }
1576
1577#else
1578 int iStatus;
1579 pid_t pidWait;
1580 pid_t pid = fork();
1581 if (!pid)
1582 {
1583 if (pszStdOut)
1584 {
1585 int fdReDir;
1586
1587 close(STDOUT_FILENO);
1588 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666);
1589 if (fdReDir < 0)
1590 FatalDie("%s - failed to create stdout redirection file '%s': %s\n",
1591 pszMsg, pszStdOut, strerror(errno));
1592 if (fdReDir != STDOUT_FILENO)
1593 {
1594 if (dup2(fdReDir, STDOUT_FILENO) < 0)
1595 FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno));
1596 close(fdReDir);
1597 }
1598 }
1599
1600 execvp(papszArgv[0], (char **)papszArgv);
1601 FatalDie("%s - execvp failed: %s\n",
1602 pszMsg, strerror(errno));
1603 }
1604 if (pid == -1)
1605 FatalDie("%s - fork() failed: %s\n", pszMsg, strerror(errno));
1606
1607 pidWait = waitpid(pid, &iStatus, 0);
1608 while (pidWait < 0 && errno == EINTR)
1609 pidWait = waitpid(pid, &iStatus, 0);
1610 if (pidWait != pid)
1611 FatalDie("%s - waitpid failed rc=%d: %s\n",
1612 pszMsg, pidWait, strerror(errno));
1613 if (!WIFEXITED(iStatus))
1614 FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
1615 if (WEXITSTATUS(iStatus))
1616 FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
1617#endif
1618 (void)cArgv;
1619}
1620
1621
1622/**
1623 * Spawns child with optional redirection of stdin and stdout.
1624 *
1625 * @param pEntry The cache entry.
1626 * @param papszArgv Argument vector. The cArgv element is NULL.
1627 * @param cArgv The number of arguments in the vector.
1628 * @param fdStdIn Child stdin, -1 if it should inherit our stdin. Will be closed.
1629 * @param fdStdOut Child stdout, -1 if it should inherit our stdout. Will be closed.
1630 * @param pszMsg Message to start the info/error messages with.
1631 */
1632static pid_t kOCEntrySpawnChild(PCKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, int fdStdIn, int fdStdOut, const char *pszMsg)
1633{
1634 pid_t pid;
1635 int fdSavedStdOut = -1;
1636 int fdSavedStdIn = -1;
1637
1638 /*
1639 * Setup redirection.
1640 */
1641 if (fdStdOut != -1 && fdStdOut != STDOUT_FILENO)
1642 {
1643 fdSavedStdOut = dup(STDOUT_FILENO);
1644 if (dup2(fdStdOut, STDOUT_FILENO) < 0)
1645 FatalDie("%s - dup2(,1) failed: %s\n", pszMsg, strerror(errno));
1646 close(fdStdOut);
1647#ifndef __WIN__
1648 fcntl(fdSavedStdOut, F_SETFD, FD_CLOEXEC);
1649#endif
1650 }
1651 if (fdStdIn != -1 && fdStdIn != STDIN_FILENO)
1652 {
1653 fdSavedStdIn = dup(STDIN_FILENO);
1654 if (dup2(fdStdIn, STDIN_FILENO) < 0)
1655 FatalDie("%s - dup2(,0) failed: %s\n", pszMsg, strerror(errno));
1656 close(fdStdIn);
1657#ifndef __WIN__
1658 fcntl(fdSavedStdIn, F_SETFD, FD_CLOEXEC);
1659#endif
1660 }
1661
1662 /*
1663 * Create the child process.
1664 */
1665#if defined(__OS2__) || defined(__WIN__)
1666 errno = 0;
1667 pid = _spawnvp(_P_NOWAIT, papszArgv[0], papszArgv);
1668 if (pid == -1)
1669 FatalDie("precompile - _spawnvp failed: %s\n", strerror(errno));
1670
1671#else
1672 pid = fork();
1673 if (!pid)
1674 {
1675 execvp(papszArgv[0], (char **)papszArgv);
1676 FatalDie("precompile - execvp failed: %s\n", strerror(errno));
1677 }
1678 if (pid == -1)
1679 FatalDie("precompile - fork() failed: %s\n", strerror(errno));
1680#endif
1681
1682 /*
1683 * Restore stdout & stdin.
1684 */
1685 if (fdSavedStdIn != -1)
1686 {
1687 close(STDIN_FILENO);
1688 dup2(fdStdOut, STDIN_FILENO);
1689 close(fdSavedStdIn);
1690 }
1691 if (fdSavedStdOut != -1)
1692 {
1693 close(STDOUT_FILENO);
1694 dup2(fdSavedStdOut, STDOUT_FILENO);
1695 close(fdSavedStdOut);
1696 }
1697
1698 InfoMsg(3, "%s - spawned %ld\n", pszMsg, (long)pid);
1699 (void)cArgv;
1700 (void)pEntry;
1701 return pid;
1702}
1703
1704
1705/**
1706 * Waits for a child and exits fatally if the child failed in any way.
1707 *
1708 * @param pEntry The cache entry.
1709 * @param pid The child to wait for.
1710 * @param pszMsg Message to start the info/error messages with.
1711 */
1712static void kOCEntryWaitChild(PCKOCENTRY pEntry, pid_t pid, const char *pszMsg)
1713{
1714 int iStatus = -1;
1715 pid_t pidWait;
1716 InfoMsg(3, "%s - wait-child %ld\n", pszMsg, (long)pid);
1717
1718#ifdef __WIN__
1719 pidWait = _cwait(&iStatus, pid, _WAIT_CHILD);
1720 if (pidWait == -1)
1721 FatalDie("%s - waitpid failed: %s\n", pszMsg, strerror(errno));
1722 if (iStatus)
1723 FatalDie("%s - failed with rc %d\n", pszMsg, iStatus);
1724#else
1725 pidWait = waitpid(pid, &iStatus, 0);
1726 while (pidWait < 0 && errno == EINTR)
1727 pidWait = waitpid(pid, &iStatus, 0);
1728 if (pidWait != pid)
1729 FatalDie("%s - waitpid failed rc=%d: %s\n", pidWait, strerror(errno));
1730 if (!WIFEXITED(iStatus))
1731 FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
1732 if (WEXITSTATUS(iStatus))
1733 FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
1734#endif
1735 (void)pEntry;
1736}
1737
1738
1739/**
1740 * Creates a pipe for setting up redirected stdin/stdout.
1741 *
1742 * @param pEntry The cache entry.
1743 * @param pFDs Where to store the two file descriptors.
1744 * @param pszMsg The operation message for info/error messages.
1745 */
1746static void kOCEntryCreatePipe(PKOCENTRY pEntry, int *pFDs, const char *pszMsg)
1747{
1748 pFDs[0] = pFDs[1] = -1;
1749#if defined(__WIN__)
1750 if (_pipe(pFDs, 0, _O_NOINHERIT | _O_BINARY) < 0)
1751#else
1752 if (pipe(pFDs) < 0)
1753#endif
1754 FatalDie("%s - pipe failed: %s\n", pszMsg, strerror(errno));
1755#if !defined(__WIN__)
1756 fcntl(pFDs[0], F_SETFD, FD_CLOEXEC);
1757 fcntl(pFDs[1], F_SETFD, FD_CLOEXEC);
1758#endif
1759}
1760
1761
1762/**
1763 * Spawns a child that produces output to stdout.
1764 *
1765 * @param papszArgv Argument vector. The cArgv element is NULL.
1766 * @param cArgv The number of arguments in the vector.
1767 * @param pszMsg The operation message for info/error messages.
1768 * @param pfnConsumer Pointer to a consumer callback function that is responsible
1769 * for servicing the child output and closing the pipe.
1770 */
1771static void kOCEntrySpawnProducer(PKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, const char *pszMsg,
1772 void (*pfnConsumer)(PKOCENTRY, int))
1773{
1774 int fds[2];
1775 pid_t pid;
1776
1777 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1778 pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, -1, fds[1 /* write */], pszMsg);
1779
1780 pfnConsumer(pEntry, fds[0 /* read */]);
1781
1782 kOCEntryWaitChild(pEntry, pid, pszMsg);
1783}
1784
1785
1786/**
1787 * Spawns a child that consumes input on stdin.
1788 *
1789 * @param papszArgv Argument vector. The cArgv element is NULL.
1790 * @param cArgv The number of arguments in the vector.
1791 * @param pszMsg The operation message for info/error messages.
1792 * @param pfnProducer Pointer to a producer callback function that is responsible
1793 * for serving the child input and closing the pipe.
1794 */
1795static void kOCEntrySpawnConsumer(PKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, const char *pszMsg,
1796 void (*pfnProducer)(PKOCENTRY, int))
1797{
1798 int fds[2];
1799 pid_t pid;
1800
1801 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1802 pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, fds[0 /* read */], -1, pszMsg);
1803
1804 pfnProducer(pEntry, fds[1 /* write */]);
1805
1806 kOCEntryWaitChild(pEntry, pid, pszMsg);
1807}
1808
1809
1810/**
1811 * Spawns two child processes, one producing output and one consuming.
1812 * Terminating on failure.
1813 *
1814 * @param papszArgv Argument vector. The cArgv element is NULL.
1815 * @param cArgv The number of arguments in the vector.
1816 * @param pszMsg The operation message for info/error messages.
1817 * @param pfnConsumer Pointer to a consumer callback function that is responsible
1818 * for servicing the child output and closing the pipe.
1819 */
1820static void kOCEntrySpawnTee(PKOCENTRY pEntry, const char * const *papszProdArgv, unsigned cProdArgv,
1821 const char * const *papszConsArgv, unsigned cConsArgv,
1822 const char *pszMsg, void (*pfnTeeConsumer)(PKOCENTRY, int, int))
1823{
1824 int fds[2];
1825 int fdIn, fdOut;
1826 pid_t pidProducer, pidConsumer;
1827
1828 /*
1829 * The producer.
1830 */
1831 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1832 pidConsumer = kOCEntrySpawnChild(pEntry, papszProdArgv, cProdArgv, -1, fds[1 /* write */], pszMsg);
1833 fdIn = fds[0 /* read */];
1834
1835 /*
1836 * The consumer.
1837 */
1838 kOCEntryCreatePipe(pEntry, fds, pszMsg);
1839 pidProducer = kOCEntrySpawnChild(pEntry, papszConsArgv, cConsArgv, fds[0 /* read */], -1, pszMsg);
1840 fdOut = fds[1 /* write */];
1841
1842 /*
1843 * Hand it on to the tee consumer.
1844 */
1845 pfnTeeConsumer(pEntry, fdIn, fdOut);
1846
1847 /*
1848 * Reap the children.
1849 */
1850 kOCEntryWaitChild(pEntry, pidProducer, pszMsg);
1851 kOCEntryWaitChild(pEntry, pidConsumer, pszMsg);
1852}
1853
1854
1855/**
1856 * Reads the output from the precompiler.
1857 *
1858 * @param pEntry The cache entry. New.cbCpp and New.pszCppMapping will be updated.
1859 * @param pWhich Specifies what to read (old/new).
1860 * @param fNonFatal Whether failure is fatal or not.
1861 */
1862static int kOCEntryReadCppOutput(PKOCENTRY pEntry, struct KOCENTRYDATA *pWhich, int fNonFatal)
1863{
1864 pWhich->pszCppMapping = ReadFileInDir(pWhich->pszCppName, pEntry->pszDir, &pWhich->cbCpp);
1865 if (!pWhich->pszCppMapping)
1866 {
1867 if (!fNonFatal)
1868 FatalDie("failed to open/read '%s' in '%s': %s\n",
1869 pWhich->pszCppName, pEntry->pszDir, strerror(errno));
1870 InfoMsg(2, "failed to open/read '%s' in '%s': %s\n",
1871 pWhich->pszCppName, pEntry->pszDir, strerror(errno));
1872 return -1;
1873 }
1874
1875 InfoMsg(3, "precompiled file is %lu bytes long\n", (unsigned long)pWhich->cbCpp);
1876 return 0;
1877}
1878
1879
1880/**
1881 * Worker for kOCEntryPreCompile and calculates the checksum of
1882 * the precompiler output.
1883 *
1884 * @param pEntry The cache entry. NewSum will be updated.
1885 */
1886static void kOCEntryCalcChecksum(PKOCENTRY pEntry)
1887{
1888 KOCSUMCTX Ctx;
1889 kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
1890 kOCSumUpdate(&pEntry->New.SumHead, &Ctx, pEntry->New.pszCppMapping, pEntry->New.cbCpp);
1891 kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
1892 kOCSumInfo(&pEntry->New.SumHead, 4, "cpp (file)");
1893}
1894
1895
1896/**
1897 * This consumes the precompiler output and checksums it.
1898 *
1899 * @param pEntry The cache entry.
1900 * @param fdIn The precompiler output pipe.
1901 * @param fdOut The compiler input pipe, -1 if no compiler.
1902 */
1903static void kOCEntryPreCompileConsumer(PKOCENTRY pEntry, int fdIn)
1904{
1905 KOCSUMCTX Ctx;
1906 long cbLeft;
1907 long cbAlloc;
1908 char *psz;
1909
1910 kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
1911 cbAlloc = pEntry->Old.cbCpp ? (pEntry->Old.cbCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
1912 cbLeft = cbAlloc;
1913 pEntry->New.pszCppMapping = psz = xmalloc(cbAlloc);
1914 for (;;)
1915 {
1916 /*
1917 * Read data from the pipe.
1918 */
1919 long cbRead = read(fdIn, psz, cbLeft - 1);
1920 if (!cbRead)
1921 break;
1922 if (cbRead < 0)
1923 {
1924 if (errno == EINTR)
1925 continue;
1926 FatalDie("precompile - read(%d,,%ld) failed: %s\n",
1927 fdIn, (long)cbLeft, strerror(errno));
1928 }
1929
1930 /*
1931 * Process the data.
1932 */
1933 psz[cbRead] = '\0';
1934 kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead);
1935
1936 /*
1937 * Advance.
1938 */
1939 psz += cbRead;
1940 cbLeft -= cbRead;
1941 if (cbLeft <= 1)
1942 {
1943 size_t off = psz - pEntry->New.pszCppMapping;
1944 cbLeft = 4*1024*1024;
1945 cbAlloc += cbLeft;
1946 pEntry->New.pszCppMapping = xrealloc(pEntry->New.pszCppMapping, cbAlloc);
1947 psz = pEntry->New.pszCppMapping + off;
1948 }
1949 }
1950
1951 close(fdIn);
1952 pEntry->New.cbCpp = cbAlloc - cbLeft;
1953 kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
1954 kOCSumInfo(&pEntry->New.SumHead, 4, "cpp (pipe)");
1955}
1956
1957
1958
1959
1960/**
1961 * Run the precompiler and calculate the checksum of the output.
1962 *
1963 * @param pEntry The cache entry.
1964 * @param papszArgvPreComp The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
1965 * @param cArgvPreComp The number of arguments.
1966 */
1967static void kOCEntryPreCompile(PKOCENTRY pEntry, const char * const *papszArgvPreComp, unsigned cArgvPreComp)
1968{
1969 /*
1970 * If we're executing the precompiler in piped mode, it's relatively simple.
1971 */
1972 if (pEntry->fPipedPreComp)
1973 kOCEntrySpawnProducer(pEntry, papszArgvPreComp, cArgvPreComp, "precompile",
1974 kOCEntryPreCompileConsumer);
1975 else
1976 {
1977 /*
1978 * Rename the old precompiled output to '-old' so the precompiler won't
1979 * overwrite it when we execute it.
1980 */
1981 if ( pEntry->Old.pszCppName
1982 && DoesFileInDirExist(pEntry->Old.pszCppName, pEntry->pszDir))
1983 {
1984 size_t cch = strlen(pEntry->Old.pszCppName);
1985 char *psz = xmalloc(cch + sizeof("-old"));
1986 memcpy(psz, pEntry->Old.pszCppName, cch);
1987 memcpy(psz + cch, "-old", sizeof("-old"));
1988
1989 InfoMsg(3, "renaming '%s' to '%s' in '%s'\n", pEntry->Old.pszCppName, psz, pEntry->pszDir);
1990 UnlinkFileInDir(psz, pEntry->pszDir);
1991 if (RenameFileInDir(pEntry->Old.pszCppName, psz, pEntry->pszDir))
1992 FatalDie("failed to rename '%s' -> '%s' in '%s': %s\n",
1993 pEntry->Old.pszCppName, psz, pEntry->pszDir, strerror(errno));
1994 free(pEntry->Old.pszCppName);
1995 pEntry->Old.pszCppName = psz;
1996 }
1997
1998 /*
1999 * Precompile it and calculate the checksum on the output.
2000 */
2001 InfoMsg(3, "precompiling -> '%s'...\n", pEntry->New.pszCppName);
2002 kOCEntrySpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
2003 kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
2004 kOCEntryCalcChecksum(pEntry);
2005 }
2006}
2007
2008
2009/**
2010 * Worker function for kOCEntryTeeConsumer and kOCEntryCompileIt that
2011 * writes the precompiler output to disk.
2012 *
2013 * @param pEntry The cache entry.
2014 * @param fFreeIt Whether we can free it after writing it or not.
2015 */
2016static void kOCEntryWriteCppOutput(PKOCENTRY pEntry, int fFreeIt)
2017{
2018 /*
2019 * Remove old files.
2020 */
2021 if (pEntry->Old.pszCppName)
2022 UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir);
2023 if (pEntry->New.pszCppName)
2024 UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
2025
2026 /*
2027 * Write it to disk if we've got a file name.
2028 */
2029 if (pEntry->New.pszCppName)
2030 {
2031 long cbLeft;
2032 char *psz;
2033 int fd = OpenFileInDir(pEntry->New.pszCppName, pEntry->pszDir,
2034 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
2035 if (fd == -1)
2036 FatalDie("Failed to create '%s' in '%s': %s\n",
2037 pEntry->New.pszCppName, pEntry->pszDir, strerror(errno));
2038 psz = pEntry->New.pszCppMapping;
2039 cbLeft = pEntry->New.cbCpp;
2040 while (cbLeft > 0)
2041 {
2042 long cbWritten = write(fd, psz, cbLeft);
2043 if (cbWritten < 0)
2044 {
2045 int iErr = errno;
2046 if (iErr == EINTR)
2047 continue;
2048 close(fd);
2049 UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
2050 FatalDie("error writing '%s' in '%s': %s\n",
2051 pEntry->New.pszCppName, pEntry->pszDir, strerror(iErr));
2052 }
2053
2054 psz += cbWritten;
2055 cbLeft -= cbWritten;
2056 }
2057 close(fd);
2058 }
2059
2060 /*
2061 * Free it.
2062 */
2063 if (fFreeIt)
2064 {
2065 free(pEntry->New.pszCppMapping);
2066 pEntry->New.pszCppMapping = NULL;
2067 }
2068}
2069
2070
2071/**
2072 * kOCEntrySpawnConsumer callback that passes the precompiler
2073 * output to the compiler and writes it to the disk (latter only when necesary).
2074 *
2075 * @param pEntry The cache entry.
2076 * @param fdOut The pipe handle connected to the childs stdin.
2077 */
2078static void kOCEntryCompileProducer(PKOCENTRY pEntry, int fdOut)
2079{
2080 const char *psz = pEntry->New.pszCppMapping;
2081 long cbLeft = pEntry->New.cbCpp;
2082 while (cbLeft > 0)
2083 {
2084 long cbWritten = write(fdOut, psz, cbLeft);
2085 if (cbWritten < 0)
2086 {
2087 if (errno == EINTR)
2088 continue;
2089 FatalDie("compile - write(%d,,%ld) failed: %s\n", fdOut, cbLeft, strerror(errno));
2090 }
2091 psz += cbWritten;
2092 cbLeft -= cbWritten;
2093 }
2094 close(fdOut);
2095
2096 if (pEntry->fPipedPreComp)
2097 kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
2098}
2099
2100
2101/**
2102 * Does the actual compiling.
2103 *
2104 * @param pEntry The cache entry.
2105 */
2106static void kOCEntryCompileIt(PKOCENTRY pEntry)
2107{
2108 /*
2109 * Delete the object files and free old cpp output that's no longer needed.
2110 */
2111 if (pEntry->Old.pszObjName)
2112 UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir);
2113 UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir);
2114
2115 free(pEntry->Old.pszCppMapping);
2116 pEntry->Old.pszCppMapping = NULL;
2117 if (!pEntry->fPipedPreComp && !pEntry->fPipedCompile)
2118 {
2119 free(pEntry->New.pszCppMapping);
2120 pEntry->New.pszCppMapping = NULL;
2121 }
2122
2123 /*
2124 * Do the (re-)compile job.
2125 */
2126 if (pEntry->fPipedCompile)
2127 {
2128 if ( !pEntry->fPipedPreComp
2129 && !pEntry->New.pszCppMapping)
2130 kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
2131 InfoMsg(3, "compiling -> '%s'...\n", pEntry->New.pszObjName);
2132 kOCEntrySpawnConsumer(pEntry, (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile,
2133 "compile", kOCEntryCompileProducer);
2134 }
2135 else
2136 {
2137 if (pEntry->fPipedPreComp)
2138 kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
2139 InfoMsg(3, "compiling -> '%s'...\n", pEntry->New.pszObjName);
2140 kOCEntrySpawn(pEntry, (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile, "compile", NULL);
2141 }
2142}
2143
2144
2145/**
2146 * kOCEntrySpawnTee callback that works sort of like 'tee'.
2147 *
2148 * It will calculate the precompiled output checksum and
2149 * write it to disk while the compiler is busy compiling it.
2150 *
2151 * @param pEntry The cache entry.
2152 * @param fdIn The input handle (connected to the precompiler).
2153 * @param fdOut The output handle (connected to the compiler).
2154 */
2155static void kOCEntryTeeConsumer(PKOCENTRY pEntry, int fdIn, int fdOut)
2156{
2157 KOCSUMCTX Ctx;
2158 long cbLeft;
2159 long cbAlloc;
2160 char *psz;
2161
2162 kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
2163 cbAlloc = pEntry->Old.cbCpp ? (pEntry->Old.cbCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
2164 cbLeft = cbAlloc;
2165 pEntry->New.pszCppMapping = psz = xmalloc(cbAlloc);
2166 InfoMsg(3, "precompiler|compile - starting passhtru...\n");
2167 for (;;)
2168 {
2169 /*
2170 * Read data from the pipe.
2171 */
2172 long cbRead = read(fdIn, psz, cbLeft - 1);
2173 if (!cbRead)
2174 break;
2175 if (cbRead < 0)
2176 {
2177 if (errno == EINTR)
2178 continue;
2179 FatalDie("precompile|compile - read(%d,,%ld) failed: %s\n",
2180 fdIn, (long)cbLeft, strerror(errno));
2181 }
2182 InfoMsg(3, "precompiler|compile - read %d\n", cbRead);
2183
2184 /*
2185 * Process the data.
2186 */
2187 psz[cbRead] = '\0';
2188 kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead);
2189 do
2190 {
2191 long cbWritten = write(fdOut, psz, cbRead);
2192 if (cbWritten < 0)
2193 {
2194 if (errno == EINTR)
2195 continue;
2196 FatalDie("precompile|compile - write(%d,,%ld) failed: %s\n", fdOut, cbRead, strerror(errno));
2197 }
2198 psz += cbWritten;
2199 cbRead -= cbWritten;
2200 cbLeft -= cbWritten;
2201 } while (cbRead > 0);
2202
2203 /*
2204 * Expand the buffer?
2205 */
2206 if (cbLeft <= 1)
2207 {
2208 size_t off = psz - pEntry->New.pszCppMapping;
2209 cbLeft = 4*1024*1024;
2210 cbAlloc += cbLeft;
2211 pEntry->New.pszCppMapping = xrealloc(pEntry->New.pszCppMapping, cbAlloc);
2212 psz = pEntry->New.pszCppMapping + off;
2213 }
2214 }
2215 InfoMsg(3, "precompiler|compile - done passhtru\n");
2216
2217 close(fdIn);
2218 close(fdOut);
2219 pEntry->New.cbCpp = cbAlloc - cbLeft;
2220 kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
2221 kOCSumInfo(&pEntry->New.SumHead, 4, "cpp (tee)");
2222
2223 /*
2224 * Write the precompiler output to disk and free the memory it
2225 * occupies while the compiler is busy compiling.
2226 */
2227 kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
2228}
2229
2230
2231/**
2232 * Performs pre-compile and compile in one go (typical clean build scenario).
2233 *
2234 * @param pEntry The cache entry.
2235 * @param papszArgvPreComp The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
2236 * @param cArgvPreComp The number of arguments.
2237 */
2238static void kOCEntryPreCompileAndCompile(PKOCENTRY pEntry, const char * const *papszArgvPreComp, unsigned cArgvPreComp)
2239{
2240 if ( pEntry->fPipedCompile
2241 && pEntry->fPipedPreComp)
2242 {
2243 /*
2244 * Clean up old stuff first.
2245 */
2246 if (pEntry->Old.pszObjName)
2247 UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir);
2248 if (pEntry->New.pszObjName)
2249 UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir);
2250 if (pEntry->Old.pszCppName)
2251 UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir);
2252 if (pEntry->New.pszCppName)
2253 UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
2254
2255 /*
2256 * Do the actual compile and write the precompiler output to disk.
2257 */
2258 kOCEntrySpawnTee(pEntry, papszArgvPreComp, cArgvPreComp,
2259 (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile,
2260 "precompile|compile", kOCEntryTeeConsumer);
2261 }
2262 else
2263 {
2264 kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
2265 kOCEntryCompileIt(pEntry);
2266 }
2267}
2268
2269
2270/**
2271 * Check whether the string is a '#line' statement.
2272 *
2273 * @returns 1 if it is, 0 if it isn't.
2274 * @param psz The line to examin.
2275 * @parma piLine Where to store the line number.
2276 * @parma ppszFile Where to store the start of the filename.
2277 */
2278static int kOCEntryIsLineStatement(const char *psz, unsigned *piLine, const char **ppszFile)
2279{
2280 unsigned iLine;
2281
2282 /* Expect a hash. */
2283 if (*psz++ != '#')
2284 return 0;
2285
2286 /* Skip blanks between '#' and the line / number */
2287 while (*psz == ' ' || *psz == '\t')
2288 psz++;
2289
2290 /* Skip the 'line' if present. */
2291 if (!strncmp(psz, "line", sizeof("line") - 1))
2292 psz += sizeof("line");
2293
2294 /* Expect a line number now. */
2295 if ((unsigned char)(*psz - '0') > 9)
2296 return 0;
2297 iLine = 0;
2298 do
2299 {
2300 iLine *= 10;
2301 iLine += (*psz - '0');
2302 psz++;
2303 }
2304 while ((unsigned char)(*psz - '0') <= 9);
2305
2306 /* Expect one or more space now. */
2307 if (*psz != ' ' && *psz != '\t')
2308 return 0;
2309 do psz++;
2310 while (*psz == ' ' || *psz == '\t');
2311
2312 /* that's good enough. */
2313 *piLine = iLine;
2314 *ppszFile = psz;
2315 return 1;
2316}
2317
2318
2319/**
2320 * Scan backwards for the previous #line statement.
2321 *
2322 * @returns The filename in the previous statement.
2323 * @param pszStart Where to start.
2324 * @param pszStop Where to stop. Less than pszStart.
2325 * @param piLine The line number count to adjust.
2326 */
2327static const char *kOCEntryFindFileStatement(const char *pszStart, const char *pszStop, unsigned *piLine)
2328{
2329 unsigned iLine = *piLine;
2330 assert(pszStart >= pszStop);
2331 while (pszStart >= pszStop)
2332 {
2333 if (*pszStart == '\n')
2334 iLine++;
2335 else if (*pszStart == '#')
2336 {
2337 unsigned iLineTmp;
2338 const char *pszFile;
2339 const char *psz = pszStart - 1;
2340 while (psz >= pszStop && (*psz == ' ' || *psz =='\t'))
2341 psz--;
2342 if ( (psz < pszStop || *psz == '\n')
2343 && kOCEntryIsLineStatement(pszStart, &iLineTmp, &pszFile))
2344 {
2345 *piLine = iLine + iLineTmp - 1;
2346 return pszFile;
2347 }
2348 }
2349 pszStart--;
2350 }
2351 return NULL;
2352}
2353
2354
2355/**
2356 * Worker for kOCEntryCompareOldAndNewOutput() that compares the
2357 * precompiled output using a fast but not very good method.
2358 *
2359 * @returns 1 if matching, 0 if not matching.
2360 * @param pEntry The entry containing the names of the files to compare.
2361 * The entry is not updated in any way.
2362 */
2363static int kOCEntryCompareFast(PCKOCENTRY pEntry)
2364{
2365 const char * psz1 = pEntry->New.pszCppMapping;
2366 const char * const pszEnd1 = psz1 + pEntry->New.cbCpp;
2367 const char * psz2 = pEntry->Old.pszCppMapping;
2368 const char * const pszEnd2 = psz2 + pEntry->Old.cbCpp;
2369
2370 assert(*pszEnd1 == '\0');
2371 assert(*pszEnd2 == '\0');
2372
2373 /*
2374 * Iterate block by block and backtrack when we find a difference.
2375 */
2376 for (;;)
2377 {
2378 size_t cch = pszEnd1 - psz1;
2379 if (cch > (size_t)(pszEnd2 - psz2))
2380 cch = pszEnd2 - psz2;
2381 if (cch > 4096)
2382 cch = 4096;
2383 if ( cch
2384 && !memcmp(psz1, psz2, cch))
2385 {
2386 /* no differences */
2387 psz1 += cch;
2388 psz2 += cch;
2389 }
2390 else
2391 {
2392 /*
2393 * Pinpoint the difference exactly and the try find the start
2394 * of that line. Then skip forward until we find something to
2395 * work on that isn't spaces, #line statements or closing curly
2396 * braces.
2397 *
2398 * The closing curly braces are ignored because they are frequently
2399 * found at the end of header files (__END_DECLS) and the worst
2400 * thing that may happen if it isn't one of these braces we're
2401 * ignoring is that the final line in a function block is a little
2402 * bit off in the debug info.
2403 *
2404 * Since we might be skipping a few new empty headers, it is
2405 * possible that we will omit this header from the dependencies
2406 * when using VCC. This might not be a problem, since it seems
2407 * we'll have to use the precompiler output to generate the deps
2408 * anyway.
2409 */
2410 const char *psz;
2411 const char *pszMismatch1;
2412 const char *pszFile1 = NULL;
2413 unsigned iLine1 = 0;
2414 unsigned cCurlyBraces1 = 0;
2415 const char *pszMismatch2;
2416 const char *pszFile2 = NULL;
2417 unsigned iLine2 = 0;
2418 unsigned cCurlyBraces2 = 0;
2419
2420 /* locate the difference. */
2421 while (cch >= 512 && !memcmp(psz1, psz2, 512))
2422 psz1 += 512, psz2 += 512, cch -= 512;
2423 while (cch >= 64 && !memcmp(psz1, psz2, 64))
2424 psz1 += 64, psz2 += 64, cch -= 64;
2425 while (*psz1 == *psz2 && cch > 0)
2426 psz1++, psz2++, cch--;
2427
2428 /* locate the start of that line. */
2429 psz = psz1;
2430 while ( psz > pEntry->New.pszCppMapping
2431 && psz[-1] != '\n')
2432 psz--;
2433 psz2 -= (psz1 - psz);
2434 pszMismatch2 = psz2;
2435 pszMismatch1 = psz1 = psz;
2436
2437 /* Parse the 1st file line by line. */
2438 while (psz1 < pszEnd1)
2439 {
2440 if (*psz1 == '\n')
2441 {
2442 psz1++;
2443 iLine1++;
2444 }
2445 else
2446 {
2447 psz = psz1;
2448 while (isspace(*psz) && *psz != '\n')
2449 psz++;
2450 if (*psz == '\n')
2451 {
2452 psz1 = psz + 1;
2453 iLine1++;
2454 }
2455 else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine1, &pszFile1))
2456 {
2457 psz1 = memchr(psz, '\n', pszEnd1 - psz);
2458 if (!psz1++)
2459 psz1 = pszEnd1;
2460 }
2461 else if (*psz == '}')
2462 {
2463 do psz++;
2464 while (isspace(*psz) && *psz != '\n');
2465 if (*psz == '\n')
2466 iLine1++;
2467 else if (psz != pszEnd1)
2468 break;
2469 cCurlyBraces1++;
2470 psz1 = psz;
2471 }
2472 else if (psz == pszEnd1)
2473 psz1 = psz;
2474 else /* found something that can be compared. */
2475 break;
2476 }
2477 }
2478
2479 /* Ditto for the 2nd file. */
2480 while (psz2 < pszEnd2)
2481 {
2482 if (*psz2 == '\n')
2483 {
2484 psz2++;
2485 iLine2++;
2486 }
2487 else
2488 {
2489 psz = psz2;
2490 while (isspace(*psz) && *psz != '\n')
2491 psz++;
2492 if (*psz == '\n')
2493 {
2494 psz2 = psz + 1;
2495 iLine2++;
2496 }
2497 else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine2, &pszFile2))
2498 {
2499 psz2 = memchr(psz, '\n', pszEnd2 - psz);
2500 if (!psz2++)
2501 psz2 = pszEnd2;
2502 }
2503 else if (*psz == '}')
2504 {
2505 do psz++;
2506 while (isspace(*psz) && *psz != '\n');
2507 if (*psz == '\n')
2508 iLine2++;
2509 else if (psz != pszEnd2)
2510 break;
2511 cCurlyBraces2++;
2512 psz2 = psz;
2513 }
2514 else if (psz == pszEnd2)
2515 psz2 = psz;
2516 else /* found something that can be compared. */
2517 break;
2518 }
2519 }
2520
2521 /* Match the number of ignored closing curly braces. */
2522 if (cCurlyBraces1 != cCurlyBraces2)
2523 return 0;
2524
2525 /* Reaching the end of any of them means the return statement can decide. */
2526 if ( psz1 == pszEnd1
2527 || psz2 == pszEnd2)
2528 break;
2529
2530 /* Match the current line. */
2531 psz = memchr(psz1, '\n', pszEnd1 - psz1);
2532 if (!psz)
2533 psz = pszEnd1;
2534 cch = psz - psz1;
2535 if (psz2 + cch > pszEnd2)
2536 break;
2537 if (memcmp(psz1, psz2, cch))
2538 break;
2539
2540 /* Check that we're at the same location now. */
2541 if (!pszFile1)
2542 pszFile1 = kOCEntryFindFileStatement(pszMismatch1, pEntry->New.pszCppMapping, &iLine1);
2543 if (!pszFile2)
2544 pszFile2 = kOCEntryFindFileStatement(pszMismatch2, pEntry->Old.pszCppMapping, &iLine2);
2545 if (pszFile1 && pszFile2)
2546 {
2547 if (iLine1 != iLine2)
2548 break;
2549 while (*pszFile1 == *pszFile2 && *pszFile1 != '\n' && *pszFile1)
2550 pszFile1++, pszFile2++;
2551 if (*pszFile1 != *pszFile2)
2552 break;
2553 }
2554 else if (pszFile1 || pszFile2)
2555 {
2556 assert(0); /* this shouldn't happen. */
2557 break;
2558 }
2559
2560 /* Try align psz1 on 8 or 4 bytes so at least one of the buffers are aligned. */
2561 psz1 += cch;
2562 psz2 += cch;
2563 if (cch >= ((uintptr_t)psz1 & 7))
2564 {
2565 psz2 -= ((uintptr_t)psz1 & 7);
2566 psz1 -= ((uintptr_t)psz1 & 7);
2567 }
2568 else if (cch >= ((uintptr_t)psz1 & 3))
2569 {
2570 psz2 -= ((uintptr_t)psz1 & 3);
2571 psz1 -= ((uintptr_t)psz1 & 3);
2572 }
2573 }
2574 }
2575
2576 return psz1 == pszEnd1
2577 && psz2 == pszEnd2;
2578}
2579
2580
2581/**
2582 * Worker for kOCEntryCompileIfNeeded that compares the
2583 * precompiled output.
2584 *
2585 * @returns 1 if matching, 0 if not matching.
2586 * @param pEntry The entry containing the names of the files to compare.
2587 * This will load the old cpp output (changing pszOldCppName and Old.cbCpp).
2588 */
2589static int kOCEntryCompareOldAndNewOutput(PKOCENTRY pEntry)
2590{
2591 /*
2592 * I may implement a more sophisticated alternative method later... maybe.
2593 */
2594 if (kOCEntryReadCppOutput(pEntry, &pEntry->Old, 1 /* nonfatal */) == -1)
2595 return 0;
2596 //if ()
2597 // return kOCEntryCompareBest(pEntry);
2598 return kOCEntryCompareFast(pEntry);
2599}
2600
2601
2602/**
2603 * Check if re-compilation is required.
2604 * This sets the fNeedCompile flag.
2605 *
2606 * @param pEntry The cache entry.
2607 */
2608static void kOCEntryCalcRecompile(PKOCENTRY pEntry)
2609{
2610 if (pEntry->fNeedCompiling)
2611 return;
2612
2613 /*
2614 * Check if the precompiler output differ in any significant way?
2615 */
2616 if (!kOCSumHasEqualInChain(&pEntry->Old.SumHead, &pEntry->New.SumHead))
2617 {
2618 InfoMsg(2, "no checksum match - comparing output\n");
2619 if (!kOCEntryCompareOldAndNewOutput(pEntry))
2620 pEntry->fNeedCompiling = 1;
2621 else
2622 kOCSumAddChain(&pEntry->New.SumHead, &pEntry->Old.SumHead);
2623 }
2624}
2625
2626
2627/**
2628 * Does this cache entry need compiling or what?
2629 *
2630 * @returns 1 if it does, 0 if it doesn't.
2631 * @param pEntry The cache entry in question.
2632 */
2633static int kOCEntryNeedsCompiling(PCKOCENTRY pEntry)
2634{
2635 return pEntry->fNeedCompiling;
2636}
2637
2638
2639/**
2640 * Worker function for kOCEntryCopy.
2641 *
2642 * @param pEntry The entry we're coping to, which pszTo is relative to.
2643 * @param pszTo The destination.
2644 * @param pszFrom The source. This path will be freed.
2645 */
2646static void kOCEntryCopyFile(PCKOCENTRY pEntry, const char *pszTo, char *pszSrc)
2647{
2648 char *pszDst = MakePathFromDirAndFile(pszTo, pEntry->pszDir);
2649 char *pszBuf = xmalloc(256 * 1024);
2650 char *psz;
2651 int fdSrc;
2652 int fdDst;
2653
2654 /*
2655 * Open the files.
2656 */
2657 fdSrc = open(pszSrc, O_RDONLY | O_BINARY);
2658 if (fdSrc == -1)
2659 FatalDie("failed to open '%s': %s\n", pszSrc, strerror(errno));
2660
2661 unlink(pszDst);
2662 fdDst = open(pszDst, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
2663 if (fdDst == -1)
2664 FatalDie("failed to create '%s': %s\n", pszDst, strerror(errno));
2665
2666 /*
2667 * Copy them.
2668 */
2669 for (;;)
2670 {
2671 /* read a chunk. */
2672 long cbRead = read(fdSrc, pszBuf, 256*1024);
2673 if (cbRead < 0)
2674 {
2675 if (errno == EINTR)
2676 continue;
2677 FatalDie("read '%s' failed: %s\n", pszSrc, strerror(errno));
2678 }
2679 if (!cbRead)
2680 break; /* eof */
2681
2682 /* write the chunk. */
2683 psz = pszBuf;
2684 do
2685 {
2686 long cbWritten = write(fdDst, psz, cbRead);
2687 if (cbWritten < 0)
2688 {
2689 if (errno == EINTR)
2690 continue;
2691 FatalDie("write '%s' failed: %s\n", pszSrc, strerror(errno));
2692 }
2693 psz += cbWritten;
2694 cbRead -= cbWritten;
2695 } while (cbRead > 0);
2696 }
2697
2698 /* cleanup */
2699 if (close(fdDst) != 0)
2700 FatalDie("closing '%s' failed: %s\n", pszDst, strerror(errno));
2701 close(fdSrc);
2702 free(pszBuf);
2703 free(pszDst);
2704 free(pszSrc);
2705}
2706
2707
2708/**
2709 * Copies the object (and whatever else) from one cache entry to another.
2710 *
2711 * This is called when a matching cache entry has been found and we don't
2712 * need to recompile anything.
2713 *
2714 * @param pEntry The entry to copy to.
2715 * @param pFrom The entry to copy from.
2716 */
2717static void kOCEntryCopy(PKOCENTRY pEntry, PCKOCENTRY pFrom)
2718{
2719 kOCEntryCopyFile(pEntry, pEntry->New.pszObjName,
2720 MakePathFromDirAndFile(pFrom->New.pszObjName
2721 ? pFrom->New.pszObjName : pFrom->Old.pszObjName,
2722 pFrom->pszDir));
2723}
2724
2725
2726/**
2727 * Gets the absolute path to the cache entry.
2728 *
2729 * @returns absolute path to the cache entry.
2730 * @param pEntry The cache entry in question.
2731 */
2732static const char *kOCEntryAbsPath(PCKOCENTRY pEntry)
2733{
2734 return pEntry->pszAbsPath;
2735}
2736
2737
2738
2739
2740
2741
2742/**
2743 * Digest of one cache entry.
2744 *
2745 * This contains all the information required to find a matching
2746 * cache entry without having to open each of the files.
2747 */
2748typedef struct KOCDIGEST
2749{
2750 /** The relative path to the entry. Optional if pszAbsPath is set. */
2751 char *pszRelPath;
2752 /** The absolute path to the entry. Optional if pszRelPath is set. */
2753 char *pszAbsPath;
2754 /** The target os/arch identifier. */
2755 char *pszTarget;
2756 /** A unique number assigned to the entry when it's (re)-inserted
2757 * into the cache. This is used for simple consitency checking. */
2758 uint32_t uKey;
2759 /** The checksum of the compile argument vector. */
2760 KOCSUM SumCompArgv;
2761 /** The list of precompiler output checksums that's . */
2762 KOCSUM SumHead;
2763} KOCDIGEST;
2764/** Pointer to a file digest. */
2765typedef KOCDIGEST *PKOCDIGEST;
2766/** Pointer to a const file digest. */
2767typedef KOCDIGEST *PCKOCDIGEST;
2768
2769
2770/**
2771 * Initializes the specified digest.
2772 *
2773 * @param pDigest The digest.
2774 */
2775static void kOCDigestInit(PKOCDIGEST pDigest)
2776{
2777 memset(pDigest, 0, sizeof(*pDigest));
2778 kOCSumInit(&pDigest->SumHead);
2779}
2780
2781
2782/**
2783 * Initializes the digest for the specified entry.
2784 *
2785 * @param pDigest The (uninitialized) digest.
2786 * @param pEntry The entry.
2787 */
2788static void kOCDigestInitFromEntry(PKOCDIGEST pDigest, PCKOCENTRY pEntry)
2789{
2790 kOCDigestInit(pDigest);
2791
2792 pDigest->uKey = pEntry->uKey;
2793 pDigest->pszTarget = xstrdup(pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget);
2794
2795 kOCSumInit(&pDigest->SumCompArgv);
2796 if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv))
2797 kOCSumAdd(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv);
2798 else
2799 kOCSumAdd(&pDigest->SumCompArgv, &pEntry->Old.SumCompArgv);
2800
2801 kOCSumInit(&pDigest->SumHead);
2802 if (!kOCSumIsEmpty(&pEntry->New.SumHead))
2803 kOCSumAddChain(&pDigest->SumHead, &pEntry->New.SumHead);
2804 else
2805 kOCSumAddChain(&pDigest->SumHead, &pEntry->Old.SumHead);
2806
2807 /** @todo implement selective relative path support. */
2808 pDigest->pszRelPath = NULL;
2809 pDigest->pszAbsPath = xstrdup(kOCEntryAbsPath(pEntry));
2810}
2811
2812
2813/**
2814 * Purges a digest, freeing all resources and returning
2815 * it to the initial state.
2816 *
2817 * @param pDigest The digest.
2818 */
2819static void kOCDigestPurge(PKOCDIGEST pDigest)
2820{
2821 free(pDigest->pszRelPath);
2822 free(pDigest->pszAbsPath);
2823 free(pDigest->pszTarget);
2824 pDigest->pszTarget = pDigest->pszAbsPath = pDigest->pszRelPath = NULL;
2825 pDigest->uKey = 0;
2826 kOCSumDeleteChain(&pDigest->SumCompArgv);
2827 kOCSumDeleteChain(&pDigest->SumHead);
2828}
2829
2830
2831/**
2832 * Returns the absolute path to the entry, calculating
2833 * the path if necessary.
2834 *
2835 * @returns absolute path.
2836 * @param pDigest The digest.
2837 * @param pszDir The cache directory that it might be relative to.
2838 */
2839static const char *kOCDigestAbsPath(PCKOCDIGEST pDigest, const char *pszDir)
2840{
2841 if (!pDigest->pszAbsPath)
2842 {
2843 char *pszPath = MakePathFromDirAndFile(pDigest->pszRelPath, pszDir);
2844 ((PKOCDIGEST)pDigest)->pszAbsPath = AbsPath(pszPath);
2845 free(pszPath);
2846 }
2847 return pDigest->pszAbsPath;
2848}
2849
2850
2851/**
2852 * Checks that the digest matches the
2853 *
2854 * @returns 1 if valid, 0 if invalid in some way.
2855 *
2856 * @param pDigest The digest to validate.
2857 * @param pEntry What to validate it against.
2858 */
2859static int kOCDigestIsValid(PCKOCDIGEST pDigest, PCKOCENTRY pEntry)
2860{
2861 PCKOCSUM pSum;
2862 PCKOCSUM pSumEntry;
2863
2864 if (pDigest->uKey != pEntry->uKey)
2865 return 0;
2866
2867 if (!kOCSumIsEqual(&pDigest->SumCompArgv,
2868 kOCSumIsEmpty(&pEntry->New.SumCompArgv)
2869 ? &pEntry->Old.SumCompArgv : &pEntry->New.SumCompArgv))
2870 return 0;
2871
2872 if (strcmp(pDigest->pszTarget, pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget))
2873 return 0;
2874
2875 /* match the checksums */
2876 pSumEntry = kOCSumIsEmpty(&pEntry->New.SumHead)
2877 ? &pEntry->Old.SumHead : &pEntry->New.SumHead;
2878 for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext)
2879 if (!kOCSumHasEqualInChain(pSumEntry, pSum))
2880 return 0;
2881
2882 return 1;
2883}
2884
2885
2886
2887
2888
2889/**
2890 * The structure for the central cache entry.
2891 */
2892typedef struct KOBJCACHE
2893{
2894 /** The entry name. */
2895 const char *pszName;
2896 /** The dir that relative names in the digest are relative to. */
2897 char *pszDir;
2898 /** The absolute path. */
2899 char *pszAbsPath;
2900
2901 /** The cache file descriptor. */
2902 int fd;
2903 /** The stream associated with fd. */
2904 FILE *pFile;
2905 /** Whether it's currently locked or not. */
2906 unsigned fLocked;
2907 /** Whether the cache file is dirty and needs writing back. */
2908 unsigned fDirty;
2909 /** Whether this is a new cache or not. */
2910 unsigned fNewCache;
2911
2912 /** The cache file generation. */
2913 uint32_t uGeneration;
2914 /** The next valid key. (Determin at load time.) */
2915 uint32_t uNextKey;
2916
2917 /** Number of digests in paDigests. */
2918 unsigned cDigests;
2919 /** Array of digests for the KOCENTRY objects in the cache. */
2920 PKOCDIGEST paDigests;
2921
2922} KOBJCACHE;
2923/** Pointer to a cache. */
2924typedef KOBJCACHE *PKOBJCACHE;
2925/** Pointer to a const cache. */
2926typedef KOBJCACHE const *PCKOBJCACHE;
2927
2928
2929/**
2930 * Creates an empty cache.
2931 *
2932 * This doesn't touch the file system, it just create the data structure.
2933 *
2934 * @returns Pointer to a cache.
2935 * @param pszCacheFile The cache file.
2936 */
2937static PKOBJCACHE kObjCacheCreate(const char *pszCacheFile)
2938{
2939 PKOBJCACHE pCache;
2940 size_t off;
2941
2942 /*
2943 * Allocate an empty entry.
2944 */
2945 pCache = xmallocz(sizeof(*pCache));
2946 pCache->fd = -1;
2947
2948 /*
2949 * Setup the directory and cache file name.
2950 */
2951 pCache->pszAbsPath = AbsPath(pszCacheFile);
2952 pCache->pszName = FindFilenameInPath(pCache->pszAbsPath);
2953 off = pCache->pszName - pCache->pszAbsPath;
2954 if (!off)
2955 FatalDie("Failed to find abs path for '%s'!\n", pszCacheFile);
2956 pCache->pszDir = xmalloc(off);
2957 memcpy(pCache->pszDir, pCache->pszAbsPath, off - 1);
2958 pCache->pszDir[off - 1] = '\0';
2959
2960 return pCache;
2961}
2962
2963
2964/**
2965 * Destroys the cache - closing any open files, freeing up heap memory and such.
2966 *
2967 * @param pCache The cache.
2968 */
2969static void kObjCacheDestroy(PKOBJCACHE pCache)
2970{
2971 if (pCache->pFile)
2972 {
2973 errno = 0;
2974 if (fclose(pCache->pFile) != 0)
2975 FatalMsg("fclose failed: %s\n", strerror(errno));
2976 pCache->pFile = NULL;
2977 pCache->fd = -1;
2978 }
2979 free(pCache->paDigests);
2980 free(pCache->pszAbsPath);
2981 free(pCache->pszDir);
2982 free(pCache);
2983}
2984
2985
2986/**
2987 * Purges the data in the cache object.
2988 *
2989 * @param pCache The cache object.
2990 */
2991static void kObjCachePurge(PKOBJCACHE pCache)
2992{
2993 while (pCache->cDigests > 0)
2994 kOCDigestPurge(&pCache->paDigests[--pCache->cDigests]);
2995 free(pCache->paDigests);
2996 pCache->paDigests = NULL;
2997 pCache->uGeneration = 0;
2998 pCache->uNextKey = 0;
2999}
3000
3001
3002/**
3003 * (Re-)reads the file.
3004 *
3005 * @param pCache The cache to (re)-read.
3006 */
3007static void kObjCacheRead(PKOBJCACHE pCache)
3008{
3009 unsigned i;
3010 char szBuf[8192];
3011 int fBad = 0;
3012
3013 InfoMsg(4, "reading cache file...\n");
3014
3015 /*
3016 * Rewind the file & stream, and associate a temporary buffer
3017 * with the stream to speed up reading.
3018 */
3019 if (lseek(pCache->fd, 0, SEEK_SET) == -1)
3020 FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno));
3021 rewind(pCache->pFile);
3022 if (setvbuf(pCache->pFile, szBuf, _IOFBF, sizeof(szBuf)) != 0)
3023 FatalDie("fdopen(cache-fd,rb) failed: %s\n", strerror(errno));
3024
3025 /*
3026 * Read magic and generation.
3027 */
3028 if ( !fgets(g_szLine, sizeof(g_szLine), pCache->pFile)
3029 || strcmp(g_szLine, "magic=kObjCache-v0.1.0\n"))
3030 {
3031 InfoMsg(2, "bad cache file (magic)\n");
3032 fBad = 1;
3033 }
3034 else if ( !fgets(g_szLine, sizeof(g_szLine), pCache->pFile)
3035 || strncmp(g_szLine, "generation=", sizeof("generation=") - 1))
3036 {
3037 InfoMsg(2, "bad cache file (generation)\n");
3038 fBad = 1;
3039 }
3040 else if ( pCache->uGeneration
3041 && pCache->uGeneration == atol(&g_szLine[sizeof("generation=") - 1]))
3042 {
3043 InfoMsg(3, "drop re-read unmodified cache file\n");
3044 fBad = 0;
3045 }
3046 else
3047 {
3048 int fBadBeforeMissing;
3049
3050 /*
3051 * Read everything (anew).
3052 */
3053 kObjCachePurge(pCache);
3054 do
3055 {
3056 PKOCDIGEST pDigest;
3057 char *pszNl;
3058 char *pszVal;
3059 char *psz;
3060
3061 /* Split the line and drop the trailing newline. */
3062 pszVal = strchr(g_szLine, '=');
3063 if ((fBad = pszVal == NULL))
3064 break;
3065 *pszVal++ = '\0';
3066
3067 pszNl = strchr(pszVal, '\n');
3068 if (pszNl)
3069 *pszNl = '\0';
3070
3071 /* digest '#'? */
3072 psz = strchr(g_szLine, '#');
3073 if (psz)
3074 {
3075 char *pszNext;
3076 i = strtoul(++psz, &pszNext, 0);
3077 if ((fBad = pszNext && *pszNext))
3078 break;
3079 if ((fBad = i >= pCache->cDigests))
3080 break;
3081 pDigest = &pCache->paDigests[i];
3082 *psz = '\0';
3083 }
3084 else
3085 pDigest = NULL;
3086
3087
3088 /* string case on value name. */
3089 if (!strcmp(g_szLine, "sum-#"))
3090 {
3091 KOCSUM Sum;
3092 if ((fBad = kOCSumInitFromString(&Sum, pszVal) != 0))
3093 break;
3094 kOCSumAdd(&pDigest->SumHead, &Sum);
3095 }
3096 else if (!strcmp(g_szLine, "digest-abs-#"))
3097 {
3098 if ((fBad = pDigest->pszAbsPath != NULL))
3099 break;
3100 pDigest->pszAbsPath = xstrdup(pszVal);
3101 }
3102 else if (!strcmp(g_szLine, "digest-rel-#"))
3103 {
3104 if ((fBad = pDigest->pszRelPath != NULL))
3105 break;
3106 pDigest->pszRelPath = xstrdup(pszVal);
3107 }
3108 else if (!strcmp(g_szLine, "key-#"))
3109 {
3110 if ((fBad = pDigest->uKey != 0))
3111 break;
3112 pDigest->uKey = strtoul(pszVal, &psz, 0);
3113 if ((fBad = psz && *psz))
3114 break;
3115 if (pDigest->uKey >= pCache->uNextKey)
3116 pCache->uNextKey = pDigest->uKey + 1;
3117 }
3118 else if (!strcmp(g_szLine, "comp-argv-sum-#"))
3119 {
3120 if ((fBad = !kOCSumIsEmpty(&pDigest->SumCompArgv)))
3121 break;
3122 if ((fBad = kOCSumInitFromString(&pDigest->SumCompArgv, pszVal) != 0))
3123 break;
3124 }
3125 else if (!strcmp(g_szLine, "target-#"))
3126 {
3127 if ((fBad = pDigest->pszTarget != NULL))
3128 break;
3129 pDigest->pszTarget = xstrdup(pszVal);
3130 }
3131 else if (!strcmp(g_szLine, "digests"))
3132 {
3133 if ((fBad = pCache->paDigests != NULL))
3134 break;
3135 pCache->cDigests = strtoul(pszVal, &psz, 0);
3136 if ((fBad = psz && *psz))
3137 break;
3138 i = (pCache->cDigests + 4) & ~3;
3139 pCache->paDigests = xmalloc(i * sizeof(pCache->paDigests[0]));
3140 for (i = 0; i < pCache->cDigests; i++)
3141 kOCDigestInit(&pCache->paDigests[i]);
3142 }
3143 else if (!strcmp(g_szLine, "generation"))
3144 {
3145 if ((fBad = pCache->uGeneration != 0))
3146 break;
3147 pCache->uGeneration = strtoul(pszVal, &psz, 0);
3148 if ((fBad = psz && *psz))
3149 break;
3150 }
3151 else if (!strcmp(g_szLine, "the-end"))
3152 {
3153 fBad = strcmp(pszVal, "fine");
3154 break;
3155 }
3156 else
3157 {
3158 fBad = 1;
3159 break;
3160 }
3161 } while (fgets(g_szLine, sizeof(g_szLine), pCache->pFile));
3162
3163 /*
3164 * Did we find everything?
3165 */
3166 fBadBeforeMissing = fBad;
3167 if ( !fBad
3168 && !pCache->uGeneration)
3169 fBad = 1;
3170 if (!fBad)
3171 for (i = 0; i < pCache->cDigests; i++)
3172 {
3173 if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumCompArgv)))
3174 break;
3175 if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumHead)))
3176 break;
3177 if ((fBad = pCache->paDigests[i].uKey == 0))
3178 break;
3179 if ((fBad = pCache->paDigests[i].pszAbsPath == NULL
3180 && pCache->paDigests[i].pszRelPath == NULL))
3181 break;
3182 if ((fBad = pCache->paDigests[i].pszTarget == NULL))
3183 break;
3184 InfoMsg(4, "digest-%u: %s\n", i, pCache->paDigests[i].pszAbsPath
3185 ? pCache->paDigests[i].pszAbsPath : pCache->paDigests[i].pszRelPath);
3186 }
3187 if (fBad)
3188 InfoMsg(2, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff");
3189 else if (ferror(pCache->pFile))
3190 {
3191 InfoMsg(2, "cache file read error\n");
3192 fBad = 1;
3193 }
3194 }
3195 if (fBad)
3196 {
3197 kObjCachePurge(pCache);
3198 pCache->fNewCache = 1;
3199 }
3200
3201 /*
3202 * Disassociate the buffer from the stream changing
3203 * it to non-buffered mode.
3204 */
3205 if (setvbuf(pCache->pFile, NULL, _IONBF, 0) != 0)
3206 FatalDie("setvbuf(,0,,0) failed: %s\n", strerror(errno));
3207}
3208
3209
3210/**
3211 * Re-writes the cache file.
3212 *
3213 * @param pCache The cache to commit and unlock.
3214 */
3215static void kObjCacheWrite(PKOBJCACHE pCache)
3216{
3217 unsigned i;
3218 off_t cb;
3219 char szBuf[8192];
3220 assert(pCache->fLocked);
3221 assert(pCache->fDirty);
3222
3223 /*
3224 * Rewind the file & stream, and associate a temporary buffer
3225 * with the stream to speed up the writing.
3226 */
3227 if (lseek(pCache->fd, 0, SEEK_SET) == -1)
3228 FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno));
3229 rewind(pCache->pFile);
3230 if (setvbuf(pCache->pFile, szBuf, _IOFBF, sizeof(szBuf)) != 0)
3231 FatalDie("setvbuf failed: %s\n", strerror(errno));
3232
3233 /*
3234 * Write the header.
3235 */
3236 pCache->uGeneration++;
3237 fprintf(pCache->pFile,
3238 "magic=kObjCache-v0.1.0\n"
3239 "generation=%d\n"
3240 "digests=%d\n",
3241 pCache->uGeneration,
3242 pCache->cDigests);
3243
3244 /*
3245 * Write the digests.
3246 */
3247 for (i = 0; i < pCache->cDigests; i++)
3248 {
3249 PCKOCDIGEST pDigest = &pCache->paDigests[i];
3250 PKOCSUM pSum;
3251
3252 if (pDigest->pszAbsPath)
3253 fprintf(pCache->pFile, "digest-abs-#%u=%s\n", i, pDigest->pszAbsPath);
3254 if (pDigest->pszRelPath)
3255 fprintf(pCache->pFile, "digest-rel-#%u=%s\n", i, pDigest->pszRelPath);
3256 fprintf(pCache->pFile, "key-#%u=%u\n", i, pDigest->uKey);
3257 fprintf(pCache->pFile, "target-#%u=%s\n", i, pDigest->pszTarget);
3258 fprintf(pCache->pFile, "comp-argv-sum-#%u=", i);
3259 kOCSumFPrintf(&pDigest->SumCompArgv, pCache->pFile);
3260 for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext)
3261 {
3262 fprintf(pCache->pFile, "sum-#%u=", i);
3263 kOCSumFPrintf(pSum, pCache->pFile);
3264 }
3265 }
3266
3267 /*
3268 * Close the stream and unlock fhe file.
3269 * (Closing the stream shouldn't close the file handle IIRC...)
3270 */
3271 fprintf(pCache->pFile, "the-end=fine\n");
3272 errno = 0;
3273 if ( fflush(pCache->pFile) < 0
3274 || ferror(pCache->pFile))
3275 {
3276 int iErr = errno;
3277 fclose(pCache->pFile);
3278 UnlinkFileInDir(pCache->pszName, pCache->pszDir);
3279 FatalDie("Stream error occured while writing '%s' in '%s': %s\n",
3280 pCache->pszName, pCache->pszDir, strerror(iErr));
3281 }
3282 if (setvbuf(pCache->pFile, NULL, _IONBF, 0) != 0)
3283 FatalDie("setvbuf(,0,,0) failed: %s\n", strerror(errno));
3284
3285 cb = lseek(pCache->fd, 0, SEEK_CUR);
3286 if (cb == -1)
3287 FatalDie("lseek(cache-file,0,CUR) failed: %s\n", strerror(errno));
3288#if defined(__WIN__)
3289 if (_chsize(pCache->fd, cb) == -1)
3290#else
3291 if (ftruncate(pCache->fd, cb) == -1)
3292#endif
3293 FatalDie("file truncation failed: %s\n", strerror(errno));
3294 InfoMsg(4, "wrote '%s' in '%s', %d bytes\n", pCache->pszName, pCache->pszDir, cb);
3295}
3296
3297
3298/**
3299 * Cleans out all invalid digests.s
3300 *
3301 * This is done periodically from the unlock routine to make
3302 * sure we don't accidentally accumulate stale digests.
3303 *
3304 * @param pCache The cache to chek.
3305 */
3306static void kObjCacheClean(PKOBJCACHE pCache)
3307{
3308 unsigned i = pCache->cDigests;
3309 while (i-- > 0)
3310 {
3311 /*
3312 * Try open it and purge it if it's bad.
3313 * (We don't kill the entry file because that's kmk clean's job.)
3314 */
3315 PCKOCDIGEST pDigest = &pCache->paDigests[i];
3316 PKOCENTRY pEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir));
3317 kOCEntryRead(pEntry);
3318 if ( !kOCEntryCheck(pEntry)
3319 || !kOCDigestIsValid(pDigest, pEntry))
3320 {
3321 unsigned cLeft;
3322 kOCDigestPurge(pDigest);
3323
3324 pCache->cDigests--;
3325 cLeft = pCache->cDigests - i;
3326 if (cLeft)
3327 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3328
3329 pCache->fDirty = 1;
3330 }
3331 kOCEntryDestroy(pEntry);
3332 }
3333}
3334
3335
3336/**
3337 * Locks the cache for exclusive access.
3338 *
3339 * This will open the file if necessary and lock the entire file
3340 * using the best suitable platform API (tricky).
3341 *
3342 * @param pCache The cache to lock.
3343 */
3344static void kObjCacheLock(PKOBJCACHE pCache)
3345{
3346 struct stat st;
3347#if defined(__WIN__)
3348 OVERLAPPED OverLapped;
3349#endif
3350
3351 assert(!pCache->fLocked);
3352
3353 /*
3354 * Open it?
3355 */
3356 if (pCache->fd < 0)
3357 {
3358 pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666);
3359 if (pCache->fd == -1)
3360 {
3361 MakePath(pCache->pszDir);
3362 pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666);
3363 if (pCache->fd == -1)
3364 FatalDie("Failed to create '%s' in '%s': %s\n", pCache->pszName, pCache->pszDir, strerror(errno));
3365 }
3366
3367 pCache->pFile = fdopen(pCache->fd, "r+b");
3368 if (!pCache->pFile)
3369 FatalDie("fdopen failed: %s\n", strerror(errno));
3370 if (setvbuf(pCache->pFile, NULL, _IONBF, 0) != 0)
3371 FatalDie("setvbuf(,0,,0) failed: %s\n", strerror(errno));
3372 }
3373
3374 /*
3375 * Lock it.
3376 */
3377#if defined(__WIN__)
3378 memset(&OverLapped, 0, sizeof(OverLapped));
3379 if (!LockFileEx((HANDLE)_get_osfhandle(pCache->fd), LOCKFILE_EXCLUSIVE_LOCK, 0, ~0, 0, &OverLapped))
3380 FatalDie("Failed to lock the cache file: Windows Error %d\n", GetLastError());
3381#else
3382 if (flock(pCache->fd, LOCK_EX) != 0)
3383 FatalDie("Failed to lock the cache file: %s\n", strerror(errno));
3384#endif
3385 pCache->fLocked = 1;
3386
3387 /*
3388 * Check for new cache and read it it's an existing cache.
3389 *
3390 * There is no point in initializing a new cache until we've finished
3391 * compiling and has something to put into it, so we'll leave it as a
3392 * 0 byte file.
3393 */
3394 if (fstat(pCache->fd, &st) == -1)
3395 FatalDie("fstat(cache-fd) failed: %s\n", strerror(errno));
3396 if (st.st_size)
3397 kObjCacheRead(pCache);
3398 else
3399 {
3400 pCache->fNewCache = 1;
3401 InfoMsg(2, "the cache file is empty\n");
3402 }
3403}
3404
3405
3406/**
3407 * Unlocks the cache (without writing anything back).
3408 *
3409 * @param pCache The cache to unlock.
3410 */
3411static void kObjCacheUnlock(PKOBJCACHE pCache)
3412{
3413#if defined(__WIN__)
3414 OVERLAPPED OverLapped;
3415#endif
3416 assert(pCache->fLocked);
3417
3418 /*
3419 * Write it back if it's dirty.
3420 */
3421 if (pCache->fDirty)
3422 {
3423 if ( pCache->cDigests >= 16
3424 && (pCache->uGeneration % 19) == 19)
3425 kObjCacheClean(pCache);
3426 kObjCacheWrite(pCache);
3427 pCache->fDirty = 0;
3428 }
3429
3430 /*
3431 * Lock it.
3432 */
3433#if defined(__WIN__)
3434 memset(&OverLapped, 0, sizeof(OverLapped));
3435 if (!UnlockFileEx((HANDLE)_get_osfhandle(pCache->fd), 0, ~0U, 0, &OverLapped))
3436 FatalDie("Failed to unlock the cache file: Windows Error %d\n", GetLastError());
3437#else
3438 if (flock(pCache->fd, LOCK_UN) != 0)
3439 FatalDie("Failed to unlock the cache file: %s\n", strerror(errno));
3440#endif
3441 pCache->fLocked = 0;
3442}
3443
3444
3445/**
3446 * Removes the entry from the cache.
3447 *
3448 * The entry doesn't need to be in the cache.
3449 * The cache entry (file) itself is not touched.
3450 *
3451 * @param pCache The cache.
3452 * @param pEntry The entry.
3453 */
3454static void kObjCacheRemoveEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
3455{
3456 unsigned i = pCache->cDigests;
3457 while (i-- > 0)
3458 {
3459 PKOCDIGEST pDigest = &pCache->paDigests[i];
3460 if (ArePathsIdentical(kOCDigestAbsPath(pDigest, pCache->pszDir),
3461 kOCEntryAbsPath(pEntry), ~0U))
3462 {
3463 unsigned cLeft;
3464 kOCDigestPurge(pDigest);
3465
3466 pCache->cDigests--;
3467 cLeft = pCache->cDigests - i;
3468 if (cLeft)
3469 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3470
3471 pCache->fDirty = 1;
3472 InfoMsg(3, "removing entry '%s'; %d left.\n", kOCEntryAbsPath(pEntry), pCache->cDigests);
3473 }
3474 }
3475}
3476
3477
3478/**
3479 * Inserts the entry into the cache.
3480 *
3481 * The cache entry (file) itself is not touched by this operation,
3482 * the pEntry object otoh is.
3483 *
3484 * @param pCache The cache.
3485 * @param pEntry The entry.
3486 */
3487static void kObjCacheInsertEntry(PKOBJCACHE pCache, PKOCENTRY pEntry)
3488{
3489 unsigned i;
3490
3491 /*
3492 * Find a new key.
3493 */
3494 pEntry->uKey = pCache->uNextKey++;
3495 if (!pEntry->uKey)
3496 pEntry->uKey = pCache->uNextKey++;
3497 i = pCache->cDigests;
3498 while (i-- > 0)
3499 if (pCache->paDigests[i].uKey == pEntry->uKey)
3500 {
3501 pEntry->uKey = pCache->uNextKey++;
3502 if (!pEntry->uKey)
3503 pEntry->uKey = pCache->uNextKey++;
3504 i = pCache->cDigests;
3505 }
3506
3507 /*
3508 * Reallocate the digest array?
3509 */
3510 if ( !(pCache->cDigests & 3)
3511 && (pCache->cDigests || !pCache->paDigests))
3512 pCache->paDigests = xrealloc(pCache->paDigests, sizeof(pCache->paDigests[0]) * (pCache->cDigests + 4));
3513
3514 /*
3515 * Create a new digest.
3516 */
3517 kOCDigestInitFromEntry(&pCache->paDigests[pCache->cDigests], pEntry);
3518 pCache->cDigests++;
3519 InfoMsg(4, "Inserted digest #%u: %s\n", pCache->cDigests - 1, kOCEntryAbsPath(pEntry));
3520
3521 pCache->fDirty = 1;
3522}
3523
3524
3525/**
3526 * Find a matching cache entry.
3527 */
3528static PKOCENTRY kObjCacheFindMatchingEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
3529{
3530 unsigned i = pCache->cDigests;
3531
3532 assert(pEntry->fNeedCompiling);
3533 assert(!kOCSumIsEmpty(&pEntry->New.SumCompArgv));
3534 assert(!kOCSumIsEmpty(&pEntry->New.SumHead));
3535
3536 while (i-- > 0)
3537 {
3538 /*
3539 * Matching?
3540 */
3541 PCKOCDIGEST pDigest = &pCache->paDigests[i];
3542 if ( kOCSumIsEqual(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv)
3543 && kOCSumHasEqualInChain(&pDigest->SumHead, &pEntry->New.SumHead))
3544 {
3545 /*
3546 * Try open it.
3547 */
3548 unsigned cLeft;
3549 PKOCENTRY pRetEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir));
3550 kOCEntryRead(pRetEntry);
3551 if ( kOCEntryCheck(pRetEntry)
3552 && kOCDigestIsValid(pDigest, pRetEntry))
3553 return pRetEntry;
3554 kOCEntryDestroy(pRetEntry);
3555
3556 /* bad entry, purge it. */
3557 InfoMsg(3, "removing bad digest '%s'\n", kOCDigestAbsPath(pDigest, pCache->pszDir));
3558 kOCDigestPurge(pDigest);
3559
3560 pCache->cDigests--;
3561 cLeft = pCache->cDigests - i;
3562 if (cLeft)
3563 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3564
3565 pCache->fDirty = 1;
3566 }
3567 }
3568
3569 return NULL;
3570}
3571
3572
3573/**
3574 * Is this a new cache?
3575 *
3576 * @returns 1 if new, 0 if not new.
3577 * @param pEntry The entry.
3578 */
3579static int kObjCacheIsNew(PKOBJCACHE pCache)
3580{
3581 return pCache->fNewCache;
3582}
3583
3584
3585/**
3586 * Prints a syntax error and returns the appropriate exit code
3587 *
3588 * @returns approriate exit code.
3589 * @param pszFormat The syntax error message.
3590 * @param ... Message args.
3591 */
3592static int SyntaxError(const char *pszFormat, ...)
3593{
3594 va_list va;
3595 fprintf(stderr, "kObjCache: syntax error: ");
3596 va_start(va, pszFormat);
3597 vfprintf(stderr, pszFormat, va);
3598 va_end(va);
3599 return 1;
3600}
3601
3602
3603/**
3604 * Prints the usage.
3605 * @returns 0.
3606 */
3607static int usage(void)
3608{
3609 printf("syntax: kObjCache [--kObjCache-options] [-v|--verbose]\n"
3610 " < [-c|--cache-file <cache-file>]\n"
3611 " | [-n|--name <name-in-cache>] [[-d|--cache-dir <cache-dir>]] >\n"
3612 " <-f|--file <local-cache-file>>\n"
3613 " <-t|--target <target-name>>\n"
3614 " [-r|--redir-stdout] [-p|--passthru]\n"
3615 " --kObjCache-cpp <filename> <precompiler + args>\n"
3616 " --kObjCache-cc <object> <compiler + args>\n"
3617 " [--kObjCache-both [args]]\n"
3618 " [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
3619 " kObjCache <-V|--version>\n"
3620 " kObjCache [-?|/?|-h|/h|--help|/help]\n"
3621 "\n"
3622 "The env.var. KOBJCACHE_DIR sets the default cache diretory (-d).\n"
3623 "The env.var. KOBJCACHE_OPTS allow you to specifie additional options\n"
3624 "without having to mess with the makefiles. These are appended with "
3625 "a --kObjCache-options between them and the command args.\n"
3626 "\n");
3627 return 0;
3628}
3629
3630
3631int main(int argc, char **argv)
3632{
3633 PKOBJCACHE pCache;
3634 PKOCENTRY pEntry;
3635
3636 const char *pszCacheDir = getenv("KOBJCACHE_DIR");
3637 const char *pszCacheName = NULL;
3638 const char *pszCacheFile = NULL;
3639 const char *pszEntryFile = NULL;
3640
3641 const char **papszArgvPreComp = NULL;
3642 unsigned cArgvPreComp = 0;
3643 const char *pszPreCompName = NULL;
3644 int fRedirPreCompStdOut = 0;
3645
3646 const char **papszArgvCompile = NULL;
3647 unsigned cArgvCompile = 0;
3648 const char *pszObjName = NULL;
3649 int fRedirCompileStdIn = 0;
3650
3651 const char *pszTarget = NULL;
3652
3653 enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
3654
3655 size_t cch;
3656 char *psz;
3657 int i;
3658
3659 SetErrorPrefix("kObjCache");
3660
3661 /*
3662 * Arguments passed in the environmnet?
3663 */
3664 psz = getenv("KOBJCACHE_OPTS");
3665 if (psz)
3666 AppendArgs(&argc, &argv, psz, "--kObjCache-options");
3667
3668 /*
3669 * Parse the arguments.
3670 */
3671 if (argc <= 1)
3672 return usage();
3673 for (i = 1; i < argc; i++)
3674 {
3675 if (!strcmp(argv[i], "--kObjCache-cpp"))
3676 {
3677 enmMode = kOC_CppArgv;
3678 if (!pszPreCompName)
3679 {
3680 if (++i >= argc)
3681 return SyntaxError("--kObjCache-cpp requires an object filename!\n");
3682 pszPreCompName = argv[i];
3683 }
3684 }
3685 else if (!strcmp(argv[i], "--kObjCache-cc"))
3686 {
3687 enmMode = kOC_CcArgv;
3688 if (!pszObjName)
3689 {
3690 if (++i >= argc)
3691 return SyntaxError("--kObjCache-cc requires an precompiler output filename!\n");
3692 pszObjName = argv[i];
3693 }
3694 }
3695 else if (!strcmp(argv[i], "--kObjCache-both"))
3696 enmMode = kOC_BothArgv;
3697 else if (!strcmp(argv[i], "--kObjCache-options"))
3698 enmMode = kOC_Options;
3699 else if (!strcmp(argv[i], "--help"))
3700 return usage();
3701 else if (enmMode != kOC_Options)
3702 {
3703 if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv)
3704 {
3705 if (!(cArgvPreComp % 16))
3706 papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0]));
3707 papszArgvPreComp[cArgvPreComp++] = argv[i];
3708 papszArgvPreComp[cArgvPreComp] = NULL;
3709 }
3710 if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv)
3711 {
3712 if (!(cArgvCompile % 16))
3713 papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0]));
3714 papszArgvCompile[cArgvCompile++] = argv[i];
3715 papszArgvCompile[cArgvCompile] = NULL;
3716 }
3717 }
3718 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--entry-file"))
3719 {
3720 if (i + 1 >= argc)
3721 return SyntaxError("%s requires a cache entry filename!\n", argv[i]);
3722 pszEntryFile = argv[++i];
3723 }
3724 else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache-file"))
3725 {
3726 if (i + 1 >= argc)
3727 return SyntaxError("%s requires a cache filename!\n", argv[i]);
3728 pszCacheFile = argv[++i];
3729 }
3730 else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--name"))
3731 {
3732 if (i + 1 >= argc)
3733 return SyntaxError("%s requires a cache name!\n", argv[i]);
3734 pszCacheName = argv[++i];
3735 }
3736 else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--cache-dir"))
3737 {
3738 if (i + 1 >= argc)
3739 return SyntaxError("%s requires a cache directory!\n", argv[i]);
3740 pszCacheDir = argv[++i];
3741 }
3742 else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--target"))
3743 {
3744 if (i + 1 >= argc)
3745 return SyntaxError("%s requires a target platform/arch name!\n", argv[i]);
3746 pszTarget = argv[++i];
3747 }
3748 else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--passthru"))
3749 fRedirPreCompStdOut = fRedirCompileStdIn = 1;
3750 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
3751 fRedirPreCompStdOut = 1;
3752 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
3753 g_cVerbosityLevel++;
3754 else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
3755 g_cVerbosityLevel = 0;
3756 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?")
3757 || !strcmp(argv[i], "/h") || !strcmp(argv[i], "/?") || !strcmp(argv[i], "/help"))
3758 return usage();
3759 else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version"))
3760 {
3761 printf("kObjCache v0.1.0 ($Revision: 1053 $)\n");
3762 return 0;
3763 }
3764 else
3765 return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
3766 }
3767 if (!pszEntryFile)
3768 return SyntaxError("No cache entry filename (-f)!\n");
3769 if (!pszTarget)
3770 return SyntaxError("No target name (-t)!\n");
3771 if (!cArgvCompile)
3772 return SyntaxError("No compiler arguments (--kObjCache-cc)!\n");
3773 if (!cArgvPreComp)
3774 return SyntaxError("No precompiler arguments (--kObjCache-cc)!\n");
3775
3776 /*
3777 * Calc the cache file name.
3778 * It's a bit messy since the extension has to be replaced.
3779 */
3780 if (!pszCacheFile)
3781 {
3782 if (!pszCacheDir)
3783 return SyntaxError("No cache dir (-d / KOBJCACHE_DIR) and no cache filename!\n");
3784 if (!pszCacheName)
3785 {
3786 psz = (char *)FindFilenameInPath(pszEntryFile);
3787 if (!*psz)
3788 return SyntaxError("The cache file (-f) specifies a directory / nothing!\n");
3789 cch = psz - pszEntryFile;
3790 pszCacheName = memcpy(xmalloc(cch + 5), psz, cch + 1);
3791 psz = strrchr(pszCacheName, '.');
3792 if (!psz || psz <= pszCacheName)
3793 psz = (char *)pszCacheName + cch;
3794 memcpy(psz, ".koc", sizeof(".koc") - 1);
3795 }
3796 pszCacheFile = MakePathFromDirAndFile(pszCacheName, pszCacheDir);
3797 }
3798
3799 /*
3800 * Create and initialize the two objects we'll be working on.
3801 *
3802 * We're supposed to be the only ones actually writing to the local file,
3803 * so it's perfectly fine to read it here before we lock it. This simplifies
3804 * the detection of object name and compiler argument changes.
3805 */
3806 SetErrorPrefix("kObjCache - %s", FindFilenameInPath(pszCacheFile));
3807 pCache = kObjCacheCreate(pszCacheFile);
3808
3809 pEntry = kOCEntryCreate(pszEntryFile);
3810 kOCEntryRead(pEntry);
3811 kOCEntrySetCompileObjName(pEntry, pszObjName);
3812 kOCEntrySetCompileArgv(pEntry, papszArgvCompile, cArgvCompile);
3813 kOCEntrySetTarget(pEntry, pszTarget);
3814 kOCEntrySetCppName(pEntry, pszPreCompName);
3815 kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn);
3816
3817 /*
3818 * Open (& lock) the two files and do validity checks and such.
3819 */
3820 kObjCacheLock(pCache);
3821 if ( kObjCacheIsNew(pCache)
3822 && kOCEntryNeedsCompiling(pEntry))
3823 {
3824 /*
3825 * Both files are missing/invalid.
3826 * Optimize this path as it is frequently used when making a clean build.
3827 */
3828 kObjCacheUnlock(pCache);
3829 InfoMsg(1, "doing full compile\n");
3830 kOCEntryPreCompileAndCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3831 kObjCacheLock(pCache);
3832 }
3833 else
3834 {
3835 /*
3836 * Do the precompile (don't need to lock the cache file for this).
3837 */
3838 kObjCacheUnlock(pCache);
3839 kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3840
3841 /*
3842 * Check if we need to recompile. If we do, try see if the is a cache entry first.
3843 */
3844 kOCEntryCalcRecompile(pEntry);
3845 if (kOCEntryNeedsCompiling(pEntry))
3846 {
3847 PKOCENTRY pUseEntry;
3848 kObjCacheLock(pCache);
3849 kObjCacheRemoveEntry(pCache, pEntry);
3850 pUseEntry = kObjCacheFindMatchingEntry(pCache, pEntry);
3851 if (pUseEntry)
3852 {
3853 InfoMsg(1, "using cache entry '%s'\n", kOCEntryAbsPath(pUseEntry));
3854 kOCEntryCopy(pEntry, pUseEntry);
3855 kOCEntryDestroy(pUseEntry);
3856 }
3857 else
3858 {
3859 kObjCacheUnlock(pCache);
3860 InfoMsg(1, "recompiling\n");
3861 kOCEntryCompileIt(pEntry);
3862 kObjCacheLock(pCache);
3863 }
3864 }
3865 else
3866 {
3867 InfoMsg(1, "no need to recompile\n");
3868 kObjCacheLock(pCache);
3869 }
3870 }
3871
3872 /*
3873 * Update the cache files.
3874 */
3875 kObjCacheRemoveEntry(pCache, pEntry);
3876 kObjCacheInsertEntry(pCache, pEntry);
3877 kOCEntryWrite(pEntry);
3878 kObjCacheUnlock(pCache);
3879 kObjCacheDestroy(pCache);
3880 return 0;
3881}
3882
3883
3884/** @page kObjCache Benchmarks.
3885 *
3886 * (2007-06-10)
3887 *
3888 * Mac OS X debug -j 3 cached clobber build (rm -Rf out ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
3889 * real 11m28.811s
3890 * user 13m59.291s
3891 * sys 3m24.590s
3892 *
3893 * Mac OS X debug -j 3 cached depend build [cdefs.h] (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
3894 * real 1m26.895s
3895 * user 1m26.971s
3896 * sys 0m32.532s
3897 *
3898 * Mac OS X debug -j 3 cached depend build [err.h] (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
3899 * real 1m18.049s
3900 * user 1m20.462s
3901 * sys 0m27.887s
3902 *
3903 * Mac OS X release -j 3 cached clobber build (rm -Rf out/darwin.x86/release ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1 BUILD_TYPE=release):
3904 * real 13m27.751s
3905 * user 18m12.654s
3906 * sys 3m25.170s
3907 *
3908 * Mac OS X profile -j 3 cached clobber build (rm -Rf out/darwin.x86/profile ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1 BUILD_TYPE=profile):
3909 * real 9m9.720s
3910 * user 8m53.005s
3911 * sys 2m13.110s
3912 *
3913 * Mac OS X debug -j 3 clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=debug):
3914 * real 10m18.129s
3915 * user 12m52.687s
3916 * sys 2m51.277s
3917 *
3918 * Mac OS X debug -j 3 debug build [cdefs.h] (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=debug):
3919 * real 4m46.147s
3920 * user 5m27.087s
3921 * sys 1m11.775s
3922 *
3923 * Mac OS X debug -j 3 debug build [err.h] (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=debug):
3924 * real 4m17.572s
3925 * user 5m7.450s
3926 * sys 1m3.450s
3927 *
3928 * Mac OS X release -j 3 clobber build (rm -Rf out/darwin.x86/release ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=release):
3929 * real 12m14.742s
3930 * user 17m11.794s
3931 * sys 2m51.454s
3932 *
3933 * Mac OS X profile -j 3 clobber build (rm -Rf out/darwin.x86/profile ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=profile):
3934 * real 12m33.821s
3935 * user 17m35.086s
3936 * sys 2m53.312s
3937 *
3938 * Note. The profile build can pick object files from the release build.
3939 * (all with KOBJCACHE_OPTS=-v; which means a bit more output and perhaps a second or two slower.)
3940 */
3941
Note: See TracBrowser for help on using the repository browser.

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