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

Last change on this file since 2591 was 2568, checked in by bird, 13 years ago

kObjCache: mkdir solaris crap (ENOSYS + EACCES).

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