VirtualBox

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

Last change on this file since 1093 was 1093, checked in by bird, 18 years ago

Fixed some OS/2 issues.

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

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