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

Last change on this file since 2553 was 2546, checked in by bird, 14 years ago

Applied modified patches for Haiku support from Mike Smith.

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