VirtualBox

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

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

When matching the current line, the newline must be included or we might end up spinning for ever if the 2nd line is a subset of the 1st. Part of the blame here is also on the buffer realignment, which would cause us to be stuck on the same line.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 115.5 KB
Line 
1/* $Id: kObjCache.c 1087 2007-08-29 12:40:58Z bird $ */
2/** @file
3 *
4 * kObjCache - Object Cache.
5 *
6 * Copyright (c) 2007 knut st. osmundsen <[email protected]>
7 *
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#if 0
32# define ELECTRIC_HEAP
33# include "../kmk/electric.h"
34#endif
35#include <string.h>
36#include <stdlib.h>
37#include <stdarg.h>
38#include <stdio.h>
39#include <errno.h>
40#include <assert.h>
41#include <sys/stat.h>
42#include <fcntl.h>
43#include <limits.h>
44#include <ctype.h>
45#ifndef PATH_MAX
46# define PATH_MAX _MAX_PATH /* windows */
47#endif
48#if defined(__OS2__) || defined(__WIN__)
49# include <process.h>
50# include <io.h>
51# ifdef __OS2__
52# include <unistd.h>
53# endif
54# if defined(_MSC_VER)
55# 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#else
3373 if (flock(pCache->fd, LOCK_EX) != 0)
3374 FatalDie("Failed to lock the cache file: %s\n", strerror(errno));
3375#endif
3376 pCache->fLocked = 1;
3377
3378 /*
3379 * Check for new cache and read it it's an existing cache.
3380 *
3381 * There is no point in initializing a new cache until we've finished
3382 * compiling and has something to put into it, so we'll leave it as a
3383 * 0 byte file.
3384 */
3385 if (fstat(pCache->fd, &st) == -1)
3386 FatalDie("fstat(cache-fd) failed: %s\n", strerror(errno));
3387 if (st.st_size)
3388 kObjCacheRead(pCache);
3389 else
3390 {
3391 pCache->fNewCache = 1;
3392 InfoMsg(2, "the cache file is empty\n");
3393 }
3394}
3395
3396
3397/**
3398 * Unlocks the cache (without writing anything back).
3399 *
3400 * @param pCache The cache to unlock.
3401 */
3402static void kObjCacheUnlock(PKOBJCACHE pCache)
3403{
3404#if defined(__WIN__)
3405 OVERLAPPED OverLapped;
3406#endif
3407 assert(pCache->fLocked);
3408
3409 /*
3410 * Write it back if it's dirty.
3411 */
3412 if (pCache->fDirty)
3413 {
3414 if ( pCache->cDigests >= 16
3415 && (pCache->uGeneration % 19) == 19)
3416 kObjCacheClean(pCache);
3417 kObjCacheWrite(pCache);
3418 pCache->fDirty = 0;
3419 }
3420
3421 /*
3422 * Lock it.
3423 */
3424#if defined(__WIN__)
3425 memset(&OverLapped, 0, sizeof(OverLapped));
3426 if (!UnlockFileEx((HANDLE)_get_osfhandle(pCache->fd), 0, ~0U, 0, &OverLapped))
3427 FatalDie("Failed to unlock the cache file: Windows Error %d\n", GetLastError());
3428#else
3429 if (flock(pCache->fd, LOCK_UN) != 0)
3430 FatalDie("Failed to unlock the cache file: %s\n", strerror(errno));
3431#endif
3432 pCache->fLocked = 0;
3433}
3434
3435
3436/**
3437 * Removes the entry from the cache.
3438 *
3439 * The entry doesn't need to be in the cache.
3440 * The cache entry (file) itself is not touched.
3441 *
3442 * @param pCache The cache.
3443 * @param pEntry The entry.
3444 */
3445static void kObjCacheRemoveEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
3446{
3447 unsigned i = pCache->cDigests;
3448 while (i-- > 0)
3449 {
3450 PKOCDIGEST pDigest = &pCache->paDigests[i];
3451 if (ArePathsIdentical(kOCDigestAbsPath(pDigest, pCache->pszDir),
3452 kOCEntryAbsPath(pEntry), ~0U))
3453 {
3454 unsigned cLeft;
3455 kOCDigestPurge(pDigest);
3456
3457 pCache->cDigests--;
3458 cLeft = pCache->cDigests - i;
3459 if (cLeft)
3460 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3461
3462 pCache->fDirty = 1;
3463 InfoMsg(3, "removing entry '%s'; %d left.\n", kOCEntryAbsPath(pEntry), pCache->cDigests);
3464 }
3465 }
3466}
3467
3468
3469/**
3470 * Inserts the entry into the cache.
3471 *
3472 * The cache entry (file) itself is not touched by this operation,
3473 * the pEntry object otoh is.
3474 *
3475 * @param pCache The cache.
3476 * @param pEntry The entry.
3477 */
3478static void kObjCacheInsertEntry(PKOBJCACHE pCache, PKOCENTRY pEntry)
3479{
3480 unsigned i;
3481
3482 /*
3483 * Find a new key.
3484 */
3485 pEntry->uKey = pCache->uNextKey++;
3486 if (!pEntry->uKey)
3487 pEntry->uKey = pCache->uNextKey++;
3488 i = pCache->cDigests;
3489 while (i-- > 0)
3490 if (pCache->paDigests[i].uKey == pEntry->uKey)
3491 {
3492 pEntry->uKey = pCache->uNextKey++;
3493 if (!pEntry->uKey)
3494 pEntry->uKey = pCache->uNextKey++;
3495 i = pCache->cDigests;
3496 }
3497
3498 /*
3499 * Reallocate the digest array?
3500 */
3501 if ( !(pCache->cDigests & 3)
3502 && (pCache->cDigests || !pCache->paDigests))
3503 pCache->paDigests = xrealloc(pCache->paDigests, sizeof(pCache->paDigests[0]) * (pCache->cDigests + 4));
3504
3505 /*
3506 * Create a new digest.
3507 */
3508 kOCDigestInitFromEntry(&pCache->paDigests[pCache->cDigests], pEntry);
3509 pCache->cDigests++;
3510 InfoMsg(4, "Inserted digest #%u: %s\n", pCache->cDigests - 1, kOCEntryAbsPath(pEntry));
3511
3512 pCache->fDirty = 1;
3513}
3514
3515
3516/**
3517 * Find a matching cache entry.
3518 */
3519static PKOCENTRY kObjCacheFindMatchingEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
3520{
3521 unsigned i = pCache->cDigests;
3522
3523 assert(pEntry->fNeedCompiling);
3524 assert(!kOCSumIsEmpty(&pEntry->New.SumCompArgv));
3525 assert(!kOCSumIsEmpty(&pEntry->New.SumHead));
3526
3527 while (i-- > 0)
3528 {
3529 /*
3530 * Matching?
3531 */
3532 PCKOCDIGEST pDigest = &pCache->paDigests[i];
3533 if ( kOCSumIsEqual(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv)
3534 && kOCSumHasEqualInChain(&pDigest->SumHead, &pEntry->New.SumHead))
3535 {
3536 /*
3537 * Try open it.
3538 */
3539 unsigned cLeft;
3540 PKOCENTRY pRetEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir));
3541 kOCEntryRead(pRetEntry);
3542 if ( kOCEntryCheck(pRetEntry)
3543 && kOCDigestIsValid(pDigest, pRetEntry))
3544 return pRetEntry;
3545 kOCEntryDestroy(pRetEntry);
3546
3547 /* bad entry, purge it. */
3548 InfoMsg(3, "removing bad digest '%s'\n", kOCDigestAbsPath(pDigest, pCache->pszDir));
3549 kOCDigestPurge(pDigest);
3550
3551 pCache->cDigests--;
3552 cLeft = pCache->cDigests - i;
3553 if (cLeft)
3554 memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
3555
3556 pCache->fDirty = 1;
3557 }
3558 }
3559
3560 return NULL;
3561}
3562
3563
3564/**
3565 * Is this a new cache?
3566 *
3567 * @returns 1 if new, 0 if not new.
3568 * @param pEntry The entry.
3569 */
3570static int kObjCacheIsNew(PKOBJCACHE pCache)
3571{
3572 return pCache->fNewCache;
3573}
3574
3575
3576/**
3577 * Prints a syntax error and returns the appropriate exit code
3578 *
3579 * @returns approriate exit code.
3580 * @param pszFormat The syntax error message.
3581 * @param ... Message args.
3582 */
3583static int SyntaxError(const char *pszFormat, ...)
3584{
3585 va_list va;
3586 fprintf(stderr, "kObjCache: syntax error: ");
3587 va_start(va, pszFormat);
3588 vfprintf(stderr, pszFormat, va);
3589 va_end(va);
3590 return 1;
3591}
3592
3593
3594/**
3595 * Prints the usage.
3596 * @returns 0.
3597 */
3598static int usage(void)
3599{
3600 printf("syntax: kObjCache [--kObjCache-options] [-v|--verbose]\n"
3601 " < [-c|--cache-file <cache-file>]\n"
3602 " | [-n|--name <name-in-cache>] [[-d|--cache-dir <cache-dir>]] >\n"
3603 " <-f|--file <local-cache-file>>\n"
3604 " <-t|--target <target-name>>\n"
3605 " [-r|--redir-stdout] [-p|--passthru]\n"
3606 " --kObjCache-cpp <filename> <precompiler + args>\n"
3607 " --kObjCache-cc <object> <compiler + args>\n"
3608 " [--kObjCache-both [args]]\n"
3609 " [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
3610 " kObjCache <-V|--version>\n"
3611 " kObjCache [-?|/?|-h|/h|--help|/help]\n"
3612 "\n"
3613 "The env.var. KOBJCACHE_DIR sets the default cache diretory (-d).\n"
3614 "The env.var. KOBJCACHE_OPTS allow you to specifie additional options\n"
3615 "without having to mess with the makefiles. These are appended with "
3616 "a --kObjCache-options between them and the command args.\n"
3617 "\n");
3618 return 0;
3619}
3620
3621
3622int main(int argc, char **argv)
3623{
3624 PKOBJCACHE pCache;
3625 PKOCENTRY pEntry;
3626
3627 const char *pszCacheDir = getenv("KOBJCACHE_DIR");
3628 const char *pszCacheName = NULL;
3629 const char *pszCacheFile = NULL;
3630 const char *pszEntryFile = NULL;
3631
3632 const char **papszArgvPreComp = NULL;
3633 unsigned cArgvPreComp = 0;
3634 const char *pszPreCompName = NULL;
3635 int fRedirPreCompStdOut = 0;
3636
3637 const char **papszArgvCompile = NULL;
3638 unsigned cArgvCompile = 0;
3639 const char *pszObjName = NULL;
3640 int fRedirCompileStdIn = 0;
3641
3642 const char *pszTarget = NULL;
3643
3644 enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
3645
3646 size_t cch;
3647 char *psz;
3648 int i;
3649
3650 SetErrorPrefix("kObjCache");
3651
3652 /*
3653 * Arguments passed in the environmnet?
3654 */
3655 psz = getenv("KOBJCACHE_OPTS");
3656 if (psz)
3657 AppendArgs(&argc, &argv, psz, "--kObjCache-options");
3658
3659 /*
3660 * Parse the arguments.
3661 */
3662 if (argc <= 1)
3663 return usage();
3664 for (i = 1; i < argc; i++)
3665 {
3666 if (!strcmp(argv[i], "--kObjCache-cpp"))
3667 {
3668 enmMode = kOC_CppArgv;
3669 if (!pszPreCompName)
3670 {
3671 if (++i >= argc)
3672 return SyntaxError("--kObjCache-cpp requires an object filename!\n");
3673 pszPreCompName = argv[i];
3674 }
3675 }
3676 else if (!strcmp(argv[i], "--kObjCache-cc"))
3677 {
3678 enmMode = kOC_CcArgv;
3679 if (!pszObjName)
3680 {
3681 if (++i >= argc)
3682 return SyntaxError("--kObjCache-cc requires an precompiler output filename!\n");
3683 pszObjName = argv[i];
3684 }
3685 }
3686 else if (!strcmp(argv[i], "--kObjCache-both"))
3687 enmMode = kOC_BothArgv;
3688 else if (!strcmp(argv[i], "--kObjCache-options"))
3689 enmMode = kOC_Options;
3690 else if (!strcmp(argv[i], "--help"))
3691 return usage();
3692 else if (enmMode != kOC_Options)
3693 {
3694 if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv)
3695 {
3696 if (!(cArgvPreComp % 16))
3697 papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0]));
3698 papszArgvPreComp[cArgvPreComp++] = argv[i];
3699 papszArgvPreComp[cArgvPreComp] = NULL;
3700 }
3701 if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv)
3702 {
3703 if (!(cArgvCompile % 16))
3704 papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0]));
3705 papszArgvCompile[cArgvCompile++] = argv[i];
3706 papszArgvCompile[cArgvCompile] = NULL;
3707 }
3708 }
3709 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--entry-file"))
3710 {
3711 if (i + 1 >= argc)
3712 return SyntaxError("%s requires a cache entry filename!\n", argv[i]);
3713 pszEntryFile = argv[++i];
3714 }
3715 else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache-file"))
3716 {
3717 if (i + 1 >= argc)
3718 return SyntaxError("%s requires a cache filename!\n", argv[i]);
3719 pszCacheFile = argv[++i];
3720 }
3721 else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--name"))
3722 {
3723 if (i + 1 >= argc)
3724 return SyntaxError("%s requires a cache name!\n", argv[i]);
3725 pszCacheName = argv[++i];
3726 }
3727 else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--cache-dir"))
3728 {
3729 if (i + 1 >= argc)
3730 return SyntaxError("%s requires a cache directory!\n", argv[i]);
3731 pszCacheDir = argv[++i];
3732 }
3733 else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--target"))
3734 {
3735 if (i + 1 >= argc)
3736 return SyntaxError("%s requires a target platform/arch name!\n", argv[i]);
3737 pszTarget = argv[++i];
3738 }
3739 else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--passthru"))
3740 fRedirPreCompStdOut = fRedirCompileStdIn = 1;
3741 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
3742 fRedirPreCompStdOut = 1;
3743 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
3744 g_cVerbosityLevel++;
3745 else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
3746 g_cVerbosityLevel = 0;
3747 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?")
3748 || !strcmp(argv[i], "/h") || !strcmp(argv[i], "/?") || !strcmp(argv[i], "/help"))
3749 return usage();
3750 else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version"))
3751 {
3752 printf("kObjCache v0.1.0 ($Revision: 1087 $)\n");
3753 return 0;
3754 }
3755 else
3756 return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
3757 }
3758 if (!pszEntryFile)
3759 return SyntaxError("No cache entry filename (-f)!\n");
3760 if (!pszTarget)
3761 return SyntaxError("No target name (-t)!\n");
3762 if (!cArgvCompile)
3763 return SyntaxError("No compiler arguments (--kObjCache-cc)!\n");
3764 if (!cArgvPreComp)
3765 return SyntaxError("No precompiler arguments (--kObjCache-cc)!\n");
3766
3767 /*
3768 * Calc the cache file name.
3769 * It's a bit messy since the extension has to be replaced.
3770 */
3771 if (!pszCacheFile)
3772 {
3773 if (!pszCacheDir)
3774 return SyntaxError("No cache dir (-d / KOBJCACHE_DIR) and no cache filename!\n");
3775 if (!pszCacheName)
3776 {
3777 psz = (char *)FindFilenameInPath(pszEntryFile);
3778 if (!*psz)
3779 return SyntaxError("The cache file (-f) specifies a directory / nothing!\n");
3780 cch = psz - pszEntryFile;
3781 pszCacheName = memcpy(xmalloc(cch + 5), psz, cch + 1);
3782 psz = strrchr(pszCacheName, '.');
3783 if (!psz || psz <= pszCacheName)
3784 psz = (char *)pszCacheName + cch;
3785 memcpy(psz, ".koc", sizeof(".koc") - 1);
3786 }
3787 pszCacheFile = MakePathFromDirAndFile(pszCacheName, pszCacheDir);
3788 }
3789
3790 /*
3791 * Create and initialize the two objects we'll be working on.
3792 *
3793 * We're supposed to be the only ones actually writing to the local file,
3794 * so it's perfectly fine to read it here before we lock it. This simplifies
3795 * the detection of object name and compiler argument changes.
3796 */
3797 SetErrorPrefix("kObjCache - %s", FindFilenameInPath(pszCacheFile));
3798 pCache = kObjCacheCreate(pszCacheFile);
3799
3800 pEntry = kOCEntryCreate(pszEntryFile);
3801 kOCEntryRead(pEntry);
3802 kOCEntrySetCompileObjName(pEntry, pszObjName);
3803 kOCEntrySetCompileArgv(pEntry, papszArgvCompile, cArgvCompile);
3804 kOCEntrySetTarget(pEntry, pszTarget);
3805 kOCEntrySetCppName(pEntry, pszPreCompName);
3806 kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn);
3807
3808 /*
3809 * Open (& lock) the two files and do validity checks and such.
3810 */
3811 kObjCacheLock(pCache);
3812 if ( kObjCacheIsNew(pCache)
3813 && kOCEntryNeedsCompiling(pEntry))
3814 {
3815 /*
3816 * Both files are missing/invalid.
3817 * Optimize this path as it is frequently used when making a clean build.
3818 */
3819 kObjCacheUnlock(pCache);
3820 InfoMsg(1, "doing full compile\n");
3821 kOCEntryPreCompileAndCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3822 kObjCacheLock(pCache);
3823 }
3824 else
3825 {
3826 /*
3827 * Do the precompile (don't need to lock the cache file for this).
3828 */
3829 kObjCacheUnlock(pCache);
3830 kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
3831
3832 /*
3833 * Check if we need to recompile. If we do, try see if the is a cache entry first.
3834 */
3835 kOCEntryCalcRecompile(pEntry);
3836 if (kOCEntryNeedsCompiling(pEntry))
3837 {
3838 PKOCENTRY pUseEntry;
3839 kObjCacheLock(pCache);
3840 kObjCacheRemoveEntry(pCache, pEntry);
3841 pUseEntry = kObjCacheFindMatchingEntry(pCache, pEntry);
3842 if (pUseEntry)
3843 {
3844 InfoMsg(1, "using cache entry '%s'\n", kOCEntryAbsPath(pUseEntry));
3845 kOCEntryCopy(pEntry, pUseEntry);
3846 kOCEntryDestroy(pUseEntry);
3847 }
3848 else
3849 {
3850 kObjCacheUnlock(pCache);
3851 InfoMsg(1, "recompiling\n");
3852 kOCEntryCompileIt(pEntry);
3853 kObjCacheLock(pCache);
3854 }
3855 }
3856 else
3857 {
3858 InfoMsg(1, "no need to recompile\n");
3859 kObjCacheLock(pCache);
3860 }
3861 }
3862
3863 /*
3864 * Update the cache files.
3865 */
3866 kObjCacheRemoveEntry(pCache, pEntry);
3867 kObjCacheInsertEntry(pCache, pEntry);
3868 kOCEntryWrite(pEntry);
3869 kObjCacheUnlock(pCache);
3870 kObjCacheDestroy(pCache);
3871 return 0;
3872}
3873
3874
3875/** @page kObjCache Benchmarks.
3876 *
3877 * (2007-06-10)
3878 *
3879 * 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):
3880 * real 11m28.811s
3881 * user 13m59.291s
3882 * sys 3m24.590s
3883 *
3884 * 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):
3885 * real 1m26.895s
3886 * user 1m26.971s
3887 * sys 0m32.532s
3888 *
3889 * 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):
3890 * real 1m18.049s
3891 * user 1m20.462s
3892 * sys 0m27.887s
3893 *
3894 * 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):
3895 * real 13m27.751s
3896 * user 18m12.654s
3897 * sys 3m25.170s
3898 *
3899 * 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):
3900 * real 9m9.720s
3901 * user 8m53.005s
3902 * sys 2m13.110s
3903 *
3904 * 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):
3905 * real 10m18.129s
3906 * user 12m52.687s
3907 * sys 2m51.277s
3908 *
3909 * 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):
3910 * real 4m46.147s
3911 * user 5m27.087s
3912 * sys 1m11.775s
3913 *
3914 * 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):
3915 * real 4m17.572s
3916 * user 5m7.450s
3917 * sys 1m3.450s
3918 *
3919 * 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):
3920 * real 12m14.742s
3921 * user 17m11.794s
3922 * sys 2m51.454s
3923 *
3924 * 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):
3925 * real 12m33.821s
3926 * user 17m35.086s
3927 * sys 2m53.312s
3928 *
3929 * Note. The profile build can pick object files from the release build.
3930 * (all with KOBJCACHE_OPTS=-v; which means a bit more output and perhaps a second or two slower.)
3931 */
3932
Note: See TracBrowser for help on using the repository browser.

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