VirtualBox

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

Last change on this file since 1885 was 1732, checked in by bird, 16 years ago

kObjCache: shut up pedantic gcc warnings.

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

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