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

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

flock is BSD, use flock on Solaris.

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