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

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

kObjCache: Early commit of the C preprocessor output optimizer.

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