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