1 //
2 //  Copyright (C) 2011-2020  Nick Gasson
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 //
17 
18 #include "util.h"
19 #include "lib.h"
20 #include "tree.h"
21 #include "common.h"
22 #include "vcode.h"
23 
24 #include <assert.h>
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <ctype.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <dirent.h>
31 #include <errno.h>
32 #include <time.h>
33 #include <fcntl.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <sys/time.h>
37 
38 typedef struct search_path search_path_t;
39 typedef struct lib_unit    lib_unit_t;
40 typedef struct lib_index   lib_index_t;
41 typedef struct lib_list    lib_list_t;
42 typedef struct lib_map     lib_map_t;
43 
44 struct lib_unit {
45    tree_t        top;
46    tree_kind_t   kind;
47    tree_rd_ctx_t read_ctx;
48    bool          dirty;
49    lib_mtime_t   mtime;
50 };
51 
52 struct lib_index {
53    ident_t      name;
54    tree_kind_t  kind;
55    lib_index_t *next;
56 };
57 
58 struct lib {
59    char         path[PATH_MAX];
60    ident_t      name;
61    unsigned     n_units;
62    unsigned     units_alloc;
63    lib_unit_t  *units;
64    lib_index_t *index;
65    lib_mtime_t  index_mtime;
66    off_t        index_size;
67    int          lock_fd;
68    bool         readonly;
69 };
70 
71 struct lib_list {
72    lib_t       item;
73    lib_list_t *next;
74 };
75 
76 struct search_path {
77    search_path_t *next;
78    const char    *path;
79 };
80 
81 static lib_t          work = NULL;
82 static lib_list_t    *loaded = NULL;
83 static search_path_t *search_paths = NULL;
84 
85 static const char *lib_file_path(lib_t lib, const char *name);
86 static lib_mtime_t lib_stat_mtime(struct stat *st);
87 
standard_suffix(vhdl_standard_t std)88 static const char *standard_suffix(vhdl_standard_t std)
89 {
90    static const char *ext[] = {
91       "87", "93", "00", "02", "08"
92    };
93 
94    assert(std < ARRAY_LEN(ext));
95    return ext[std];
96 }
97 
upcase_name(const char * name)98 static ident_t upcase_name(const char * name)
99 {
100    char *name_copy LOCAL = xstrdup(name);
101 
102    char *last_slash = strrchr(name_copy, '/');
103    while ((last_slash != NULL) && (*(last_slash + 1) == '\0')) {
104       *last_slash = '\0';
105       last_slash = strrchr(name_copy, '/');
106    }
107 
108    char *name_up = (last_slash != NULL) ? last_slash + 1 : name_copy;
109    for (char *p = name_up; *p != '\0'; p++)
110       *p = toupper((int)*p);
111 
112    char *last_dot = strrchr(name_up, '.');
113    if (last_dot != NULL)
114       *last_dot = '\0';
115 
116    ident_t i = ident_new(name_up);
117    return i;
118 }
119 
lib_add_to_index(lib_t lib,ident_t name,tree_kind_t kind)120 static void lib_add_to_index(lib_t lib, ident_t name, tree_kind_t kind)
121 {
122    // Keep the index in sorted order to make library builds reproducible
123    lib_index_t **it;
124    for (it = &(lib->index);
125         *it != NULL && ident_compare((*it)->name, name) < 0;
126         it = &((*it)->next))
127       ;
128 
129    if (*it != NULL && (*it)->name == name) {
130       // Already in the index
131       (*it)->kind = kind;
132    }
133    else {
134       lib_index_t *new = xmalloc(sizeof(lib_index_t));
135       new->name = name;
136       new->kind = kind;
137       new->next = *it;
138 
139       *it = new;
140    }
141 }
142 
lib_read_index(lib_t lib)143 static void lib_read_index(lib_t lib)
144 {
145    fbuf_t *f = lib_fbuf_open(lib, "_index", FBUF_IN);
146    if (f != NULL) {
147       struct stat st;
148       if (stat(fbuf_file_name(f), &st) < 0)
149          fatal_errno("%s", fbuf_file_name(f));
150 
151       lib->index_mtime = lib_stat_mtime(&st);
152       lib->index_size  = st.st_size;
153 
154       ident_rd_ctx_t ictx = ident_read_begin(f);
155       lib_index_t **insert = &(lib->index);
156 
157       const int entries = read_u32(f);
158       for (int i = 0; i < entries; i++) {
159          ident_t name = ident_read(ictx);
160          tree_kind_t kind = read_u16(f);
161          assert(kind < T_LAST_TREE_KIND);
162 
163          while (*insert && ident_compare((*insert)->name, name) < 0)
164             insert = &((*insert)->next);
165 
166          if (*insert && (*insert)->name == name) {
167             (*insert)->kind = kind;
168             insert = &((*insert)->next);
169          }
170          else {
171             lib_index_t *new = xmalloc(sizeof(lib_index_t));
172             new->name = name;
173             new->kind = kind;
174             new->next = *insert;
175 
176             *insert = new;
177             insert = &(new->next);
178          }
179       }
180 
181       ident_read_end(ictx);
182       fbuf_close(f);
183    }
184 }
185 
lib_init(const char * name,const char * rpath,int lock_fd)186 static lib_t lib_init(const char *name, const char *rpath, int lock_fd)
187 {
188    struct lib *l = xcalloc(sizeof(struct lib));
189    l->n_units  = 0;
190    l->units    = NULL;
191    l->name     = upcase_name(name);
192    l->index    = NULL;
193    l->lock_fd  = lock_fd;
194    l->readonly = false;
195 
196    if (rpath == NULL)
197       l->path[0] = '\0';
198    else if (realpath(rpath, l->path) == NULL)
199       checked_sprintf(l->path, PATH_MAX, "%s", rpath);
200 
201    lib_list_t *el = xmalloc(sizeof(lib_list_t));
202    el->item = l;
203    el->next = loaded;
204    loaded = el;
205 
206    if (l->lock_fd == -1 && rpath != NULL) {
207       const char *lock_path = lib_file_path(l, "_NVC_LIB");
208 
209       // Try to open the lock file read-write as this is required for
210       // exlusive locking on some NFS implementations
211       int mode = O_RDWR;
212       if (access(lock_path, mode) != 0) {
213          if (errno == EACCES || errno == EPERM) {
214             mode = O_RDONLY;
215             l->readonly = true;
216          }
217          else
218             fatal_errno("access: %s", lock_path);
219       }
220 
221       if ((l->lock_fd = open(lock_path, mode)) < 0)
222          fatal_errno("open: %s", lock_path);
223 
224       file_read_lock(l->lock_fd);
225    }
226 
227    lib_read_index(l);
228 
229    if (l->lock_fd != -1)
230       file_unlock(l->lock_fd);
231 
232    return l;
233 }
234 
lib_find_in_index(lib_t lib,ident_t name)235 static lib_index_t *lib_find_in_index(lib_t lib, ident_t name)
236 {
237    lib_index_t *it;
238    for (it = lib->index;
239         (it != NULL) && (it->name != name);
240         it = it->next)
241       ;
242 
243    return it;
244 }
245 
lib_put_aux(lib_t lib,tree_t unit,tree_rd_ctx_t ctx,bool dirty,lib_mtime_t mtime)246 static lib_unit_t *lib_put_aux(lib_t lib, tree_t unit,
247                                tree_rd_ctx_t ctx, bool dirty,
248                                lib_mtime_t mtime)
249 {
250    assert(lib != NULL);
251    assert(unit != NULL);
252 
253    lib_unit_t *where = NULL;
254    ident_t name = tree_ident(unit);
255 
256    for (unsigned i = 0; (where == NULL) && (i < lib->n_units); i++) {
257       if (tree_ident(lib->units[i].top) == tree_ident(unit))
258          where = &(lib->units[i]);
259    }
260 
261    if (where == NULL) {
262       if (lib->n_units == 0) {
263          lib->units_alloc = 16;
264          lib->units = xmalloc(sizeof(lib_unit_t) * lib->units_alloc);
265       }
266       else if (lib->n_units == lib->units_alloc) {
267          lib->units_alloc *= 2;
268          lib->units = xrealloc(lib->units,
269                                sizeof(lib_unit_t) * lib->units_alloc);
270       }
271 
272       where = &(lib->units[lib->n_units++]);
273    }
274 
275    where->top      = unit;
276    where->read_ctx = ctx;
277    where->dirty    = dirty;
278    where->mtime    = mtime;
279    where->kind     = tree_kind(unit);
280 
281    lib_add_to_index(lib, name, tree_kind(unit));
282 
283    return where;
284 }
285 
lib_find_at(const char * name,const char * path,bool exact)286 static lib_t lib_find_at(const char *name, const char *path, bool exact)
287 {
288    char dir[PATH_MAX];
289    char *p = dir + checked_sprintf(dir, sizeof(dir) - 4 - strlen(name),
290                                    "%s" PATH_SEP, path);
291    bool found = false;
292 
293    if (!exact) {
294       // Append library name converting to lower case
295       for (const char *n = name; *n != '\0'; n++)
296          *p++ = tolower((int)*n);
297 
298       // Try suffixing standard revision extensions first
299       for (vhdl_standard_t s = standard(); (s > STD_87) && !found; s--) {
300          checked_sprintf(p, 4, ".%s", standard_suffix(s));
301          found = (access(dir, F_OK) == 0);
302       }
303    }
304 
305    if (!found) {
306       *p = '\0';
307       if (access(dir, F_OK) < 0)
308          return NULL;
309    }
310 
311    char marker[PATH_MAX];
312    checked_sprintf(marker, sizeof(marker), "%s" PATH_SEP "_NVC_LIB", dir);
313    if (access(marker, F_OK) < 0)
314       return NULL;
315 
316    return lib_init(name, dir, -1);
317 }
318 
lib_file_path(lib_t lib,const char * name)319 static const char *lib_file_path(lib_t lib, const char *name)
320 {
321    static char buf[PATH_MAX];
322    checked_sprintf(buf, sizeof(buf), "%s" PATH_SEP "%s", lib->path, name);
323    return buf;
324 }
325 
lib_loaded(ident_t name_i)326 lib_t lib_loaded(ident_t name_i)
327 {
328    if (name_i == work_i && work != NULL)
329       return work;
330 
331    for (lib_list_t *it = loaded; it != NULL; it = it->next) {
332       if (lib_name(it->item) == name_i)
333          return it->item;
334    }
335 
336    return NULL;
337 }
338 
lib_new(const char * name,const char * path)339 lib_t lib_new(const char *name, const char *path)
340 {
341    ident_t name_i = upcase_name(name);
342 
343    lib_t lib = lib_loaded(name_i);
344    if (lib != NULL)
345       return lib;
346 
347    char *name_copy LOCAL = xstrdup(name);
348    char *sep = strrchr(name_copy, '/');
349 
350    // Ignore trailing slashes
351    while ((sep != NULL) && (*(sep + 1) == '\0')) {
352       *sep = '\0';
353       sep = strrchr(name_copy, '/');
354    }
355 
356    if (sep != NULL) {
357       // Work library contains explicit path
358       *sep = '\0';
359       name = sep + 1;
360       lib = lib_find_at(name, name_copy, false);
361    }
362    else {
363       // Look only in working directory
364       lib = lib_find_at(name, ".", false);
365    }
366 
367    if (lib != NULL)
368       return lib;
369 
370    const char *last_slash = strrchr(name, '/');
371    const char *last_dot = strrchr(name, '.');
372 
373    if ((last_dot != NULL) && (last_dot > last_slash)) {
374       const char *ext = standard_suffix(standard());
375       if (strcmp(last_dot + 1, ext) != 0)
376          fatal("library directory suffix must be '%s' for this standard", ext);
377    }
378 
379    for (const char *p = (last_slash ? last_slash + 1 : name);
380         (*p != '\0') && (p != last_dot);
381         p++) {
382       if (!isalnum((int)*p) && (*p != '_'))
383          fatal("invalid character '%c' in library name", *p);
384    }
385 
386    char *lockf LOCAL = xasprintf("%s" PATH_SEP "%s", path, "_NVC_LIB");
387 
388    struct stat buf;
389    if (stat(path, &buf) == 0) {
390       if (S_ISDIR(buf.st_mode)) {
391          struct stat sb;
392          if (stat(lockf, &sb) != 0 && !opt_get_int("force-init"))
393             fatal("directory %s already exists and is not an NVC library "
394                   "(use --force-init to override this check)", path);
395       }
396       else
397          fatal("path %s already exists and is not a directory", path);
398    }
399 
400    make_dir(path);
401 
402    int fd = open(lockf, O_CREAT | O_EXCL | O_RDWR, 0777);
403    if (fd < 0) {
404       // If errno is EEXIST we raced with another process to create the
405       // lock file.  Calling into lib_init with fd as -1 will cause it
406       // to be opened again without O_CREAT.
407       if (errno != EEXIST)
408          fatal_errno("lib_new: %s", lockf);
409    }
410    else {
411       file_write_lock(fd);
412 
413       const char *marker = PACKAGE_STRING "\n";
414       if (write(fd, marker, strlen(marker)) < 0)
415          fatal_errno("write: %s", path);
416    }
417 
418    return lib_init(name, path, fd);
419 }
420 
lib_tmp(const char * name)421 lib_t lib_tmp(const char *name)
422 {
423    // For unit tests, avoids creating files
424    return lib_init(name, NULL, -1);
425 }
426 
push_path(const char * path)427 static void push_path(const char *path)
428 {
429    for (search_path_t *it = search_paths; it != NULL; it = it->next) {
430       if (strcmp(it->path, path) == 0)
431          return;
432    }
433 
434    search_path_t *s = xmalloc(sizeof(search_path_t));
435    s->next = search_paths;
436    s->path = path;
437 
438    search_paths = s;
439 }
440 
lib_default_search_paths(void)441 static void lib_default_search_paths(void)
442 {
443    if (search_paths == NULL) {
444       push_path(DATADIR);
445 
446       const char *home_env = getenv("HOME");
447       if (home_env) {
448          char *path;
449          if (asprintf(&path, "%s/.%s/lib", home_env, PACKAGE) < 0)
450             fatal_errno("asprintf");
451          push_path(path);
452       }
453 
454       char *env_copy = NULL;
455       const char *libpath_env = getenv("NVC_LIBPATH");
456       if (libpath_env) {
457          env_copy = strdup(libpath_env);
458 
459          char *path_tok = strtok(env_copy, ":");
460          do {
461             push_path(path_tok);
462          } while ((path_tok = strtok(NULL, ":")));
463       }
464    }
465 }
466 
lib_enum_search_paths(void ** token)467 const char *lib_enum_search_paths(void **token)
468 {
469    if (*token == NULL) {
470       lib_default_search_paths();
471       *token = search_paths;
472    }
473 
474    if (*token == (void *)-1)
475       return NULL;
476    else {
477       search_path_t *old = *token;
478       if ((*token = old->next) == NULL)
479          *token = (void *)-1;
480       return old->path;
481    }
482 }
483 
lib_add_search_path(const char * path)484 void lib_add_search_path(const char *path)
485 {
486    lib_default_search_paths();
487    push_path(strdup(path));
488 }
489 
lib_add_map(const char * name,const char * path)490 void lib_add_map(const char *name, const char *path)
491 {
492    lib_t lib = lib_find_at(name, path, true);
493    if (lib == NULL)
494       warnf("library %s not found at %s", name, path);
495 }
496 
lib_find(ident_t name_i,bool required)497 lib_t lib_find(ident_t name_i, bool required)
498 {
499    lib_t lib = lib_loaded(name_i);
500    if (lib != NULL)
501       return lib;
502 
503    lib_default_search_paths();
504 
505    const char *name_str = istr(name_i);
506    for (search_path_t *it = search_paths; it != NULL; it = it->next) {
507       if ((lib = lib_find_at(name_str, it->path, false)))
508          break;
509    }
510 
511    if (lib == NULL) {
512       text_buf_t *tb = tb_new();
513       tb_printf(tb, "library %s not found in:\n", name_str);
514       for (search_path_t *it = search_paths; it != NULL; it = it->next)
515          tb_printf(tb, "  %s\n", it->path);
516       if (required)
517          fatal("%s", tb_get(tb));
518       else
519          errorf("%s", tb_get(tb));
520    }
521 
522    return lib;
523 }
524 
lib_fopen(lib_t lib,const char * name,const char * mode)525 FILE *lib_fopen(lib_t lib, const char *name, const char *mode)
526 {
527    assert(lib != NULL);
528    return fopen(lib_file_path(lib, name), mode);
529 }
530 
lib_fbuf_open(lib_t lib,const char * name,fbuf_mode_t mode)531 fbuf_t *lib_fbuf_open(lib_t lib, const char *name, fbuf_mode_t mode)
532 {
533    assert(lib != NULL);
534    if (lib->path[0] == '\0')
535       return NULL;
536    else
537       return fbuf_open(lib_file_path(lib, name), mode);
538 }
539 
lib_free(lib_t lib)540 void lib_free(lib_t lib)
541 {
542    assert(lib != NULL);
543    assert(lib != work);
544 
545    if (lib->lock_fd != -1)
546       close(lib->lock_fd);
547 
548    for (lib_list_t *it = loaded, *prev = NULL;
549         it != NULL; loaded = it, it = it->next) {
550 
551       if (it->item == lib) {
552          if (prev)
553             prev->next = it->next;
554          else
555             loaded = it->next;
556          free(it);
557          break;
558       }
559    }
560 
561    while (lib->index) {
562       lib_index_t *tmp = lib->index->next;
563       free(lib->index);
564       lib->index = tmp;
565    }
566 
567    if (lib->units != NULL)
568       free(lib->units);
569    free(lib);
570 }
571 
lib_destroy(lib_t lib)572 void lib_destroy(lib_t lib)
573 {
574    // This is convenience function for testing: remove all
575    // files associated with a library
576 
577    assert(lib != NULL);
578 
579    close(lib->lock_fd);
580    lib->lock_fd = -1;
581 
582    DIR *d = opendir(lib->path);
583    if (d == NULL) {
584       perror("opendir");
585       return;
586    }
587 
588    char buf[PATH_MAX];
589    struct dirent *e;
590    while ((e = readdir(d))) {
591       if (e->d_name[0] != '.') {
592          checked_sprintf(buf, sizeof(buf), "%s" PATH_SEP "%s",
593                          lib->path, e->d_name);
594          if (unlink(buf) < 0)
595             perror("unlink");
596       }
597    }
598 
599    closedir(d);
600 
601    if (rmdir(lib->path) < 0)
602       perror("rmdir");
603 }
604 
lib_work(void)605 lib_t lib_work(void)
606 {
607    assert(work != NULL);
608    return work;
609 }
610 
lib_set_work(lib_t lib)611 void lib_set_work(lib_t lib)
612 {
613    work = lib;
614 }
615 
lib_path(lib_t lib)616 const char *lib_path(lib_t lib)
617 {
618    assert(lib != NULL);
619    return lib->path;
620 }
621 
lib_time_to_usecs(time_t t)622 static lib_mtime_t lib_time_to_usecs(time_t t)
623 {
624    return (lib_mtime_t)t * 1000 * 1000;
625 }
626 
lib_put(lib_t lib,tree_t unit)627 void lib_put(lib_t lib, tree_t unit)
628 {
629    struct timeval tv;
630    if (gettimeofday(&tv, NULL) != 0)
631       fatal_errno("gettimeofday");
632 
633    lib_mtime_t usecs = ((lib_mtime_t)tv.tv_sec * 1000000) + tv.tv_usec;
634    lib_put_aux(lib, unit, NULL, true, usecs);
635 }
636 
lib_stat_mtime(struct stat * st)637 static lib_mtime_t lib_stat_mtime(struct stat *st)
638 {
639    lib_mtime_t mt = lib_time_to_usecs(st->st_mtime);
640 #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
641    mt += st->st_mtimespec.tv_nsec / 1000;
642 #elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
643    mt += st->st_mtim.tv_nsec / 1000;
644 #endif
645    return mt;
646 }
647 
lib_get_aux(lib_t lib,ident_t ident)648 static lib_unit_t *lib_get_aux(lib_t lib, ident_t ident)
649 {
650    assert(lib != NULL);
651 
652    // Handle aliased library names
653    ident_t lname = ident_until(ident, '.');
654    if ((lname != NULL) && (lname != lib->name)) {
655       ident_t uname = ident_rfrom(ident, '.');
656       if (uname != NULL)
657          ident = ident_prefix(lib->name, uname, '.');
658    }
659 
660    // Search in the list of already loaded units
661    for (unsigned n = 0; n < lib->n_units; n++) {
662       if (tree_ident(lib->units[n].top) == ident)
663          return &(lib->units[n]);
664    }
665 
666    if (*(lib->path) == '\0')   // Temporary library
667       return NULL;
668 
669    assert(lib->lock_fd != -1);   // Should not be called in unit tests
670    file_read_lock(lib->lock_fd);
671 
672    // Otherwise search in the filesystem
673    DIR *d = opendir(lib->path);
674    if (d == NULL)
675       fatal("%s: %s", lib->path, strerror(errno));
676 
677    lib_unit_t *unit = NULL;
678    const char *search = istr(ident);
679    struct dirent *e;
680    while ((e = readdir(d))) {
681       if (strcmp(e->d_name, search) == 0) {
682          fbuf_t *f = lib_fbuf_open(lib, e->d_name, FBUF_IN);
683          tree_rd_ctx_t ctx = tree_read_begin(f, lib_file_path(lib, e->d_name));
684          tree_t top = tree_read(ctx);
685          fbuf_close(f);
686 
687          struct stat st;
688          if (stat(lib_file_path(lib, e->d_name), &st) < 0)
689             fatal_errno("%s", e->d_name);
690 
691          lib_mtime_t mt = lib_stat_mtime(&st);
692 
693          unit = lib_put_aux(lib, top, ctx, false, mt);
694          break;
695       }
696    }
697 
698    closedir(d);
699    file_unlock(lib->lock_fd);
700 
701    if (unit == NULL && lib_find_in_index(lib, ident) != NULL)
702       fatal("library %s corrupt: unit %s present in index but missing "
703             "on disk", istr(lib->name), istr(ident));
704 
705    return unit;
706 }
707 
lib_ensure_writable(lib_t lib)708 static void lib_ensure_writable(lib_t lib)
709 {
710    if (lib->readonly)
711       fatal("cannot write to read-only library %s", istr(lib->name));
712 }
713 
lib_load_vcode(lib_t lib,ident_t unit_name)714 bool lib_load_vcode(lib_t lib, ident_t unit_name)
715 {
716    if (lib->lock_fd != -1)
717       file_read_lock(lib->lock_fd);
718 
719    char *name LOCAL = vcode_file_name(unit_name);
720    fbuf_t *f = lib_fbuf_open(lib, name, FBUF_IN);
721    if (f != NULL) {
722       vcode_read(f);
723       fbuf_close(f);
724    }
725 
726    if (lib->lock_fd != -1)
727       file_unlock(lib->lock_fd);
728    return f != NULL;
729 }
730 
lib_save_vcode(lib_t lib,vcode_unit_t vu,ident_t unit_name)731 void lib_save_vcode(lib_t lib, vcode_unit_t vu, ident_t unit_name)
732 {
733    lib_ensure_writable(lib);
734 
735    if (lib->lock_fd != -1)
736       file_write_lock(lib->lock_fd);
737 
738    char *name LOCAL = vcode_file_name(unit_name);
739    fbuf_t *fbuf = lib_fbuf_open(lib, name, FBUF_OUT);
740    vcode_write(vu, fbuf);
741    fbuf_close(fbuf);
742 
743    if (lib->lock_fd != -1)
744       file_unlock(lib->lock_fd);
745 }
746 
lib_mtime(lib_t lib,ident_t ident)747 lib_mtime_t lib_mtime(lib_t lib, ident_t ident)
748 {
749    lib_unit_t *lu = lib_get_aux(lib, ident);
750    assert(lu != NULL);
751    return lu->mtime;
752 }
753 
lib_stat(lib_t lib,const char * name,lib_mtime_t * mt)754 bool lib_stat(lib_t lib, const char *name, lib_mtime_t *mt)
755 {
756    struct stat buf;
757    if (stat(lib_file_path(lib, name), &buf) == 0) {
758       if (mt != NULL)
759          *mt = lib_stat_mtime(&buf);
760       return true;
761    }
762    else
763       return false;
764 }
765 
lib_get(lib_t lib,ident_t ident)766 tree_t lib_get(lib_t lib, ident_t ident)
767 {
768    lib_unit_t *lu = lib_get_aux(lib, ident);
769    if (lu != NULL) {
770       if (lu->read_ctx != NULL) {
771          tree_read_end(lu->read_ctx);
772          lu->read_ctx = NULL;
773       }
774       return lu->top;
775    }
776    else
777       return NULL;
778 }
779 
lib_get_check_stale(lib_t lib,ident_t ident)780 tree_t lib_get_check_stale(lib_t lib, ident_t ident)
781 {
782    lib_unit_t *lu = lib_get_aux(lib, ident);
783    if (lu != NULL) {
784       if (lu->read_ctx != NULL) {
785          tree_read_end(lu->read_ctx);
786          lu->read_ctx = NULL;
787       }
788 
789       if (!opt_get_int("ignore-time")) {
790          const loc_t *loc = tree_loc(lu->top);
791 
792          struct stat st;
793          if (stat(istr(loc->file), &st) == 0 && lu->mtime < lib_stat_mtime(&st))
794             fatal("design unit %s is older than its source file %s and must "
795                   "be reanalysed\n(You can use the --ignore-time option to "
796                   "skip this check)", istr(ident), istr(loc->file));
797       }
798 
799       return lu->top;
800    }
801    else
802       return NULL;
803 }
804 
lib_name(lib_t lib)805 ident_t lib_name(lib_t lib)
806 {
807    assert(lib != NULL);
808    return lib->name;
809 }
810 
lib_save(lib_t lib)811 void lib_save(lib_t lib)
812 {
813    assert(lib != NULL);
814 
815    assert(lib->lock_fd != -1);   // Should not be called in unit tests
816    lib_ensure_writable(lib);
817    file_write_lock(lib->lock_fd);
818 
819    for (unsigned n = 0; n < lib->n_units; n++) {
820       if (lib->units[n].dirty) {
821          const char *name = istr(tree_ident(lib->units[n].top));
822          fbuf_t *f = lib_fbuf_open(lib, name, FBUF_OUT);
823          if (f == NULL)
824             fatal("failed to create %s in library %s", name, istr(lib->name));
825          tree_wr_ctx_t ctx = tree_write_begin(f);
826          tree_write(lib->units[n].top, ctx);
827          tree_write_end(ctx);
828          fbuf_close(f);
829 
830          lib->units[n].dirty = false;
831       }
832    }
833 
834    const char *index_path = lib_file_path(lib, "_index");
835    struct stat st;
836    if (stat(index_path, &st) == 0
837        && (lib_stat_mtime(&st) != lib->index_mtime
838            || st.st_size != lib->index_size)) {
839       // Library was updated concurrently: re-read the index while we
840       // have the lock
841       lib_read_index(lib);
842    }
843 
844    int index_sz = lib_index_size(lib);
845 
846    fbuf_t *f = lib_fbuf_open(lib, "_index", FBUF_OUT);
847    if (f == NULL)
848       fatal("failed to create library %s index", istr(lib->name));
849 
850    ident_wr_ctx_t ictx = ident_write_begin(f);
851 
852    write_u32(index_sz, f);
853    for (lib_index_t *it = lib->index; it != NULL; it = it->next) {
854       ident_write(it->name, ictx);
855       write_u16(it->kind, f);
856    }
857 
858    ident_write_end(ictx);
859    fbuf_close(f);
860 
861    if (stat(index_path, &st) != 0)
862       fatal_errno("stat: %s", index_path);
863    lib->index_mtime = lib_stat_mtime(&st);
864    lib->index_size  = st.st_size;
865 
866    file_unlock(lib->lock_fd);
867 }
868 
lib_index_kind(lib_t lib,ident_t ident)869 int lib_index_kind(lib_t lib, ident_t ident)
870 {
871    lib_index_t *it = lib_find_in_index(lib, ident);
872    return it != NULL ? it->kind : T_LAST_TREE_KIND;
873 }
874 
lib_walk_index(lib_t lib,lib_index_fn_t fn,void * context)875 void lib_walk_index(lib_t lib, lib_index_fn_t fn, void *context)
876 {
877    assert(lib != NULL);
878 
879    lib_index_t *it;
880    for (it = lib->index; it != NULL; it = it->next)
881       (*fn)(it->name, it->kind, context);
882 }
883 
lib_index_size(lib_t lib)884 unsigned lib_index_size(lib_t lib)
885 {
886    assert(lib != NULL);
887 
888    unsigned n = 0;
889    for (lib_index_t *it = lib->index; it != NULL; it = it->next)
890       n++;
891 
892    return n;
893 }
894 
lib_realpath(lib_t lib,const char * name,char * buf,size_t buflen)895 void lib_realpath(lib_t lib, const char *name, char *buf, size_t buflen)
896 {
897    assert(lib != NULL);
898 
899    if (name)
900       checked_sprintf(buf, buflen, "%s" PATH_SEP "%s", lib->path, name);
901    else
902       strncpy(buf, lib->path, buflen);
903 }
904 
lib_mkdir(lib_t lib,const char * name)905 void lib_mkdir(lib_t lib, const char *name)
906 {
907    make_dir(lib_file_path(lib, name));
908 }
909 
lib_delete(lib_t lib,const char * name)910 void lib_delete(lib_t lib, const char *name)
911 {
912    assert(lib != NULL);
913    if (remove(lib_file_path(lib, name)) != 0 && errno != ENOENT)
914       fatal_errno("remove: %s", name);
915 }
916