VirtualBox

source: kBuild/trunk/src/lib/kDep.c@ 3381

Last change on this file since 3381 was 3315, checked in by bird, 5 years ago

lib/kDep+users: Escape spaces and such in dependency files (experimental).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.9 KB
Line 
1/* $Id: kDep.c 3315 2020-03-31 01:12:19Z bird $ */
2/** @file
3 * kDep - Common Dependency Managemnt Code.
4 */
5
6/*
7 * Copyright (c) 2004-2013 knut st. osmundsen <[email protected]>
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a
10 * copy of this software and associated documentation files (the "Software"),
11 * to deal in the Software without restriction, including without limitation
12 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 * and/or sell copies of the Software, and to permit persons to whom the
14 * Software is furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
25 * IN THE SOFTWARE.
26 *
27 * Alternatively, the content of this file may be used under the terms of the
28 * GPL version 2 or later, or LGPL version 2.1 or later.
29 */
30
31
32/*******************************************************************************
33* Header Files *
34*******************************************************************************/
35#ifdef KMK /* For when it gets compiled and linked into kmk. */
36# include "makeint.h"
37#endif
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <errno.h>
42#include <ctype.h>
43#include <limits.h>
44#include <sys/stat.h>
45#include "k/kDefs.h"
46#include "k/kTypes.h"
47#if K_OS == K_OS_WINDOWS
48# define USE_WIN_MMAP
49# include <io.h>
50# include <Windows.h>
51# include "nt_fullpath.h"
52# include "nt/ntstat.h"
53#else
54# include <dirent.h>
55# include <unistd.h>
56# include <stdint.h>
57#endif
58
59#include "kDep.h"
60
61#ifdef KWORKER
62extern int kwFsPathExists(const char *pszPath);
63#endif
64
65
66/*********************************************************************************************************************************
67* Defined Constants And Macros *
68*********************************************************************************************************************************/
69/* For the GNU/hurd weirdo. */
70#if !defined(PATH_MAX) && !defined(_MAX_PATH)
71# define PATH_MAX 4096
72#endif
73
74
75/**
76 * Initializes the dep instance.
77 *
78 * @param pThis The dep instance to init.
79 */
80void depInit(PDEPGLOBALS pThis)
81{
82 pThis->pDeps = NULL;
83}
84
85
86/**
87 * Cleans up the dep instance (frees resources).
88 *
89 * @param pThis The dep instance to cleanup.
90 */
91void depCleanup(PDEPGLOBALS pThis)
92{
93 PDEP pDep = pThis->pDeps;
94 pThis->pDeps = NULL;
95 while (pDep)
96 {
97 PDEP pFree = pDep;
98 pDep = pDep->pNext;
99 free(pFree);
100 }
101}
102
103
104/**
105 * Corrects all slashes to unix slashes.
106 *
107 * @returns pszFilename.
108 * @param pszFilename The filename to correct.
109 */
110static char *fixslash(char *pszFilename)
111{
112 char *psz = pszFilename;
113 while ((psz = strchr(psz, '\\')) != NULL)
114 *psz++ = '/';
115 return pszFilename;
116}
117
118
119#if K_OS == K_OS_OS2
120
121/**
122 * Corrects the case of a path.
123 *
124 * @param pszPath Pointer to the path, both input and output.
125 * The buffer must be able to hold one more byte than the string length.
126 */
127static void fixcase(char *pszFilename)
128{
129 return;
130}
131
132#elif K_OS != K_OS_WINDOWS
133
134/**
135 * Corrects the case of a path.
136 *
137 * @param pszPath Pointer to the path, both input and output.
138 */
139static void fixcase(char *pszFilename)
140{
141 char *psz;
142
143 /*
144 * Skip the root.
145 */
146 psz = pszFilename;
147 while (*psz == '/')
148 psz++;
149
150 /*
151 * Iterate all the components.
152 */
153 while (*psz)
154 {
155 char chSlash;
156 struct stat s;
157 char *pszStart = psz;
158
159 /*
160 * Find the next slash (or end of string) and terminate the string there.
161 */
162 while (*psz != '/' && *psz)
163 psz++;
164 chSlash = *psz;
165 *psz = '\0';
166
167 /*
168 * Does this part exist?
169 * If not we'll enumerate the directory and search for an case-insensitive match.
170 */
171 if (stat(pszFilename, &s))
172 {
173 struct dirent *pEntry;
174 DIR *pDir;
175 if (pszStart == pszFilename)
176 pDir = opendir(*pszFilename ? pszFilename : ".");
177 else
178 {
179 pszStart[-1] = '\0';
180 pDir = opendir(pszFilename);
181 pszStart[-1] = '/';
182 }
183 if (!pDir)
184 {
185 *psz = chSlash;
186 break; /* giving up, if we fail to open the directory. */
187 }
188
189 while ((pEntry = readdir(pDir)) != NULL)
190 {
191 if (!strcasecmp(pEntry->d_name, pszStart))
192 {
193 strcpy(pszStart, pEntry->d_name);
194 break;
195 }
196 }
197 closedir(pDir);
198 if (!pEntry)
199 {
200 *psz = chSlash;
201 break; /* giving up if not found. */
202 }
203 }
204
205 /* restore the slash and press on. */
206 *psz = chSlash;
207 while (*psz == '/')
208 psz++;
209 }
210
211 return;
212}
213
214#endif /* !OS/2 && !Windows */
215
216
217/**
218 * 'Optimizes' and corrects the dependencies.
219 */
220void depOptimize(PDEPGLOBALS pThis, int fFixCase, int fQuiet, const char *pszIgnoredExt)
221{
222 /*
223 * Walk the list correct the names and re-insert them.
224 */
225 size_t cchIgnoredExt = pszIgnoredExt ? strlen(pszIgnoredExt) : 0;
226 PDEP pDepOrg = pThis->pDeps;
227 PDEP pDep = pThis->pDeps;
228 pThis->pDeps = NULL;
229 for (; pDep; pDep = pDep->pNext)
230 {
231#ifndef PATH_MAX
232 char szFilename[_MAX_PATH + 1];
233#else
234 char szFilename[PATH_MAX + 1];
235#endif
236 char *pszFilename;
237#if !defined(KWORKER) && !defined(KMK)
238 struct stat s;
239#endif
240
241 /*
242 * Skip some fictive names like <built-in> and <command line>.
243 */
244 if ( pDep->szFilename[0] == '<'
245 && pDep->szFilename[pDep->cchFilename - 1] == '>')
246 continue;
247 pszFilename = pDep->szFilename;
248
249 /*
250 * Skip pszIgnoredExt if given.
251 */
252 if ( pszIgnoredExt
253 && pDep->cchFilename > cchIgnoredExt
254 && memcmp(&pDep->szFilename[pDep->cchFilename - cchIgnoredExt], pszIgnoredExt, cchIgnoredExt) == 0)
255 continue;
256
257#if K_OS != K_OS_OS2 && K_OS != K_OS_WINDOWS
258 /*
259 * Skip any drive letters from compilers running in wine.
260 */
261 if (pszFilename[1] == ':')
262 pszFilename += 2;
263#endif
264
265 /*
266 * The microsoft compilers are notoriously screwing up the casing.
267 * This will screw up kmk (/ GNU Make).
268 */
269 if (fFixCase)
270 {
271#if K_OS == K_OS_WINDOWS
272 nt_fullpath_cached(pszFilename, szFilename, sizeof(szFilename));
273 fixslash(szFilename);
274#else
275 strcpy(szFilename, pszFilename);
276 fixslash(szFilename);
277 fixcase(szFilename);
278#endif
279 pszFilename = szFilename;
280 }
281
282 /*
283 * Check that the file exists before we start depending on it.
284 */
285 errno = 0;
286#ifdef KWORKER
287 if (!kwFsPathExists(pszFilename))
288#elif defined(KMK)
289 if (!file_exists_p(pszFilename))
290#elif K_OS == K_OS_WINDOWS
291 if (birdStatModTimeOnly(pszFilename, &s.st_mtim, 1 /*fFollowLink*/) != 0)
292#else
293 if (stat(pszFilename, &s) != 0)
294#endif
295 {
296 if ( !fQuiet
297 || errno != ENOENT
298 || ( pszFilename[0] != '/'
299 && pszFilename[0] != '\\'
300 && ( !isalpha(pszFilename[0])
301 || pszFilename[1] != ':'
302 || ( pszFilename[2] != '/'
303 && pszFilename[2] != '\\')))
304 )
305 fprintf(stderr, "kDep: Skipping '%s' - %s!\n", pszFilename, strerror(errno));
306 continue;
307 }
308
309 /*
310 * Insert the corrected dependency.
311 */
312 depAdd(pThis, pszFilename, strlen(pszFilename));
313 }
314
315 /*
316 * Free the old ones.
317 */
318 while (pDepOrg)
319 {
320 pDep = pDepOrg;
321 pDepOrg = pDepOrg->pNext;
322 free(pDep);
323 }
324}
325
326
327/**
328 * Write a filename that contains characters that needs escaping.
329 *
330 * @param pOutput The output stream.
331 * @param pszFile The filename.
332 * @param cchFile The length of the filename.
333 * @param fDep Whether this is for a dependency file or a target file.
334 */
335int depNeedsEscaping(const char *pszFile, size_t cchFile, int fDependency)
336{
337 return memchr(pszFile, ' ', cchFile) != NULL
338 || memchr(pszFile, '\t', cchFile) != NULL
339 || memchr(pszFile, '#', cchFile) != NULL
340 || memchr(pszFile, '=', cchFile) != NULL
341 || memchr(pszFile, ';', cchFile) != NULL
342 || memchr(pszFile, '$', cchFile) != NULL
343 || memchr(pszFile, fDependency ? '|' : '%', cchFile) != NULL;
344}
345
346
347/**
348 * Write a filename that contains characters that needs escaping.
349 *
350 * @param pOutput The output stream.
351 * @param pszFile The filename.
352 * @param cchFile The length of the filename.
353 * @param fDep Whether this is for a dependency file or a target file.
354 */
355void depEscapedWrite(FILE *pOutput, const char *pszFile, size_t cchFile, int fDepenency)
356{
357 size_t cchWritten = 0;
358 size_t off = 0;
359 while (off < cchFile)
360 {
361 char const ch = pszFile[off];
362 switch (ch)
363 {
364 default:
365 off++;
366 break;
367
368 /*
369 * Escaped by slash, but any preceeding slashes must be escaped too.
370 * A couple of characters are only escaped on one side of the ':'.
371 */
372 case '%': /* target side only */
373 case '|': /* dependency side only */
374 if (ch != (fDepenency ? '|' : '%'))
375 {
376 off++;
377 break;
378 }
379 /* fall thru */
380 case ' ':
381 case '\t':
382 case '#':
383 case '=': /** @todo buggy GNU make handling */
384 case ';': /** @todo buggy GNU make handling */
385 if (cchWritten < off)
386 fwrite(&pszFile[cchWritten], off - cchWritten, 1, pOutput);
387 if (off == 0 || pszFile[off - 1] != '\\')
388 {
389 fputc('\\', pOutput);
390 cchWritten = off; /* We write the escaped character with the next bunch. */
391 }
392 else
393 {
394 size_t cchSlashes = 1;
395 while (cchSlashes < off && pszFile[off - cchSlashes - 1] == '\\')
396 cchSlashes++;
397 fwrite(&pszFile[off - cchSlashes], cchSlashes, 1, pOutput);
398 cchWritten = off - 1; /* Write a preceeding slash and the escaped character with the next bunch. */
399 }
400 off += 1;
401 break;
402
403 /*
404 * Escaped by doubling it.
405 * Implemented by including in the pending writeout job as well as in the next one.
406 */
407 case '$':
408 fwrite(&pszFile[cchWritten], off - cchWritten + 1, 1, pOutput);
409 cchWritten = off++; /* write it again the next time */
410 break;
411 }
412 }
413
414 /* Remainder: */
415 if (cchWritten < cchFile)
416 fwrite(&pszFile[cchWritten], cchFile - cchWritten, 1, pOutput);
417}
418
419
420/**
421 * Escapes all trailing trailing slashes in a filename that ends with such.
422 */
423static void depPrintTrailngSlashEscape(FILE *pOutput, const char *pszFilename, size_t cchFilename)
424{
425 size_t cchSlashes = 1;
426 while (cchSlashes < cchFilename && pszFilename[cchFilename - cchSlashes - 1] == '\\')
427 cchSlashes++;
428 fwrite(&pszFilename[cchFilename - cchSlashes], cchSlashes, 1, pOutput);
429}
430
431
432/**
433 * Prints the dependency chain.
434 *
435 * @param pThis The 'dep' instance.
436 * @param pOutput Output stream.
437 */
438void depPrintChain(PDEPGLOBALS pThis, FILE *pOutput)
439{
440 static char const g_szEntryText[] = " \\\n\t";
441 static char const g_szTailText[] = "\n\n";
442 static char const g_szTailSlashText[] = " \\\n\n";
443 PDEP pDep;
444 for (pDep = pThis->pDeps; pDep; pDep = pDep->pNext)
445 {
446 fwrite(g_szEntryText, sizeof(g_szEntryText) - 1, 1, pOutput);
447 if (!pDep->fNeedsEscaping)
448 fwrite(pDep->szFilename, pDep->cchFilename, 1, pOutput);
449 else
450 depEscapedWrite(pOutput, pDep->szFilename, pDep->cchFilename, 1 /*fDependency*/);
451 if (pDep->fTrailingSlash)
452 { /* Escape only if more dependencies. If last, we must add a line continuation or it won't work. */
453 if (pDep->pNext)
454 depPrintTrailngSlashEscape(pOutput, pDep->szFilename, pDep->cchFilename);
455 else
456 {
457 fwrite(g_szTailSlashText, sizeof(g_szTailSlashText), 1, pOutput);
458 return;
459 }
460 }
461 }
462
463 fwrite(g_szTailText, sizeof(g_szTailText) - 1, 1, pOutput);
464}
465
466
467/**
468 * Prints the dependency chain with a preceeding target.
469 *
470 * @param pThis The 'dep' instance.
471 * @param pOutput Output stream.
472 * @param pszTarget The target filename.
473 * @param fEscapeTarget Whether to consider escaping the target.
474 */
475void depPrintTargetWithDeps(PDEPGLOBALS pThis, FILE *pOutput, const char *pszTarget, int fEscapeTarget)
476{
477 static char const g_szSeparator[] = ":";
478 size_t const cchTarget = strlen(pszTarget);
479 if (!fEscapeTarget || !depNeedsEscaping(pszTarget, cchTarget, 0 /*fDependency*/))
480 fwrite(pszTarget, cchTarget, 1, pOutput);
481 else
482 depEscapedWrite(pOutput, pszTarget, cchTarget, 0 /*fDependency*/);
483
484 if (cchTarget == 0 || pszTarget[cchTarget - 1] != '\\')
485 { /* likely */ }
486 else
487 depPrintTrailngSlashEscape(pOutput, pszTarget, cchTarget);
488 fwrite(g_szSeparator, sizeof(g_szSeparator) - 1, 1, pOutput);
489
490 depPrintChain(pThis, pOutput);
491}
492
493
494/**
495 * Prints empty dependency stubs for all dependencies.
496 *
497 * @param pThis The 'dep' instance.
498 * @param pOutput Output stream.
499 */
500void depPrintStubs(PDEPGLOBALS pThis, FILE *pOutput)
501{
502 static char g_szTailText[] = ":\n\n";
503 PDEP pDep;
504 for (pDep = pThis->pDeps; pDep; pDep = pDep->pNext)
505 {
506 if (!pDep->fNeedsEscaping && memchr(pDep->szFilename, '%', pDep->cchFilename) == 0)
507 fwrite(pDep->szFilename, pDep->cchFilename, 1, pOutput);
508 else
509 depEscapedWrite(pOutput, pDep->szFilename, pDep->cchFilename, 0 /*fDependency*/);
510
511 if (pDep->cchFilename == 0 || !pDep->fTrailingSlash)
512 { /* likely */ }
513 else
514 depPrintTrailngSlashEscape(pOutput, pDep->szFilename, pDep->cchFilename);
515 fwrite(g_szTailText, sizeof(g_szTailText) - 1, 1, pOutput);
516 }
517}
518
519
520/* sdbm:
521 This algorithm was created for sdbm (a public-domain reimplementation of
522 ndbm) database library. it was found to do well in scrambling bits,
523 causing better distribution of the keys and fewer splits. it also happens
524 to be a good general hashing function with good distribution. the actual
525 function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below
526 is the faster version used in gawk. [there is even a faster, duff-device
527 version] the magic constant 65599 was picked out of thin air while
528 experimenting with different constants, and turns out to be a prime.
529 this is one of the algorithms used in berkeley db (see sleepycat) and
530 elsewhere. */
531static unsigned sdbm(const char *str, size_t size)
532{
533 unsigned hash = 0;
534 int c;
535
536 while (size-- > 0 && (c = *(unsigned const char *)str++))
537 hash = c + (hash << 6) + (hash << 16) - hash;
538
539 return hash;
540}
541
542
543/**
544 * Adds a dependency.
545 *
546 * @returns Pointer to the allocated dependency.
547 * @param pThis The 'dep' instance.
548 * @param pszFilename The filename. Does not need to be terminated.
549 * @param cchFilename The length of the filename.
550 */
551PDEP depAdd(PDEPGLOBALS pThis, const char *pszFilename, size_t cchFilename)
552{
553 unsigned uHash = sdbm(pszFilename, cchFilename);
554 PDEP pDep;
555 PDEP pDepPrev;
556
557 /*
558 * Check if we've already got this one.
559 */
560 pDepPrev = NULL;
561 for (pDep = pThis->pDeps; pDep; pDepPrev = pDep, pDep = pDep->pNext)
562 if ( pDep->uHash == uHash
563 && pDep->cchFilename == cchFilename
564 && !memcmp(pDep->szFilename, pszFilename, cchFilename))
565 return pDep;
566
567 /*
568 * Add it.
569 */
570 pDep = (PDEP)malloc(sizeof(*pDep) + cchFilename);
571 if (!pDep)
572 {
573 fprintf(stderr, "\nOut of memory! (requested %lx bytes)\n\n",
574 (unsigned long)(sizeof(*pDep) + cchFilename));
575 exit(1);
576 }
577
578 pDep->cchFilename = cchFilename;
579 memcpy(pDep->szFilename, pszFilename, cchFilename);
580 pDep->szFilename[cchFilename] = '\0';
581 pDep->fNeedsEscaping = depNeedsEscaping(pszFilename, cchFilename, 1 /*fDependency*/);
582 pDep->fTrailingSlash = cchFilename > 0 && pszFilename[cchFilename - 1] == '\\';
583 pDep->uHash = uHash;
584
585 if (pDepPrev)
586 {
587 pDep->pNext = pDepPrev->pNext;
588 pDepPrev->pNext = pDep;
589 }
590 else
591 {
592 pDep->pNext = pThis->pDeps;
593 pThis->pDeps = pDep;
594 }
595 return pDep;
596}
597
598
599/**
600 * Performs a hexdump.
601 */
602void depHexDump(const KU8 *pb, size_t cb, size_t offBase)
603{
604 const unsigned cchWidth = 16;
605 size_t off = 0;
606 while (off < cb)
607 {
608 unsigned i;
609 printf("%s%0*lx %04lx:", off ? "\n" : "", (int)sizeof(pb) * 2,
610 (unsigned long)offBase + (unsigned long)off, (unsigned long)off);
611 for (i = 0; i < cchWidth && off + i < cb ; i++)
612 printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]);
613
614 while (i++ < cchWidth)
615 printf(" ");
616 printf(" ");
617
618 for (i = 0; i < cchWidth && off + i < cb; i++)
619 {
620 const KU8 u8 = pb[i];
621 printf("%c", u8 < 127 && u8 >= 32 ? u8 : '.');
622 }
623 off += cchWidth;
624 pb += cchWidth;
625 }
626 printf("\n");
627}
628
629
630/**
631 * Reads the file specified by the pInput file stream into memory.
632 *
633 * @returns The address of the memory mapping on success. This must be
634 * freed by calling depFreeFileMemory.
635 *
636 * @param pInput The file stream to load or map into memory.
637 * @param pcbFile Where to return the mapping (file) size.
638 * @param ppvOpaque Opaque data when mapping, otherwise NULL.
639 */
640void *depReadFileIntoMemory(FILE *pInput, size_t *pcbFile, void **ppvOpaque)
641{
642 void *pvFile;
643 long cbFile;
644
645 /*
646 * Figure out file size.
647 */
648#if defined(_MSC_VER)
649 cbFile = _filelength(fileno(pInput));
650 if (cbFile < 0)
651#else
652 if ( fseek(pInput, 0, SEEK_END) < 0
653 || (cbFile = ftell(pInput)) < 0
654 || fseek(pInput, 0, SEEK_SET))
655#endif
656 {
657 fprintf(stderr, "kDep: error: Failed to determin file size.\n");
658 return NULL;
659 }
660 if (pcbFile)
661 *pcbFile = cbFile;
662
663 /*
664 * Try mmap first.
665 */
666#ifdef USE_WIN_MMAP
667 {
668 HANDLE hMapObj = CreateFileMapping((HANDLE)_get_osfhandle(fileno(pInput)),
669 NULL, PAGE_READONLY, 0, cbFile, NULL);
670 if (hMapObj != NULL)
671 {
672 pvFile = MapViewOfFile(hMapObj, FILE_MAP_READ, 0, 0, cbFile);
673 if (pvFile)
674 {
675 *ppvOpaque = hMapObj;
676 return pvFile;
677 }
678 fprintf(stderr, "kDep: warning: MapViewOfFile failed, %d.\n", GetLastError());
679 CloseHandle(hMapObj);
680 }
681 else
682 fprintf(stderr, "kDep: warning: CreateFileMapping failed, %d.\n", GetLastError());
683 }
684
685#endif
686
687 /*
688 * Allocate memory and read the file.
689 */
690 pvFile = malloc(cbFile + 1);
691 if (pvFile)
692 {
693 if (fread(pvFile, cbFile, 1, pInput))
694 {
695 ((KU8 *)pvFile)[cbFile] = '\0';
696 *ppvOpaque = NULL;
697 return pvFile;
698 }
699 fprintf(stderr, "kDep: error: Failed to read %ld bytes.\n", cbFile);
700 free(pvFile);
701 }
702 else
703 fprintf(stderr, "kDep: error: Failed to allocate %ld bytes (file mapping).\n", cbFile);
704 return NULL;
705}
706
707
708/**
709 * Free resources allocated by depReadFileIntoMemory.
710 *
711 * @param pvFile The address of the memory mapping.
712 * @param pvOpaque The opaque value returned together with the mapping.
713 */
714void depFreeFileMemory(void *pvFile, void *pvOpaque)
715{
716#if defined(USE_WIN_MMAP)
717 if (pvOpaque)
718 {
719 UnmapViewOfFile(pvFile);
720 CloseHandle(pvOpaque);
721 return;
722 }
723#endif
724 free(pvFile);
725}
726
Note: See TracBrowser for help on using the repository browser.

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