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

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

kObjCache: Implemented support for generating makefile dependencies using the #line directives inserted by the preprocessor.

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