diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
commit | 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch) | |
tree | 63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kmk/incdep.c | |
parent | Initial commit. (diff) | |
download | kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip |
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/kmk/incdep.c')
-rw-r--r-- | src/kmk/incdep.c | 2250 |
1 files changed, 2250 insertions, 0 deletions
diff --git a/src/kmk/incdep.c b/src/kmk/incdep.c new file mode 100644 index 0000000..09de6ff --- /dev/null +++ b/src/kmk/incdep.c @@ -0,0 +1,2250 @@ +#ifdef CONFIG_WITH_INCLUDEDEP +/* $Id: incdep.c 3565 2022-05-24 20:40:24Z bird $ */ +/** @file + * incdep - Simple dependency files. + */ + +/* + * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#ifdef __OS2__ +# define INCL_BASE +# define INCL_ERRORS +#endif +#ifdef KBUILD_OS_WINDOWS +# ifdef KMK +# define INCDEP_USE_KFSCACHE +# endif +#endif + +#include "makeint.h" + +#if !defined(WINDOWS32) && !defined(__OS2__) +# define HAVE_PTHREAD +#endif + +#include <assert.h> + +#include <glob.h> + +#include "filedef.h" +#include "dep.h" +#include "job.h" +#include "commands.h" +#include "variable.h" +#include "rule.h" +#include "debug.h" +#include "strcache2.h" + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#else +# include <sys/file.h> +#endif + +#ifdef WINDOWS32 +# include <io.h> +# include <process.h> +# include <Windows.h> +# define PARSE_IN_WORKER +#endif + +#ifdef INCDEP_USE_KFSCACHE +# include "nt/kFsCache.h" +extern PKFSCACHE g_pFsCache; /* dir-nt-bird.c for now */ +#endif + +#ifdef __OS2__ +# include <os2.h> +# include <sys/fmutex.h> +#endif + +#ifdef HAVE_PTHREAD +# include <pthread.h> +#endif + +#ifdef __APPLE__ +# include <malloc/malloc.h> +# define PARSE_IN_WORKER +#endif + +#if defined(__gnu_linux__) || defined(__linux__) +# define PARSE_IN_WORKER +#endif + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +struct incdep_variable_in_set +{ + struct incdep_variable_in_set *next; + /* the parameters */ + struct strcache2_entry *name_entry; /* dep strcache - WRONG */ + const char *value; /* xmalloc'ed */ + unsigned int value_length; + int duplicate_value; /* 0 */ + enum variable_origin origin; + int recursive; + struct variable_set *set; + const floc *flocp; /* NILF */ +}; + +struct incdep_variable_def +{ + struct incdep_variable_def *next; + /* the parameters */ + const floc *flocp; /* NILF */ + struct strcache2_entry *name_entry; /* dep strcache - WRONG */ + char *value; /* xmalloc'ed, free it */ + unsigned int value_length; + enum variable_origin origin; + enum variable_flavor flavor; + int target_var; +}; + +struct incdep_recorded_file +{ + struct incdep_recorded_file *next; + + /* the parameters */ + struct strcache2_entry *filename_entry; /* dep strcache; converted to a nameseq record. */ + struct dep *deps; /* All the names are dep strcache entries. */ + const floc *flocp; /* NILF */ +}; + + +/* per dep file structure. */ +struct incdep +{ + struct incdep *next; + char *file_base; + char *file_end; + + int worker_tid; +#ifdef PARSE_IN_WORKER + unsigned int err_line_no; + const char *err_msg; + + struct incdep_variable_in_set *recorded_variables_in_set_head; + struct incdep_variable_in_set *recorded_variables_in_set_tail; + + struct incdep_variable_def *recorded_variable_defs_head; + struct incdep_variable_def *recorded_variable_defs_tail; + + struct incdep_recorded_file *recorded_file_head; + struct incdep_recorded_file *recorded_file_tail; +#endif +#ifdef INCDEP_USE_KFSCACHE + /** Pointer to the fs cache object for this file (it exists and is a file). */ + PKFSOBJ pFileObj; +#else + char name[1]; +#endif +}; + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ + +/* mutex protecting the globals and an associated condition/event. */ +#ifdef HAVE_PTHREAD +static pthread_mutex_t incdep_mtx; +static pthread_cond_t incdep_cond_todo; +static pthread_cond_t incdep_cond_done; + +#elif defined (WINDOWS32) +static CRITICAL_SECTION incdep_mtx; +static HANDLE incdep_hev_todo; +static HANDLE incdep_hev_done; +static int volatile incdep_hev_todo_waiters; +static int volatile incdep_hev_done_waiters; + +#elif defined (__OS2__) +static _fmutex incdep_mtx; +static HEV incdep_hev_todo; +static HEV incdep_hev_done; +static int volatile incdep_hev_todo_waiters; +static int volatile incdep_hev_done_waiters; +#endif + +/* flag indicating whether the threads, lock and event/condvars has + been initialized or not. */ +static int incdep_initialized; + +/* the list of files that needs reading. */ +static struct incdep * volatile incdep_head_todo; +static struct incdep * volatile incdep_tail_todo; + +/* the number of files that are currently being read. */ +static int volatile incdep_num_reading; + +/* the list of files that have been read. */ +static struct incdep * volatile incdep_head_done; +static struct incdep * volatile incdep_tail_done; + + +/* The handles to the worker threads. */ +#ifdef HAVE_PTHREAD +# define INCDEP_MAX_THREADS 1 +static pthread_t incdep_threads[INCDEP_MAX_THREADS]; + +#elif defined (WINDOWS32) +# define INCDEP_MAX_THREADS 2 +static HANDLE incdep_threads[INCDEP_MAX_THREADS]; + +#elif defined (__OS2__) +# define INCDEP_MAX_THREADS 2 +static TID incdep_threads[INCDEP_MAX_THREADS]; +#endif + +static struct alloccache incdep_rec_caches[INCDEP_MAX_THREADS]; +static struct alloccache incdep_dep_caches[INCDEP_MAX_THREADS]; +static struct strcache2 incdep_dep_strcaches[INCDEP_MAX_THREADS]; +static struct strcache2 incdep_var_strcaches[INCDEP_MAX_THREADS]; +static unsigned incdep_num_threads; + +/* flag indicating whether the worker threads should terminate or not. */ +static int volatile incdep_terminate; + +#ifdef __APPLE__ +/* malloc zone for the incdep threads. */ +static malloc_zone_t *incdep_zone; +#endif + + +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +static void incdep_flush_it (floc *); +static void eval_include_dep_file (struct incdep *, floc *); +static void incdep_commit_recorded_file (const char *filename, struct dep *deps, + const floc *flocp); + + +/* xmalloc wrapper. + For working around multithreaded performance problems found on Darwin, + Linux (glibc), and possibly other systems. */ +static void * +incdep_xmalloc (struct incdep *cur, size_t size) +{ + void *ptr; + +#ifdef __APPLE__ + if (cur && cur->worker_tid != -1) + { + ptr = malloc_zone_malloc (incdep_zone, size); + if (!ptr) + O (fatal, NILF, _("virtual memory exhausted")); + } + else + ptr = xmalloc (size); +#else + ptr = xmalloc (size); +#endif + + (void)cur; + return ptr; +} + +#if 0 +/* cmalloc wrapper */ +static void * +incdep_xcalloc (struct incdep *cur, size_t size) +{ + void *ptr; + +#ifdef __APPLE__ + if (cur && cur->worker_tid != -1) + ptr = malloc_zone_calloc (incdep_zone, size, 1); + else + ptr = calloc (size, 1); +#else + ptr = calloc (size, 1); +#endif + if (!ptr) + fatal (NILF, _("virtual memory exhausted")); + + (void)cur; + return ptr; +} +#endif /* unused */ + +/* free wrapper */ +static void +incdep_xfree (struct incdep *cur, void *ptr) +{ + /* free() *must* work for the allocation hacks above because + of free_dep_chain. */ + free (ptr); + (void)cur; +} + +/* alloc a dep structure. These are allocated in bunches to save time. */ +struct dep * +incdep_alloc_dep (struct incdep *cur) +{ + struct alloccache *cache; + if (cur->worker_tid != -1) + cache = &incdep_dep_caches[cur->worker_tid]; + else + cache = &dep_cache; + return alloccache_calloc (cache); +} + +/* duplicates the dependency list pointed to by srcdep. */ +static struct dep * +incdep_dup_dep_list (struct incdep *cur, struct dep const *srcdep) +{ + struct alloccache *cache; + struct dep *retdep; + struct dep *dstdep; + + if (cur->worker_tid != -1) + cache = &incdep_dep_caches[cur->worker_tid]; + else + cache = &dep_cache; + + if (srcdep) + { + retdep = dstdep = alloccache_alloc (cache); + for (;;) + { + dstdep->name = srcdep->name; /* string cached */ + dstdep->includedep = srcdep->includedep; + srcdep = srcdep->next; + if (!srcdep) + { + dstdep->next = NULL; + break; + } + dstdep->next = alloccache_alloc (cache); + dstdep = dstdep->next; + } + } + else + retdep = NULL; + return retdep; +} + + +/* allocate a record. */ +static void * +incdep_alloc_rec (struct incdep *cur) +{ + return alloccache_alloc (&incdep_rec_caches[cur->worker_tid]); +} + +/* free a record. */ +static void +incdep_free_rec (struct incdep *cur, void *rec) +{ + /*alloccache_free (&incdep_rec_caches[cur->worker_tid], rec); - doesn't work of course. */ +} + + +/* grow a cache. */ +static void * +incdep_cache_allocator (void *thrd, unsigned int size) +{ + (void)thrd; +#ifdef __APPLE__ + return malloc_zone_malloc (incdep_zone, size); +#else + return xmalloc (size); +#endif +} + +/* term a cache. */ +static void +incdep_cache_deallocator (void *thrd, void *ptr, unsigned int size) +{ + (void)thrd; + (void)size; + free (ptr); +} + +/* acquires the lock */ +void +incdep_lock(void) +{ +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + pthread_mutex_lock (&incdep_mtx); +#elif defined (WINDOWS32) + EnterCriticalSection (&incdep_mtx); +#elif defined (__OS2__) + _fmutex_request (&incdep_mtx, 0); +#endif +} + +/* releases the lock */ +void +incdep_unlock(void) +{ +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + pthread_mutex_unlock (&incdep_mtx); +#elif defined(WINDOWS32) + LeaveCriticalSection (&incdep_mtx); +#elif defined(__OS2__) + _fmutex_release (&incdep_mtx); +#endif +} + +/* signals the main thread that there is stuff todo. caller owns the lock. */ +static void +incdep_signal_done (void) +{ +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + pthread_cond_broadcast (&incdep_cond_done); +#elif defined (WINDOWS32) + if (incdep_hev_done_waiters) + SetEvent (incdep_hev_done); +#elif defined (__OS2__) + if (incdep_hev_done_waiters) + DosPostEventSem (incdep_hev_done); +#endif +} + +/* waits for a reader to finish reading. caller owns the lock. */ +static void +incdep_wait_done (void) +{ +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + pthread_cond_wait (&incdep_cond_done, &incdep_mtx); + +#elif defined (WINDOWS32) + ResetEvent (incdep_hev_done); + incdep_hev_done_waiters++; + incdep_unlock (); + WaitForSingleObject (incdep_hev_done, INFINITE); + incdep_lock (); + incdep_hev_done_waiters--; + +#elif defined (__OS2__) + ULONG ulIgnore; + DosResetEventSem (incdep_hev_done, &ulIgnore); + incdep_hev_done_waiters++; + incdep_unlock (); + DosWaitEventSem (incdep_hev_done, SEM_INDEFINITE_WAIT); + incdep_lock (); + incdep_hev_done_waiters--; +#endif +} + +/* signals the worker threads. caller owns the lock. */ +static void +incdep_signal_todo (void) +{ +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + pthread_cond_broadcast (&incdep_cond_todo); +#elif defined(WINDOWS32) + if (incdep_hev_todo_waiters) + SetEvent (incdep_hev_todo); +#elif defined(__OS2__) + if (incdep_hev_todo_waiters) + DosPostEventSem (incdep_hev_todo); +#endif +} + +/* waits for stuff to arrive in the todo list. caller owns the lock. */ +static void +incdep_wait_todo (void) +{ +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + pthread_cond_wait (&incdep_cond_todo, &incdep_mtx); + +#elif defined (WINDOWS32) + ResetEvent (incdep_hev_todo); + incdep_hev_todo_waiters++; + incdep_unlock (); + WaitForSingleObject (incdep_hev_todo, INFINITE); + incdep_lock (); + incdep_hev_todo_waiters--; + +#elif defined (__OS2__) + ULONG ulIgnore; + DosResetEventSem (incdep_hev_todo, &ulIgnore); + incdep_hev_todo_waiters++; + incdep_unlock (); + DosWaitEventSem (incdep_hev_todo, SEM_INDEFINITE_WAIT); + incdep_lock (); + incdep_hev_todo_waiters--; +#endif +} + +/* Reads a dep file into memory. */ +static int +incdep_read_file (struct incdep *cur, floc *f) +{ +#ifdef INCDEP_USE_KFSCACHE + size_t const cbFile = (size_t)cur->pFileObj->Stats.st_size; + + assert(cur->pFileObj->fHaveStats); + cur->file_base = incdep_xmalloc (cur, cbFile + 1); + if (cur->file_base) + { + if (kFsCacheFileSimpleOpenReadClose (g_pFsCache, cur->pFileObj, 0, cur->file_base, cbFile)) + { + cur->file_end = cur->file_base + cbFile; + cur->file_base[cbFile] = '\0'; + return 0; + } + incdep_xfree (cur, cur->file_base); + } + OSS (error, f, "%s/%s: error reading file", cur->pFileObj->pParent->Obj.pszName, cur->pFileObj->pszName); + +#else /* !INCDEP_USE_KFSCACHE */ + int fd; + struct stat st; + + errno = 0; +# ifdef O_BINARY + fd = open (cur->name, O_RDONLY | O_BINARY, 0); +# else + fd = open (cur->name, O_RDONLY, 0); +# endif + if (fd < 0) + { + /* ignore non-existing dependency files. */ + int err = errno; + if (err == ENOENT || stat (cur->name, &st) != 0) + return 1; + OSS (error, f, "%s: %s", cur->name, strerror (err)); + return -1; + } +# ifdef KBUILD_OS_WINDOWS /* fewer kernel calls */ + if (!birdStatOnFdJustSize (fd, &st.st_size)) +# else + if (!fstat (fd, &st)) +# endif + { + cur->file_base = incdep_xmalloc (cur, st.st_size + 1); + if (read (fd, cur->file_base, st.st_size) == st.st_size) + { + close (fd); + cur->file_end = cur->file_base + st.st_size; + cur->file_base[st.st_size] = '\0'; + return 0; + } + + /* bail out */ + + OSS (error, f, "%s: read: %s", cur->name, strerror (errno)); + incdep_xfree (cur, cur->file_base); + } + else + OSS (error, f, "%s: fstat: %s", cur->name, strerror (errno)); + + close (fd); +#endif /* !INCDEP_USE_KFSCACHE */ + cur->file_base = cur->file_end = NULL; + return -1; +} + +/* Free the incdep structure. */ +static void +incdep_freeit (struct incdep *cur) +{ +#ifdef PARSE_IN_WORKER + assert (!cur->recorded_variables_in_set_head); + assert (!cur->recorded_variable_defs_head); + assert (!cur->recorded_file_head); +#endif + + incdep_xfree (cur, cur->file_base); +#ifdef INCDEP_USE_KFSCACHE + /** @todo release object ref some day... */ +#endif + cur->next = NULL; + free (cur); +} + +/* A worker thread. */ +void +incdep_worker (int thrd) +{ + incdep_lock (); + + while (!incdep_terminate) + { + /* get job from the todo list. */ + + struct incdep *cur = incdep_head_todo; + if (!cur) + { + incdep_wait_todo (); + continue; + } + if (cur->next) + incdep_head_todo = cur->next; + else + incdep_head_todo = incdep_tail_todo = NULL; + incdep_num_reading++; + + /* read the file. */ + + incdep_unlock (); + cur->worker_tid = thrd; + + incdep_read_file (cur, NILF); +#ifdef PARSE_IN_WORKER + eval_include_dep_file (cur, NILF); +#endif + + cur->worker_tid = -1; + incdep_lock (); + + /* insert finished job into the done list. */ + + incdep_num_reading--; + cur->next = NULL; + if (incdep_tail_done) + incdep_tail_done->next = cur; + else + incdep_head_done = cur; + incdep_tail_done = cur; + + incdep_signal_done (); + } + + incdep_unlock (); +} + +/* Thread library specific thread functions wrapping incdep_wroker. */ +#ifdef HAVE_PTHREAD +static void * +incdep_worker_pthread (void *thrd) +{ + incdep_worker ((size_t)thrd); + return NULL; +} + +#elif defined (WINDOWS32) +static unsigned __stdcall +incdep_worker_windows (void *thrd) +{ + incdep_worker ((size_t)thrd); + return 0; +} + +#elif defined (__OS2__) +static void +incdep_worker_os2 (void *thrd) +{ + incdep_worker ((size_t)thrd); +} +#endif + +/* Checks if threads are enabled or not. + + This is a special hack so that is possible to disable the threads when in a + debian fakeroot environment. Thus, in addition to the KMK_THREADS_DISABLED + and KMK_THREADS_ENABLED environment variable check we also check for signs + of fakeroot. */ +static int +incdep_are_threads_enabled (void) +{ +#if defined (CONFIG_WITHOUT_THREADS) + return 0; +#endif + + /* Generic overrides. */ + if (getenv ("KMK_THREADS_DISABLED")) + { + O (message, 1, "Threads disabled (environment)"); + return 0; + } + if (getenv ("KMK_THREADS_ENABLED")) + return 1; + +#if defined (__gnu_linux__) || defined (__linux__) || defined(__GLIBC__) + /* Try detect fakeroot. */ + if (getenv ("FAKEROOTKEY") + || getenv ("FAKEROOTUID") + || getenv ("FAKEROOTGID") + || getenv ("FAKEROOTEUID") + || getenv ("FAKEROOTEGID") + || getenv ("FAKEROOTSUID") + || getenv ("FAKEROOTSGID") + || getenv ("FAKEROOTFUID") + || getenv ("FAKEROOTFGID") + || getenv ("FAKEROOTDONTTRYCHOWN") + || getenv ("FAKEROOT_FD_BASE") + || getenv ("FAKEROOT_DB_SEARCH_PATHS")) + { + O (message, 1, "Threads disabled (fakeroot)"); + return 0; + } + + /* LD_PRELOAD could indicate undetected debian fakeroot or some + other ingenius library which cannot deal correctly with threads. */ + if (getenv ("LD_PRELOAD")) + { + O (message, 1, "Threads disabled (LD_PRELOAD)"); + return 0; + } + +#elif defined(__APPLE__) \ + || defined(__sun__) || defined(__SunOS__) || defined(__sun) || defined(__SunOS) \ + || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) \ + || defined(__HAIKU__) + /* No broken preload libraries known to be in common use on these platforms... */ + +#elif defined(_MSC_VER) || defined(_WIN32) || defined(__OS2__) + /* No preload mess to care about. */ + +#else +# error "Add your self to the appropriate case above and send a patch to bird." +#endif + return 1; +} + +/* Creates the the worker threads. */ +static void +incdep_init (floc *f) +{ + unsigned i; +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + int rc; + pthread_attr_t attr; + +#elif defined (WINDOWS32) + unsigned tid; + uintptr_t hThread; + +#elif defined (__OS2__) + int rc; + int tid; +#endif + (void)f; + + /* heap hacks */ + +#ifdef __APPLE__ + incdep_zone = malloc_create_zone (0, 0); + if (!incdep_zone) + incdep_zone = malloc_default_zone (); +#endif + + + /* create the mutex and two condition variables / event objects. */ + +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + rc = pthread_mutex_init (&incdep_mtx, NULL); + if (rc) + ON (fatal, f, _("pthread_mutex_init failed: err=%d"), rc); + rc = pthread_cond_init (&incdep_cond_todo, NULL); + if (rc) + ON (fatal, f, _("pthread_cond_init failed: err=%d"), rc); + rc = pthread_cond_init (&incdep_cond_done, NULL); + if (rc) + ON (fatal, f, _("pthread_cond_init failed: err=%d"), rc); + +#elif defined (WINDOWS32) + InitializeCriticalSection (&incdep_mtx); + incdep_hev_todo = CreateEvent (NULL, TRUE /*bManualReset*/, FALSE /*bInitialState*/, NULL); + if (!incdep_hev_todo) + ON (fatal, f, _("CreateEvent failed: err=%d"), GetLastError()); + incdep_hev_done = CreateEvent (NULL, TRUE /*bManualReset*/, FALSE /*bInitialState*/, NULL); + if (!incdep_hev_done) + ON (fatal, f, _("CreateEvent failed: err=%d"), GetLastError()); + incdep_hev_todo_waiters = 0; + incdep_hev_done_waiters = 0; + +#elif defined (__OS2__) + _fmutex_create (&incdep_mtx, 0); + rc = DosCreateEventSem (NULL, &incdep_hev_todo, 0, FALSE); + if (rc) + ON (fatal, f, _("DosCreateEventSem failed: rc=%d"), rc); + rc = DosCreateEventSem (NULL, &incdep_hev_done, 0, FALSE); + if (rc) + ON (fatal, f, _("DosCreateEventSem failed: rc=%d"), rc); + incdep_hev_todo_waiters = 0; + incdep_hev_done_waiters = 0; +#endif + + /* create the worker threads and associated per thread data. */ + + incdep_terminate = 0; + if (incdep_are_threads_enabled()) + { + incdep_num_threads = sizeof (incdep_threads) / sizeof (incdep_threads[0]); + if (incdep_num_threads + 1 > job_slots) + incdep_num_threads = job_slots <= 1 ? 1 : job_slots - 1; + for (i = 0; i < incdep_num_threads; i++) + { + /* init caches */ + unsigned rec_size = sizeof (struct incdep_variable_in_set); + if (rec_size < sizeof (struct incdep_variable_def)) + rec_size = sizeof (struct incdep_variable_def); + if (rec_size < sizeof (struct incdep_recorded_file)) + rec_size = sizeof (struct incdep_recorded_file); + alloccache_init (&incdep_rec_caches[i], rec_size, "incdep rec", + incdep_cache_allocator, (void *)(size_t)i); + alloccache_init (&incdep_dep_caches[i], sizeof(struct dep), "incdep dep", + incdep_cache_allocator, (void *)(size_t)i); + strcache2_init (&incdep_dep_strcaches[i], + "incdep dep", /* name */ + 65536, /* hash size */ + 0, /* default segment size*/ +#ifdef HAVE_CASE_INSENSITIVE_FS + 1, /* case insensitive */ +#else + 0, /* case insensitive */ +#endif + 0); /* thread safe */ + + strcache2_init (&incdep_var_strcaches[i], + "incdep var", /* name */ + 32768, /* hash size */ + 0, /* default segment size*/ + 0, /* case insensitive */ + 0); /* thread safe */ + + /* create the thread. */ +#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS) + rc = pthread_attr_init (&attr); + if (rc) + ON (fatal, f, _("pthread_attr_init failed: err=%d"), rc); + /*rc = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE); */ + rc = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + if (rc) + ON (fatal, f, _("pthread_attr_setdetachstate failed: err=%d"), rc); + rc = pthread_create (&incdep_threads[i], &attr, + incdep_worker_pthread, (void *)(size_t)i); + if (rc) + ON (fatal, f, _("pthread_mutex_init failed: err=%d"), rc); + pthread_attr_destroy (&attr); + +#elif defined (WINDOWS32) + tid = 0; + hThread = _beginthreadex (NULL, 128*1024, incdep_worker_windows, + (void *)i, 0, &tid); + if (hThread == 0 || hThread == ~(uintptr_t)0) + ON (fatal, f, _("_beginthreadex failed: err=%d"), errno); + incdep_threads[i] = (HANDLE)hThread; + +#elif defined (__OS2__) + tid = _beginthread (incdep_worker_os2, NULL, 128*1024, (void *)i); + if (tid <= 0) + ON (fatal, f, _("_beginthread failed: err=%d"), errno); + incdep_threads[i] = tid; +#endif + } + } + else + incdep_num_threads = 0; + + incdep_initialized = 1; +} + +/* Flushes outstanding work and terminates the worker threads. + This is called from snap_deps(). */ +void +incdep_flush_and_term (void) +{ + unsigned i; + + if (!incdep_initialized) + return; + + /* flush any out standing work */ + + incdep_flush_it (NILF); + + /* tell the threads to terminate */ + + incdep_lock (); + incdep_terminate = 1; + incdep_signal_todo (); + incdep_unlock (); + + /* wait for the threads to quit */ + + for (i = 0; i < incdep_num_threads; i++) + { + /* more later? */ + + /* terminate or join up the allocation caches. */ + alloccache_term (&incdep_rec_caches[i], incdep_cache_deallocator, (void *)(size_t)i); + alloccache_join (&dep_cache, &incdep_dep_caches[i]); + strcache2_term (&incdep_dep_strcaches[i]); + strcache2_term (&incdep_var_strcaches[i]); + } + incdep_num_threads = 0; + + /* destroy the lock and condition variables / event objects. */ + + /* later */ + + incdep_initialized = 0; +} + +#ifdef PARSE_IN_WORKER +/* Flushes a strcache entry returning the actual string cache entry. + The input is freed! */ +static const char * +incdep_flush_strcache_entry (struct strcache2_entry *entry) +{ + if (!entry->user) + entry->user = (void *) strcache2_add_hashed_file (&file_strcache, + (const char *)(entry + 1), + entry->length, entry->hash); + return (const char *)entry->user; +} + +/* Flushes the recorded instructions. */ +static void +incdep_flush_recorded_instructions (struct incdep *cur) +{ + struct incdep_variable_in_set *rec_vis; + struct incdep_variable_def *rec_vd; + struct incdep_recorded_file *rec_f; + + /* Display saved error. */ + + if (cur->err_msg) +#ifdef INCDEP_USE_KFSCACHE + OSSNS (error, NILF, "%s/%s(%d): %s", cur->pFileObj->pParent->Obj.pszName, cur->pFileObj->pszName, + cur->err_line_no, cur->err_msg); +#else + OSNS (error,NILF, "%s(%d): %s", cur->name, cur->err_line_no, cur->err_msg); +#endif + + + /* define_variable_in_set */ + + rec_vis = cur->recorded_variables_in_set_head; + cur->recorded_variables_in_set_head = cur->recorded_variables_in_set_tail = NULL; + if (rec_vis) + do + { + void *free_me = rec_vis; + unsigned int name_length = rec_vis->name_entry->length; + define_variable_in_set (incdep_flush_strcache_entry (rec_vis->name_entry), + name_length, + rec_vis->value, + rec_vis->value_length, + rec_vis->duplicate_value, + rec_vis->origin, + rec_vis->recursive, + rec_vis->set, + rec_vis->flocp); + rec_vis = rec_vis->next; + incdep_free_rec (cur, free_me); + } + while (rec_vis); + + /* do_variable_definition */ + + rec_vd = cur->recorded_variable_defs_head; + cur->recorded_variable_defs_head = cur->recorded_variable_defs_tail = NULL; + if (rec_vd) + do + { + void *free_me = rec_vd; + do_variable_definition_2 (rec_vd->flocp, + incdep_flush_strcache_entry (rec_vd->name_entry), + rec_vd->value, + rec_vd->value_length, + 0, + rec_vd->value, + rec_vd->origin, + rec_vd->flavor, + rec_vd->target_var); + rec_vd = rec_vd->next; + incdep_free_rec (cur, free_me); + } + while (rec_vd); + + /* record_files */ + + rec_f = cur->recorded_file_head; + cur->recorded_file_head = cur->recorded_file_tail = NULL; + if (rec_f) + do + { + void *free_me = rec_f; + struct dep *dep; + + for (dep = rec_f->deps; dep; dep = dep->next) + dep->name = incdep_flush_strcache_entry ((struct strcache2_entry *)dep->name); + + incdep_commit_recorded_file (incdep_flush_strcache_entry (rec_f->filename_entry), + rec_f->deps, + rec_f->flocp); + + rec_f = rec_f->next; + incdep_free_rec (cur, free_me); + } + while (rec_f); +} +#endif /* PARSE_IN_WORKER */ + +/* Record / issue a warning about a misformed dep file. */ +static void +incdep_warn (struct incdep *cur, unsigned int line_no, const char *msg) +{ + if (cur->worker_tid == -1) +#ifdef INCDEP_USE_KFSCACHE + OSSNS (error,NILF, "%s/%s(%d): %s", cur->pFileObj->pParent->Obj.pszName, cur->pFileObj->pszName, line_no, msg); +#else + OSNS (error, NILF, "%s(%d): %s", cur->name, line_no, msg); +#endif +#ifdef PARSE_IN_WORKER + else + { + cur->err_line_no = line_no; + cur->err_msg = msg; + } +#endif +} + +/* Dependency or file strcache allocation / recording. */ +static const char * +incdep_dep_strcache (struct incdep *cur, const char *str, int len) +{ + const char *ret; + if (cur->worker_tid == -1) + { + /* Make sure the string is terminated before we hand it to + strcache_add_len so it does have to make a temporary copy + of it on the stack. */ + char ch = str[len]; + ((char *)str)[len] = '\0'; + ret = strcache_add_len (str, len); + ((char *)str)[len] = ch; + } + else + { + /* Add it out the strcache of the thread. */ + ret = strcache2_add (&incdep_dep_strcaches[cur->worker_tid], str, len); + ret = (const char *)strcache2_get_entry(&incdep_dep_strcaches[cur->worker_tid], ret); + } + return ret; +} + +/* Variable name allocation / recording. */ +static const char * +incdep_var_strcache (struct incdep *cur, const char *str, int len) +{ + const char *ret; + if (cur->worker_tid == -1) + { + /* XXX: we're leaking this memory now! This will be fixed later. */ + ret = xmalloc (len + 1); + memcpy ((char *)ret, str, len); + ((char *)ret)[len] = '\0'; + } + else + { + /* Add it out the strcache of the thread. */ + ret = strcache2_add (&incdep_var_strcaches[cur->worker_tid], str, len); + ret = (const char *)strcache2_get_entry(&incdep_var_strcaches[cur->worker_tid], ret); + } + return ret; +} + +/* Record / perform a variable definition in a set. + The NAME is in the string cache. + The VALUE is on the heap. + The DUPLICATE_VALUE is always 0. */ +static void +incdep_record_variable_in_set (struct incdep *cur, + const char *name, unsigned int name_length, + const char *value, + unsigned int value_length, + int duplicate_value, + enum variable_origin origin, + int recursive, + struct variable_set *set, + const floc *flocp) +{ + assert (!duplicate_value); + if (cur->worker_tid == -1) + define_variable_in_set (name, name_length, value, value_length, + duplicate_value, origin, recursive, set, flocp); +#ifdef PARSE_IN_WORKER + else + { + struct incdep_variable_in_set *rec = + (struct incdep_variable_in_set *)incdep_alloc_rec (cur); + rec->name_entry = (struct strcache2_entry *)name; + rec->value = value; + rec->value_length = value_length; + rec->duplicate_value = duplicate_value; + rec->origin = origin; + rec->recursive = recursive; + rec->set = set; + rec->flocp = flocp; + + rec->next = NULL; + if (cur->recorded_variables_in_set_tail) + cur->recorded_variables_in_set_tail->next = rec; + else + cur->recorded_variables_in_set_head = rec; + cur->recorded_variables_in_set_tail = rec; + } +#endif +} + +/* Record / perform a variable definition. The VALUE should be disposed of. */ +static void +incdep_record_variable_def (struct incdep *cur, + const floc *flocp, + const char *name, + unsigned int name_length, + char *value, + unsigned int value_length, + enum variable_origin origin, + enum variable_flavor flavor, + int target_var) +{ + if (cur->worker_tid == -1) + do_variable_definition_2 (flocp, name, value, value_length, 0, value, + origin, flavor, target_var); +#ifdef PARSE_IN_WORKER + else + { + struct incdep_variable_def *rec = + (struct incdep_variable_def *)incdep_alloc_rec (cur); + rec->flocp = flocp; + rec->name_entry = (struct strcache2_entry *)name; + rec->value = value; + rec->value_length = value_length; + rec->origin = origin; + rec->flavor = flavor; + rec->target_var = target_var; + + rec->next = NULL; + if (cur->recorded_variable_defs_tail) + cur->recorded_variable_defs_tail->next = rec; + else + cur->recorded_variable_defs_head = rec; + cur->recorded_variable_defs_tail = rec; + } +#else + (void)name_length; +#endif +} + +/* Similar to record_files in read.c, only much much simpler. */ +static void +incdep_commit_recorded_file (const char *filename, struct dep *deps, + const floc *flocp) +{ + struct file *f; + + /* Perform some validations. */ + if (filename[0] == '.' + && ( streq(filename, ".POSIX") + || streq(filename, ".EXPORT_ALL_VARIABLES") + || streq(filename, ".INTERMEDIATE") + || streq(filename, ".LOW_RESOLUTION_TIME") + || streq(filename, ".NOTPARALLEL") + || streq(filename, ".ONESHELL") + || streq(filename, ".PHONY") + || streq(filename, ".PRECIOUS") + || streq(filename, ".SECONDARY") + || streq(filename, ".SECONDTARGETEXPANSION") + || streq(filename, ".SILENT") + || streq(filename, ".SHELLFLAGS") + || streq(filename, ".SUFFIXES") + ) + ) + { + OS (error, flocp, _("reserved filename '%s' used in dependency file, ignored"), filename); + return; + } + + /* Lookup or create an entry in the database. */ + f = enter_file (filename); + if (f->double_colon) + { + OS (error, flocp, _("dependency file '%s' has a double colon entry already, ignoring"), filename); + return; + } + f->is_target = 1; + + /* Append dependencies. */ + deps = enter_prereqs (deps, NULL); + if (deps) + { + struct dep *last = f->deps; + if (!last) + f->deps = deps; + else + { + while (last->next) + last = last->next; + last->next = deps; + } + } +} + +/* Record a file.*/ +static void +incdep_record_file (struct incdep *cur, + const char *filename, + struct dep *deps, + const floc *flocp) +{ + if (cur->worker_tid == -1) + incdep_commit_recorded_file (filename, deps, flocp); +#ifdef PARSE_IN_WORKER + else + { + struct incdep_recorded_file *rec = + (struct incdep_recorded_file *) incdep_alloc_rec (cur); + + rec->filename_entry = (struct strcache2_entry *)filename; + rec->deps = deps; + rec->flocp = flocp; + + rec->next = NULL; + if (cur->recorded_file_tail) + cur->recorded_file_tail->next = rec; + else + cur->recorded_file_head = rec; + cur->recorded_file_tail = rec; + } +#endif +} + +/* Counts slashes backwards from SLASH, stopping at START. */ +static size_t incdep_count_slashes_backwards(const char *slash, const char *start) +{ + size_t slashes = 1; + assert (*slash == '\\'); + while ((uintptr_t)slash > (uintptr_t)start && slash[0 - slashes] == '\\') + slashes++; + return slashes; +} + +/* Whitespace cannot be escaped at the end of a line, there has to be + some stuff following it other than a line continuation slash. + + So, we look ahead and makes sure that there is something non-whitespaced + following this allegedly escaped whitespace. + + This code ASSUMES the file content is zero terminated! */ +static int incdep_verify_escaped_whitespace(const char *ws) +{ + char ch; + + assert(ws[-1] == '\\'); + assert(ISBLANK((unsigned int)ws[0])); + + /* If the character following the '\ ' sequence is not a whitespace, + another escape character or null terminator, we're good. */ + ws += 2; + ch = *ws; + if (ch != '\\' && !ISSPACE((unsigned int)ch) && ch != '\0') + return 1; + + /* Otherwise we'll have to parse forward till we hit the end of the + line/file or something. */ + while ((ch = *ws++) != '\0') + { + if (ch == '\\') + { + /* escaped newline? */ + ch = *ws; + if (ch == '\n') + ws++; + else if (ch == '\r' && ws[1] == '\n') + ws += 2; + else + return 1; + } + else if (ISBLANK((unsigned int)ch)) + { /* contine */ } + else if (!ISSPACE((unsigned int)ch)) + return 1; + else + return 0; /* newline; all trailing whitespace will be ignored. */ + } + + return 0; +} + +/* Unescapes the next filename and returns cached copy. + + Modifies the input string that START points to. + + When NEXTP is not NULL, ASSUME target filename and that END isn't entirely + accurate in case the filename ends with a trailing backslash. There can be + more than one filename in a this case. NEXTP will be set to the first + character after then filename. + + When NEXTP is NULL, ASSUME exactly one dependency filename and that END is + accurately deliminating the string. + */ +static const char * +incdep_unescape_and_cache_filename(struct incdep *curdep, char *start, const char *end, + int const is_dep, const char **nextp, unsigned int *linenop) +{ + unsigned const esc_mask = MAP_BLANK /* ' ' + '\t' */ + | MAP_COLON /* ':' */ + | MAP_COMMENT /* '#' */ + | MAP_EQUALS /* '=' */ + | MAP_SEMI /* ';' */ + | ( is_dep + ? MAP_PIPE /* '|' */ + : MAP_PERCENT); /* '%' */ + unsigned const all_esc_mask = esc_mask | MAP_BLANK | MAP_NEWLINE; + unsigned const stop_mask = nextp ? MAP_BLANK | MAP_NEWLINE | (!is_dep ? MAP_COLON : 0) : 0; + char volatile *src; + char volatile *dst; + + /* + * Skip forward to the first escaped character so we can avoid unnecessary shifting. + */ +#if 1 + src = start; + dst = start; +#elif 1 + static const char s_szStop[] = "\n\r\t "; + + src = memchr(start, '$', end - start); + dst = memchr(start, '\\', end - start); + if (src && ((uintptr_t)src < (uintptr_t)dst || dst == NULL)) + dst = src; + else if (dst && ((uintptr_t)dst < (uintptr_t)src || src == NULL)) + src = dst; + else + { + assert(src == NULL && dst == NULL); + if (nextp) + { + int i = sizeof(s_szStop); + while (i-- > 0) + { + char *stop = memchr(start, s_szStop[i], end - start); + if (stop) + end = stop; + } + *nextp = end; + } + return incdep_dep_strcache (curdep, start, end - start); + } + if (nextp) + { + char *stop = src; + int i = sizeof(s_szStop); + while (i-- > 0) + { + char *stop2 = memchr(start, s_szStop[i], stop - start); + if (stop2) + stop = stop2; + } + if (stop != src) + { + *nextp = stop; + return incdep_dep_strcache (curdep, start, stop - start); + } + } +#endif + + /* + * Copy char-by-char, undoing escaping as we go along. + */ + while ((uintptr_t)src < (uintptr_t)end) + { + const char ch = *src++; + if (ch != '\\' && ch != '$') + { + if (!STOP_SET (ch, stop_mask)) + *dst++ = ch; + else + { + src--; + break; + } + } + else + { + char ch2 = *src++; /* No bounds checking to handle "/dir/file\ : ..." when end points at " :". */ + if (ch == '$') + { + if (ch2 != '$') /* $$ -> $ - Ignores secondary expansion! */ + src--; + *dst++ = ch; + } + else + { + unsigned int ch2_map; + + /* Eat all the slashes and see what's at the end of them as that's all + that's relevant. If there is an escapable char, we'll emit half of + the slashes. */ + size_t const max_slashes = src - start - 1; + size_t slashes = 1; + while (ch2 == '\\') + { + slashes++; + ch2 = *src++; + } + + /* Is it escapable? */ + ch2_map = stopchar_map[(unsigned char)ch2]; + if (ch2_map & all_esc_mask) + { + /* Non-whitespace is simple: Slash slashes, output or stop. */ + if (!(ch2_map & (MAP_BLANK | MAP_NEWLINE))) + { + assert(ch2_map & esc_mask); + while (slashes >= 2) + { + *dst++ = '\\'; + slashes -= 2; + } + if (slashes || !(stop_mask & ch2_map)) + *dst++ = ch2; + else + { + src--; + break; + } + } + /* Escaped blanks or newlines. + + We have to pretent that we've already replaced any escaped newlines + and associated whitespace with a single space here. We also have to + pretend trailing whitespace doesn't exist when IS_DEP is non-zero. + This makes for pretty interesting times... */ + else + { + char ch3; + + /* An Escaped blank is interesting because it is striped unconditionally + at the end of a line, regardless of how many escaped newlines may + following it. We join the escaped newline handling if we fine one + following us. */ + if (ch2_map & MAP_BLANK) + { + /* skip whitespace and check for escaped newline. */ + volatile char * const src_saved = src; + while ((ch3 = *src) != '\0' && ISBLANK(ch3)) + src++; + if (ch3 == '\\' && src[1] == '\n') + src += 2; /* Escaped blank & newline joins into single space. */ + else if (ch3 == '\\' && src[1] == '\r' && src[2] == '\n') + src += 3; /* -> Join the escaped newline code below on the next line. */ + else if (STOP_SET(ch3, stop_mask & MAP_NEWLINE)) + { /* last thing on the line, no blanks to escape. */ + while (slashes-- > 0) + *dst++ = '\\'; + break; + } + else + { + src = src_saved; + while (slashes >= 2) + { + *dst++ = '\\'; + slashes -= 2; + } + if (slashes) + { + *dst++ = ch2; + continue; + } + assert (nextp || (uintptr_t)src >= (uintptr_t)end); + break; + } + } + /* Escaped newlines get special treatment as they an any adjacent whitespace + gets reduced to a single space, including subsequent escaped newlines. + In addition, if this is the final dependency/file and there is no + significant new characters following this escaped newline, the replacement + space will also be stripped and we won't have anything to escape, meaning + that the slashes will remain as is. Finally, none of this space stuff can + be stop characters, unless of course a newline isn't escaped. */ + else + { + assert (ch2_map & MAP_NEWLINE); + if (ch2 == '\r' && *src == '\n') + src++; + } + + /* common space/newline code */ + for (;;) + { + if (linenop) + *linenop += 1; + while ((uintptr_t)src < (uintptr_t)end && ISBLANK(*src)) + src++; + if ((uintptr_t)src >= (uintptr_t)end) + { + ch3 = '\0'; + break; + } + ch3 = *src; + if (ch3 != '\\') + break; + ch3 = src[1]; + if (ch3 == '\n') + src += 2; + else if (ch3 == '\r' && src[2] == '\n') + src += 3; + else + break; + } + + if (is_dep && STOP_SET(ch3, stop_mask | MAP_NUL)) + { /* last thing on the line, no blanks to escape. */ + while (slashes-- > 0) + *dst++ = '\\'; + break; + } + while (slashes >= 2) + { + *dst++ = '\\'; + slashes -= 2; + } + if (slashes) + *dst++ = ' '; + else + { + assert (nextp || (uintptr_t)src >= (uintptr_t)end); + break; + } + } + } + /* Just output the slash if non-escapable character: */ + else + { + while (slashes-- > 0) + *dst++ = '\\'; + src--; + } + } + } + } + + if (nextp) + *nextp = (const char *)src; + return incdep_dep_strcache(curdep, start, dst - start); +} + +/* no nonsense dependency file including. + + Because nobody wants bogus dependency files to break their incremental + builds with hard to comprehend error messages, this function does not + use the normal eval routine but does all the parsing itself. This isn't, + as much work as it sounds, because the necessary feature set is very + limited. + + eval_include_dep_file groks: + + define var + endef + + var [|:|?|>]= value [\] + + [\] + file: [deps] [\] + + */ +static void +eval_include_dep_file (struct incdep *curdep, floc *f) +{ + unsigned line_no = 1; + const char *file_end = curdep->file_end; + const char *cur = curdep->file_base; + const char *endp; + + /* if no file data, just return immediately. */ + if (!cur) + return; + + /* now parse the file. */ + while ((uintptr_t)cur < (uintptr_t)file_end) + { + /* skip empty lines */ + while ((uintptr_t)cur < (uintptr_t)file_end && ISSPACE (*cur) && *cur != '\n') + ++cur; + if ((uintptr_t)cur >= (uintptr_t)file_end) + break; + if (*cur == '#') + { + cur = memchr (cur, '\n', file_end - cur); + if (!cur) + break; + } + if (*cur == '\\') + { + unsigned eol_len = (file_end - cur > 1 && cur[1] == '\n') ? 2 + : (file_end - cur > 2 && cur[1] == '\r' && cur[2] == '\n') ? 3 + : (file_end - cur == 1) ? 1 : 0; + if (eol_len) + { + cur += eol_len; + line_no++; + continue; + } + } + if (*cur == '\n') + { + cur++; + line_no++; + continue; + } + + /* define var + ... + endef */ + if (strneq (cur, "define ", 7)) + { + const char *var; + unsigned var_len; + const char *value_start; + const char *value_end; + char *value; + unsigned value_len; + int found_endef = 0; + + /* extract the variable name. */ + cur += 7; + while (ISBLANK (*cur)) + ++cur; + value_start = endp = memchr (cur, '\n', file_end - cur); + if (!endp) + endp = cur; + while (endp > cur && ISSPACE (endp[-1])) + --endp; + var_len = endp - cur; + if (!var_len) + { + incdep_warn (curdep, line_no, "bogus define statement."); + break; + } + var = incdep_var_strcache (curdep, cur, var_len); + + /* find the end of the variable. */ + cur = value_end = value_start = value_start + 1; + ++line_no; + while ((uintptr_t)cur < (uintptr_t)file_end) + { + /* check for endef, don't bother with skipping leading spaces. */ + if ( file_end - cur >= 5 + && strneq (cur, "endef", 5)) + { + endp = cur + 5; + while ((uintptr_t)endp < (uintptr_t)file_end && ISSPACE (*endp) && *endp != '\n') + endp++; + if ((uintptr_t)endp >= (uintptr_t)file_end || *endp == '\n') + { + found_endef = 1; + cur = (uintptr_t)endp >= (uintptr_t)file_end ? file_end : endp + 1; + break; + } + } + + /* skip a line ahead. */ + cur = value_end = memchr (cur, '\n', file_end - cur); + if (cur != NULL) + ++cur; + else + cur = value_end = file_end; + ++line_no; + } + + if (!found_endef) + { + incdep_warn (curdep, line_no, "missing endef, dropping the rest of the file."); + break; + } + value_len = value_end - value_start; + if (memchr (value_start, '\0', value_len)) + { + incdep_warn (curdep, line_no, "'\\0' in define, dropping the rest of the file."); + break; + } + + /* make a copy of the value, converting \r\n to \n, and define it. */ + value = incdep_xmalloc (curdep, value_len + 1); + endp = memchr (value_start, '\r', value_len); + if (endp) + { + const char *src = value_start; + char *dst = value; + for (;;) + { + size_t len = endp - src; + memcpy (dst, src, len); + dst += len; + src = endp; + if ((uintptr_t)src + 1 < (uintptr_t)file_end && src[1] == '\n') + src++; /* skip the '\r' */ + if (src >= value_end) + break; + endp = memchr (endp + 1, '\r', src - value_end); + if (!endp) + endp = value_end; + } + value_len = dst - value; + } + else + memcpy (value, value_start, value_len); + value [value_len] = '\0'; + + incdep_record_variable_in_set (curdep, + var, var_len, value, value_len, + 0 /* don't duplicate */, o_file, + 0 /* defines are recursive but this is faster */, + NULL /* global set */, f); + } + + /* file: deps + OR + variable [:]= value */ + else + { + const char *equalp; + const char *eol; + + /* Look for a colon or an equal sign. In the assignment case, we + require it to be on the same line as the variable name to simplify + the code. Because of clang, we cannot make the same assumptions + with file dependencies. So, start with the equal. */ + + assert (*cur != '\n'); + eol = memchr (cur, '\n', file_end - cur); + if (!eol) + eol = file_end; + equalp = memchr (cur, '=', eol - cur); + if (equalp && equalp != cur && (ISSPACE(equalp[-1]) || equalp[-1] != '\\')) + { + /* An assignment of some sort. */ + const char *var; + unsigned var_len; + const char *value_start; + const char *value_end; + char *value; + unsigned value_len; + unsigned multi_line = 0; + enum variable_flavor flavor; + + /* figure the flavor first. */ + flavor = f_recursive; + if (equalp > cur) + { + if (equalp[-1] == ':') + flavor = f_simple; + else if (equalp[-1] == '?') + flavor = f_conditional; + else if (equalp[-1] == '+') + flavor = f_append; + else if (equalp[-1] == '>') + flavor = f_prepend; + } + + /* extract the variable name. */ + endp = flavor == f_recursive ? equalp : equalp - 1; + while (endp > cur && ISBLANK (endp[-1])) + --endp; + var_len = endp - cur; + if (!var_len) + { + incdep_warn (curdep, line_no, "empty variable. (includedep)"); + break; + } + if ( memchr (cur, '$', var_len) + || memchr (cur, ' ', var_len) + || memchr (cur, '\t', var_len)) + { + incdep_warn (curdep, line_no, "fancy variable name. (includedep)"); + break; + } + var = incdep_var_strcache (curdep, cur, var_len); + + /* find the start of the value. */ + cur = equalp + 1; + while ((uintptr_t)cur < (uintptr_t)file_end && ISBLANK (*cur)) + cur++; + value_start = cur; + + /* find the end of the value / line (this isn't 101% correct). */ + value_end = cur; + while ((uintptr_t)cur < (uintptr_t)file_end) + { + endp = value_end = memchr (cur, '\n', file_end - cur); + if (!value_end) + value_end = file_end; + if (value_end - 1 >= cur && value_end[-1] == '\r') + --value_end; + if (value_end - 1 < cur || value_end[-1] != '\\') + { + cur = endp ? endp + 1 : file_end; + break; + } + --value_end; + if (value_end - 1 >= cur && value_end[-1] == '\\') + { + incdep_warn (curdep, line_no, "fancy escaping! (includedep)"); + cur = NULL; + break; + } + if (!endp) + { + cur = file_end; + break; + } + + cur = endp + 1; + ++multi_line; + ++line_no; + } + if (!cur) + break; + ++line_no; + + /* make a copy of the value, converting \r\n to \n, and define it. */ + value_len = value_end - value_start; + value = incdep_xmalloc (curdep, value_len + 1); + if (!multi_line) + memcpy (value, value_start, value_len); + else + { + /* unescape it */ + const char *src = value_start; + char *dst = value; + while (src < value_end) + { + const char *nextp; + + endp = memchr (src, '\n', value_end - src); + if (!endp) + nextp = endp = value_end; + else + nextp = endp + 1; + if (endp > src && endp[-1] == '\r') + --endp; + if (endp > src && endp[-1] == '\\') + --endp; + + if (src != value_start) + *dst++ = ' '; + memcpy (dst, src, endp - src); + dst += endp - src; + src = nextp; + } + value_len = dst - value; + } + value [value_len] = '\0'; + + /* do the definition */ + if (flavor == f_recursive + || ( flavor == f_simple + && !memchr (value, '$', value_len))) + incdep_record_variable_in_set (curdep, + var, var_len, value, value_len, + 0 /* don't duplicate */, o_file, + flavor == f_recursive /* recursive */, + NULL /* global set */, f); + else + incdep_record_variable_def (curdep, + f, var, var_len, value, value_len, + o_file, flavor, 0 /* not target var */); + } + else + { + /* Expecting: file: dependencies */ + + int unescape_filename = 0; + const char *filename; + const char *fnnext; + const char *fnend; + const char *colonp; + struct dep *deps = 0; + struct dep **nextdep = &deps; + struct dep *dep; + + + /* Locate the next file colon. If it's not within the bounds of + the current line, check that all new line chars are escaped. */ + + colonp = memchr (cur, ':', file_end - cur); + while ( colonp + && ( ( colonp != cur + && colonp[-1] == '\\' + && incdep_count_slashes_backwards (&colonp[-1], cur) & 1) +#ifdef HAVE_DOS_PATHS + || ( colonp + 1 < file_end + && (colonp[1] == '/' || colonp[1] == '\\') + && colonp > cur + && isalpha ((unsigned char)colonp[-1]) + && ( colonp == cur + 1 + || ISBLANK ((unsigned char)colonp[-2]))) +#endif + ) + ) + colonp = memchr (colonp + 1, ':', file_end - (colonp + 1)); + if (!colonp) + { + incdep_warn (curdep, line_no, "no colon."); + break; + } + + if ((uintptr_t)colonp < (uintptr_t)eol) + unescape_filename = memchr (cur, '\\', colonp - cur) != NULL + || memchr (cur, '$', colonp - cur) != NULL; + else if (memchr (eol, '=', colonp - eol)) + { + incdep_warn (curdep, line_no, "multi line assignment / dependency confusion."); + break; + } + else + { + const char *sol = cur; + do + { + char *eol2 = (char *)eol - 1; + if ((uintptr_t)eol2 >= (uintptr_t)sol && *eol2 == '\r') /* DOS line endings. */ + eol2--; + if ((uintptr_t)eol2 < (uintptr_t)sol || *eol2 != '\\') + incdep_warn (curdep, line_no, "no colon."); + else if (eol2 != sol && eol2[-1] == '\\') + incdep_warn (curdep, line_no, "fancy EOL escape. (includedep)"); + else + { + line_no++; + sol = eol + 1; + eol = memchr (sol, '\n', colonp - sol); + continue; + } + sol = NULL; + break; + } + while (eol != NULL); + if (!sol) + break; + unescape_filename = 1; + } + + /* Extract the first filename after trimming and basic checks. */ + fnend = colonp; + while ((uintptr_t)fnend > (uintptr_t)cur && ISBLANK (fnend[-1])) + --fnend; + if (cur == fnend) + { + incdep_warn (curdep, line_no, "empty filename."); + break; + } + fnnext = cur; + if (!unescape_filename) + { + while (fnnext != fnend && !ISBLANK (*fnnext)) + fnnext++; + filename = incdep_dep_strcache (curdep, cur, fnnext - cur); + } + else + filename = incdep_unescape_and_cache_filename (curdep, (char *)fnnext, fnend, 0, &fnnext, NULL); + + /* parse any dependencies. */ + cur = colonp + 1; + while ((uintptr_t)cur < (uintptr_t)file_end) + { + const char *dep_file; + + /* skip blanks and count lines. */ + char ch = 0; + while ((uintptr_t)cur < (uintptr_t)file_end && ISSPACE ((ch = *cur)) && ch != '\n') + ++cur; + if ((uintptr_t)cur >= (uintptr_t)file_end) + break; + if (ch == '\n') + { + cur++; + line_no++; + break; + } + + /* continuation + eol? */ + if (ch == '\\') + { + unsigned eol_len = (file_end - cur > 1 && cur[1] == '\n') ? 2 + : (file_end - cur > 2 && cur[1] == '\r' && cur[2] == '\n') ? 3 + : (file_end - cur == 1) ? 1 : 0; + if (eol_len) + { + cur += eol_len; + line_no++; + continue; + } + } + + /* find the end of the filename and cache it */ + dep_file = NULL; + endp = cur; + for (;;) + if ((uintptr_t)endp < (uintptr_t)file_end) + { + ch = *endp; + if (ch != '\\' && ch != '$' ) + { + if (!ISSPACE (ch)) + endp++; + else + { + dep_file = incdep_dep_strcache(curdep, cur, endp - cur); + break; + } + } + else + { + /* potential escape sequence, let the unescaper do the rest. */ + dep_file = incdep_unescape_and_cache_filename (curdep, (char *)cur, file_end, 1, &endp, &line_no); + break; + } + } + else + { + dep_file = incdep_dep_strcache(curdep, cur, endp - cur); + break; + } + + /* add it to the list. */ + *nextdep = dep = incdep_alloc_dep (curdep); + dep->includedep = 1; + dep->name = dep_file; + nextdep = &dep->next; + + cur = endp; + } + + /* enter the file with its dependencies. */ + incdep_record_file (curdep, filename, deps, f); + + /* More files? Record them with the same dependency list. */ + if ((uintptr_t)fnnext < (uintptr_t)fnend) + for (;;) + { + const char *filename_prev = filename; + while (fnnext != fnend && ISBLANK (*fnnext)) + fnnext++; + if (fnnext == fnend) + break; + if (*fnnext == '\\') + { + if (fnnext[1] == '\n') + { + line_no++; + fnnext += 2; + continue; + } + if (fnnext[1] == '\r' && fnnext[2] == '\n') + { + line_no++; + fnnext += 3; + continue; + } + } + + if (!unescape_filename) + { + const char *fnstart = fnnext; + while (fnnext != fnend && !ISBLANK (*fnnext)) + fnnext++; + filename = incdep_dep_strcache (curdep, fnstart, fnnext - fnstart); + } + else + filename = incdep_unescape_and_cache_filename (curdep, (char *)fnnext, fnend, 0, &fnnext, NULL); + if (filename != filename_prev) /* clang optimization. */ + incdep_record_file (curdep, filename, incdep_dup_dep_list (curdep, deps), f); + } + } + } + } + + /* free the file data */ + incdep_xfree (curdep, curdep->file_base); + curdep->file_base = curdep->file_end = NULL; +} + +/* Flushes the incdep todo and done lists. */ +static void +incdep_flush_it (floc *f) +{ + incdep_lock (); + for (;;) + { + struct incdep *cur = incdep_head_done; + + /* if the done list is empty, grab a todo list entry. */ + if (!cur && incdep_head_todo) + { + cur = incdep_head_todo; + if (cur->next) + incdep_head_todo = cur->next; + else + incdep_head_todo = incdep_tail_todo = NULL; + incdep_unlock (); + + incdep_read_file (cur, f); + eval_include_dep_file (cur, f); + incdep_freeit (cur); + + incdep_lock (); + continue; + } + + /* if the todo list and done list are empty we're either done + or will have to wait for the thread(s) to finish. */ + if (!cur && !incdep_num_reading) + break; /* done */ + if (!cur) + { + while (!incdep_head_done) + incdep_wait_done (); + cur = incdep_head_done; + } + + /* we grab the entire done list and work thru it. */ + incdep_head_done = incdep_tail_done = NULL; + incdep_unlock (); + + while (cur) + { + struct incdep *next = cur->next; +#ifdef PARSE_IN_WORKER + incdep_flush_recorded_instructions (cur); +#else + eval_include_dep_file (cur, f); +#endif + incdep_freeit (cur); + cur = next; + } + + incdep_lock (); + } /* outer loop */ + incdep_unlock (); +} + + +/* splits up a list of file names and feeds it to eval_include_dep_file, + employing threads to try speed up the file reading. */ +void +eval_include_dep (const char *names, floc *f, enum incdep_op op) +{ + struct incdep *head = 0; + struct incdep *tail = 0; + struct incdep *cur; + const char *names_iterator = names; + const char *name; + unsigned int name_len; + + /* loop through NAMES, creating a todo list out of them. */ + + while ((name = find_next_token (&names_iterator, &name_len)) != 0) + { +#ifdef INCDEP_USE_KFSCACHE + KFSLOOKUPERROR enmError; + PKFSOBJ pFileObj = kFsCacheLookupWithLengthA (g_pFsCache, name, name_len, &enmError); + if (!pFileObj) + continue; + if (pFileObj->bObjType != KFSOBJ_TYPE_FILE) + { + kFsCacheObjRelease (g_pFsCache, pFileObj); + continue; + } + + cur = xmalloc (sizeof (*cur)); /* not incdep_xmalloc here */ + cur->pFileObj = pFileObj; +#else + cur = xmalloc (sizeof (*cur) + name_len); /* not incdep_xmalloc here */ + memcpy (cur->name, name, name_len); + cur->name[name_len] = '\0'; +#endif + + cur->file_base = cur->file_end = NULL; + cur->worker_tid = -1; +#ifdef PARSE_IN_WORKER + cur->err_line_no = 0; + cur->err_msg = NULL; + cur->recorded_variables_in_set_head = NULL; + cur->recorded_variables_in_set_tail = NULL; + cur->recorded_variable_defs_head = NULL; + cur->recorded_variable_defs_tail = NULL; + cur->recorded_file_head = NULL; + cur->recorded_file_tail = NULL; +#endif + + cur->next = NULL; + if (tail) + tail->next = cur; + else + head = cur; + tail = cur; + } + +#ifdef ELECTRIC_HEAP + if (1) +#else + if (op == incdep_read_it) +#endif + { + /* work our way thru the files directly */ + + cur = head; + while (cur) + { + struct incdep *next = cur->next; + incdep_read_file (cur, f); + eval_include_dep_file (cur, f); + incdep_freeit (cur); + cur = next; + } + } + else + { + /* initialize the worker threads and related stuff the first time around. */ + + if (!incdep_initialized) + incdep_init (f); + + /* queue the files and notify the worker threads. */ + + incdep_lock (); + + if (incdep_tail_todo) + incdep_tail_todo->next = head; + else + incdep_head_todo = head; + incdep_tail_todo = tail; + + incdep_signal_todo (); + incdep_unlock (); + + /* flush the todo queue if we're requested to do so. */ + + if (op == incdep_flush) + incdep_flush_it (f); + } +} + +#endif /* CONFIG_WITH_INCLUDEDEP */ + |