VirtualBox

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

Last change on this file since 2125 was 2019, checked in by bird, 16 years ago

GPLv2 -> GPLv3. See Ticket #44 for clarifications. Fixes #44.

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