]> CyberLeo.Net >> Repos - FreeBSD/releng/9.1.git/blob - usr.sbin/makefs/walk.c
MFC r232197 (by phk):
[FreeBSD/releng/9.1.git] / usr.sbin / makefs / walk.c
1 /*      $NetBSD: walk.c,v 1.24 2008/12/28 21:51:46 christos Exp $       */
2
3 /*
4  * Copyright (c) 2001 Wasabi Systems, Inc.
5  * All rights reserved.
6  *
7  * Written by Luke Mewburn for Wasabi Systems, Inc.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *      This product includes software developed for the NetBSD Project by
20  *      Wasabi Systems, Inc.
21  * 4. The name of Wasabi Systems, Inc. may not be used to endorse
22  *    or promote products derived from this software without specific prior
23  *    written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL WASABI SYSTEMS, INC
29  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  */
37
38
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
41
42 #include <sys/param.h>
43
44 #include <assert.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdio.h>
48 #include <dirent.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <sys/stat.h>
53
54 #include "makefs.h"
55 #include "mtree.h"
56 #include "extern.h"
57
58 static  void     apply_specdir(const char *, NODE *, fsnode *, int);
59 static  void     apply_specentry(const char *, NODE *, fsnode *);
60 static  fsnode  *create_fsnode(const char *, const char *, const char *,
61                                struct stat *);
62 static  fsinode *link_check(fsinode *);
63
64
65 /*
66  * walk_dir --
67  *      build a tree of fsnodes from `root' and `dir', with a parent
68  *      fsnode of `parent' (which may be NULL for the root of the tree).
69  *      append the tree to a fsnode of `join' if it is not NULL.
70  *      each "level" is a directory, with the "." entry guaranteed to be
71  *      at the start of the list, and without ".." entries.
72  */
73 fsnode *
74 walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join)
75 {
76         fsnode          *first, *cur, *prev, *last;
77         DIR             *dirp;
78         struct dirent   *dent;
79         char            path[MAXPATHLEN + 1];
80         struct stat     stbuf;
81         char            *name, *rp;
82         int             dot, len;
83
84         assert(root != NULL);
85         assert(dir != NULL);
86
87         len = snprintf(path, sizeof(path), "%s/%s", root, dir);
88         if (len >= (int)sizeof(path))
89                 errx(1, "Pathname too long.");
90         if (debug & DEBUG_WALK_DIR)
91                 printf("walk_dir: %s %p\n", path, parent);
92         if ((dirp = opendir(path)) == NULL)
93                 err(1, "Can't opendir `%s'", path);
94         rp = path + strlen(root) + 1;
95         if (join != NULL) {
96                 first = cur = join;
97                 while (cur->next != NULL)
98                         cur = cur->next;
99                 prev = cur;
100         } else
101                 first = prev = NULL;
102         last = prev;
103         while ((dent = readdir(dirp)) != NULL) {
104                 name = dent->d_name;
105                 dot = 0;
106                 if (name[0] == '.')
107                         switch (name[1]) {
108                         case '\0':      /* "." */
109                                 if (join != NULL)
110                                         continue;
111                                 dot = 1;
112                                 break;
113                         case '.':       /* ".." */
114                                 if (name[2] == '\0')
115                                         continue;
116                                 /* FALLTHROUGH */
117                         default:
118                                 dot = 0;
119                         }
120                 if (debug & DEBUG_WALK_DIR_NODE)
121                         printf("scanning %s/%s/%s\n", root, dir, name);
122                 if (snprintf(path + len, sizeof(path) - len, "/%s", name) >=
123                     (int)sizeof(path) - len)
124                         errx(1, "Pathname too long.");
125                 if (lstat(path, &stbuf) == -1)
126                         err(1, "Can't lstat `%s'", path);
127 #ifdef S_ISSOCK
128                 if (S_ISSOCK(stbuf.st_mode & S_IFMT)) {
129                         if (debug & DEBUG_WALK_DIR_NODE)
130                                 printf("  skipping socket %s\n", path);
131                         continue;
132                 }
133 #endif
134
135                 if (join != NULL) {
136                         cur = join->next;
137                         for (;;) {
138                                 if (cur == NULL || strcmp(cur->name, name) == 0)
139                                         break;
140                                 if (cur == last) {
141                                         cur = NULL;
142                                         break;
143                                 }
144                                 cur = cur->next;
145                         }
146                         if (cur != NULL) {
147                                 if (S_ISDIR(cur->type) &&
148                                     S_ISDIR(stbuf.st_mode)) {
149                                         if (debug & DEBUG_WALK_DIR_NODE)
150                                                 printf("merging %s with %p\n",
151                                                     path, cur->child);
152                                         cur->child = walk_dir(root, rp, cur,
153                                             cur->child);
154                                         continue;
155                                 }
156                                 errx(1, "Can't merge %s `%s' with existing %s",
157                                     inode_type(stbuf.st_mode), path,
158                                     inode_type(cur->type));
159                         }
160                 }
161
162                 cur = create_fsnode(root, dir, name, &stbuf);
163                 cur->parent = parent;
164                 if (dot) {
165                                 /* ensure "." is at the start of the list */
166                         cur->next = first;
167                         first = cur;
168                         if (! prev)
169                                 prev = cur;
170                         cur->first = first;
171                 } else {                        /* not "." */
172                         if (prev)
173                                 prev->next = cur;
174                         prev = cur;
175                         if (!first)
176                                 first = cur;
177                         cur->first = first;
178                         if (S_ISDIR(cur->type)) {
179                                 cur->child = walk_dir(root, rp, cur, NULL);
180                                 continue;
181                         }
182                 }
183                 if (stbuf.st_nlink > 1) {
184                         fsinode *curino;
185
186                         curino = link_check(cur->inode);
187                         if (curino != NULL) {
188                                 free(cur->inode);
189                                 cur->inode = curino;
190                                 cur->inode->nlink++;
191                                 if (debug & DEBUG_WALK_DIR_LINKCHECK)
192                                         printf("link_check: found [%llu, %llu]\n",
193                                             (unsigned long long)curino->st.st_dev,
194                                             (unsigned long long)curino->st.st_ino);
195                         }
196                 }
197                 if (S_ISLNK(cur->type)) {
198                         char    slink[PATH_MAX+1];
199                         int     llen;
200
201                         llen = readlink(path, slink, sizeof(slink) - 1);
202                         if (llen == -1)
203                                 err(1, "Readlink `%s'", path);
204                         slink[llen] = '\0';
205                         if ((cur->symlink = strdup(slink)) == NULL)
206                                 err(1, "Memory allocation error");
207                 }
208         }
209         assert(first != NULL);
210         if (join == NULL)
211                 for (cur = first->next; cur != NULL; cur = cur->next)
212                         cur->first = first;
213         if (closedir(dirp) == -1)
214                 err(1, "Can't closedir `%s/%s'", root, dir);
215         return (first);
216 }
217
218 static fsnode *
219 create_fsnode(const char *root, const char *path, const char *name,
220     struct stat *stbuf)
221 {
222         fsnode *cur;
223
224         if ((cur = calloc(1, sizeof(fsnode))) == NULL ||
225             (cur->path = strdup(path)) == NULL ||
226             (cur->name = strdup(name)) == NULL ||
227             (cur->inode = calloc(1, sizeof(fsinode))) == NULL)
228                 err(1, "Memory allocation error");
229         cur->root = root;
230         cur->type = stbuf->st_mode & S_IFMT;
231         cur->inode->nlink = 1;
232         cur->inode->st = *stbuf;
233         return (cur);
234 }
235
236 /*
237  * free_fsnodes --
238  *      Removes node from tree and frees it and all of
239  *   its decendents.
240  */
241 void
242 free_fsnodes(fsnode *node)
243 {
244         fsnode  *cur, *next;
245
246         assert(node != NULL);
247
248         /* for ".", start with actual parent node */
249         if (node->first == node) {
250                 assert(node->name[0] == '.' && node->name[1] == '\0');
251                 if (node->parent) {
252                         assert(node->parent->child == node);
253                         node = node->parent;
254                 }
255         }
256
257         /* Find ourselves in our sibling list and unlink */
258         if (node->first != node) {
259                 for (cur = node->first; cur->next; cur = cur->next) {
260                         if (cur->next == node) {
261                                 cur->next = node->next;
262                                 node->next = NULL;
263                                 break;
264                         }
265                 }
266         }
267
268         for (cur = node; cur != NULL; cur = next) {
269                 next = cur->next;
270                 if (cur->child) {
271                         cur->child->parent = NULL;
272                         free_fsnodes(cur->child);
273                 }
274                 if (cur->inode->nlink-- == 1)
275                         free(cur->inode);
276                 if (cur->symlink)
277                         free(cur->symlink);
278                 free(cur->path);
279                 free(cur->name);
280                 free(cur);
281         }
282 }
283
284 /*
285  * apply_specfile --
286  *      read in the mtree(8) specfile, and apply it to the tree
287  *      at dir,parent. parameters in parent on equivalent types
288  *      will be changed to those found in specfile, and missing
289  *      entries will be added.
290  */
291 void
292 apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly)
293 {
294         struct timeval   start;
295         FILE    *fp;
296         NODE    *root;
297
298         assert(specfile != NULL);
299         assert(parent != NULL);
300
301         if (debug & DEBUG_APPLY_SPECFILE)
302                 printf("apply_specfile: %s, %s %p\n", specfile, dir, parent);
303
304                                 /* read in the specfile */
305         if ((fp = fopen(specfile, "r")) == NULL)
306                 err(1, "Can't open `%s'", specfile);
307         TIMER_START(start);
308         root = mtree_readspec(fp);
309         TIMER_RESULTS(start, "spec");
310         if (fclose(fp) == EOF)
311                 err(1, "Can't close `%s'", specfile);
312
313                                 /* perform some sanity checks */
314         if (root == NULL)
315                 errx(1, "Specfile `%s' did not contain a tree", specfile);
316         assert(strcmp(root->name, ".") == 0);
317         assert(root->type == F_DIR);
318
319                                 /* merge in the changes */
320         apply_specdir(dir, root, parent, speconly);
321
322 }
323
324 static u_int
325 nodetoino(u_int type)
326 {
327
328         switch (type) {
329         case F_BLOCK:
330                 return S_IFBLK;
331         case F_CHAR:
332                 return S_IFCHR;
333         case F_DIR:
334                 return S_IFDIR;
335         case F_FIFO:
336                 return S_IFIFO;
337         case F_FILE:
338                 return S_IFREG;
339         case F_LINK:
340                 return S_IFLNK;
341         case F_SOCK:
342                 return S_IFSOCK;
343         default:
344                 printf("unknown type %d", type);
345                 abort();
346         }
347         /* NOTREACHED */
348 }
349
350
351 static void
352 apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly)
353 {
354         char     path[MAXPATHLEN + 1];
355         NODE    *curnode;
356         fsnode  *curfsnode;
357
358         assert(specnode != NULL);
359         assert(dirnode != NULL);
360
361         if (debug & DEBUG_APPLY_SPECFILE)
362                 printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode);
363
364         if (specnode->type != F_DIR)
365                 errx(1, "Specfile node `%s/%s' is not a directory",
366                     dir, specnode->name);
367         if (dirnode->type != S_IFDIR)
368                 errx(1, "Directory node `%s/%s' is not a directory",
369                     dir, dirnode->name);
370
371         apply_specentry(dir, specnode, dirnode);
372
373         /* Remove any filesystem nodes not found in specfile */
374         /* XXX inefficient.  This is O^2 in each dir and it would
375          * have been better never to have walked this part of the tree
376          * to begin with
377          */
378         if (speconly) {
379                 fsnode *next;
380                 assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0');
381                 for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) {
382                         next = curfsnode->next;
383                         for (curnode = specnode->child; curnode != NULL;
384                              curnode = curnode->next) {
385                                 if (strcmp(curnode->name, curfsnode->name) == 0)
386                                         break;
387                         }
388                         if (curnode == NULL) {
389                                 if (debug & DEBUG_APPLY_SPECONLY) {
390                                         printf("apply_specdir: trimming %s/%s %p\n", dir, curfsnode->name, curfsnode);
391                                 }
392                                 free_fsnodes(curfsnode);
393                         }
394                 }
395         }
396
397                         /* now walk specnode->child matching up with dirnode */
398         for (curnode = specnode->child; curnode != NULL;
399             curnode = curnode->next) {
400                 if (debug & DEBUG_APPLY_SPECENTRY)
401                         printf("apply_specdir:  spec %s\n",
402                             curnode->name);
403                 for (curfsnode = dirnode->next; curfsnode != NULL;
404                     curfsnode = curfsnode->next) {
405 #if 0   /* too verbose for now */
406                         if (debug & DEBUG_APPLY_SPECENTRY)
407                                 printf("apply_specdir:  dirent %s\n",
408                                     curfsnode->name);
409 #endif
410                         if (strcmp(curnode->name, curfsnode->name) == 0)
411                                 break;
412                 }
413                 if (snprintf(path, sizeof(path), "%s/%s",
414                     dir, curnode->name) >= sizeof(path))
415                         errx(1, "Pathname too long.");
416                 if (curfsnode == NULL) {        /* need new entry */
417                         struct stat     stbuf;
418
419                                             /*
420                                              * don't add optional spec entries
421                                              * that lack an existing fs entry
422                                              */
423                         if ((curnode->flags & F_OPT) &&
424                             lstat(path, &stbuf) == -1)
425                                         continue;
426
427                                         /* check that enough info is provided */
428 #define NODETEST(t, m)                                                  \
429                         if (!(t))                                       \
430                                 errx(1, "`%s': %s not provided", path, m)
431                         NODETEST(curnode->flags & F_TYPE, "type");
432                         NODETEST(curnode->flags & F_MODE, "mode");
433                                 /* XXX: require F_TIME ? */
434                         NODETEST(curnode->flags & F_GID ||
435                             curnode->flags & F_GNAME, "group");
436                         NODETEST(curnode->flags & F_UID ||
437                             curnode->flags & F_UNAME, "user");
438 /*                      if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
439                                 NODETEST(curnode->flags & F_DEV,
440                                     "device number");*/
441 #undef NODETEST
442
443                         if (debug & DEBUG_APPLY_SPECFILE)
444                                 printf("apply_specdir: adding %s\n",
445                                     curnode->name);
446                                         /* build minimal fsnode */
447                         memset(&stbuf, 0, sizeof(stbuf));
448                         stbuf.st_mode = nodetoino(curnode->type);
449                         stbuf.st_nlink = 1;
450                         stbuf.st_mtime = stbuf.st_atime =
451                             stbuf.st_ctime = start_time.tv_sec;
452 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
453                         stbuf.st_mtimensec = stbuf.st_atimensec =
454                             stbuf.st_ctimensec = start_time.tv_nsec;
455 #endif
456                         curfsnode = create_fsnode(".", ".", curnode->name,
457                             &stbuf);
458                         curfsnode->parent = dirnode->parent;
459                         curfsnode->first = dirnode;
460                         curfsnode->next = dirnode->next;
461                         dirnode->next = curfsnode;
462                         if (curfsnode->type == S_IFDIR) {
463                                         /* for dirs, make "." entry as well */
464                                 curfsnode->child = create_fsnode(".", ".", ".",
465                                     &stbuf);
466                                 curfsnode->child->parent = curfsnode;
467                                 curfsnode->child->first = curfsnode->child;
468                         }
469                         if (curfsnode->type == S_IFLNK) {
470                                 assert(curnode->slink != NULL);
471                                         /* for symlinks, copy the target */
472                                 if ((curfsnode->symlink =
473                                     strdup(curnode->slink)) == NULL)
474                                         err(1, "Memory allocation error");
475                         }
476                 }
477                 apply_specentry(dir, curnode, curfsnode);
478                 if (curnode->type == F_DIR) {
479                         if (curfsnode->type != S_IFDIR)
480                                 errx(1, "`%s' is not a directory", path);
481                         assert (curfsnode->child != NULL);
482                         apply_specdir(path, curnode, curfsnode->child, speconly);
483                 }
484         }
485 }
486
487 static void
488 apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
489 {
490
491         assert(specnode != NULL);
492         assert(dirnode != NULL);
493
494         if (nodetoino(specnode->type) != dirnode->type)
495                 errx(1, "`%s/%s' type mismatch: specfile %s, tree %s",
496                     dir, specnode->name, inode_type(nodetoino(specnode->type)),
497                     inode_type(dirnode->type));
498
499         if (debug & DEBUG_APPLY_SPECENTRY)
500                 printf("apply_specentry: %s/%s\n", dir, dirnode->name);
501
502 #define ASEPRINT(t, b, o, n) \
503                 if (debug & DEBUG_APPLY_SPECENTRY) \
504                         printf("\t\t\tchanging %s from " b " to " b "\n", \
505                             t, o, n)
506
507         if (specnode->flags & (F_GID | F_GNAME)) {
508                 ASEPRINT("gid", "%d",
509                     dirnode->inode->st.st_gid, specnode->st_gid);
510                 dirnode->inode->st.st_gid = specnode->st_gid;
511         }
512         if (specnode->flags & F_MODE) {
513                 ASEPRINT("mode", "%#o",
514                     dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode);
515                 dirnode->inode->st.st_mode &= ~ALLPERMS;
516                 dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS);
517         }
518                 /* XXX: ignoring F_NLINK for now */
519         if (specnode->flags & F_SIZE) {
520                 ASEPRINT("size", "%lld",
521                     (long long)dirnode->inode->st.st_size,
522                     (long long)specnode->st_size);
523                 dirnode->inode->st.st_size = specnode->st_size;
524         }
525         if (specnode->flags & F_SLINK) {
526                 assert(dirnode->symlink != NULL);
527                 assert(specnode->slink != NULL);
528                 ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
529                 free(dirnode->symlink);
530                 if ((dirnode->symlink = strdup(specnode->slink)) == NULL)
531                         err(1, "Memory allocation error");
532         }
533         if (specnode->flags & F_TIME) {
534                 ASEPRINT("time", "%ld",
535                     (long)dirnode->inode->st.st_mtime,
536                     (long)specnode->st_mtimespec.tv_sec);
537                 dirnode->inode->st.st_mtime =           specnode->st_mtimespec.tv_sec;
538                 dirnode->inode->st.st_atime =           specnode->st_mtimespec.tv_sec;
539                 dirnode->inode->st.st_ctime =           start_time.tv_sec;
540 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
541                 dirnode->inode->st.st_mtimensec =       specnode->st_mtimespec.tv_nsec;
542                 dirnode->inode->st.st_atimensec =       specnode->st_mtimespec.tv_nsec;
543                 dirnode->inode->st.st_ctimensec =       start_time.tv_nsec;
544 #endif
545         }
546         if (specnode->flags & (F_UID | F_UNAME)) {
547                 ASEPRINT("uid", "%d",
548                     dirnode->inode->st.st_uid, specnode->st_uid);
549                 dirnode->inode->st.st_uid = specnode->st_uid;
550         }
551 #if HAVE_STRUCT_STAT_ST_FLAGS
552         if (specnode->flags & F_FLAGS) {
553                 ASEPRINT("flags", "%#lX",
554                     (unsigned long)dirnode->inode->st.st_flags,
555                     (unsigned long)specnode->st_flags);
556                 dirnode->inode->st.st_flags = specnode->st_flags;
557         }
558 #endif
559 /*      if (specnode->flags & F_DEV) {
560                 ASEPRINT("rdev", "%#llx",
561                     (unsigned long long)dirnode->inode->st.st_rdev,
562                     (unsigned long long)specnode->st_rdev);
563                 dirnode->inode->st.st_rdev = specnode->st_rdev;
564         }*/
565 #undef ASEPRINT
566
567         dirnode->flags |= FSNODE_F_HASSPEC;
568 }
569
570
571 /*
572  * dump_fsnodes --
573  *      dump the fsnodes from `cur'
574  */
575 void
576 dump_fsnodes(fsnode *root)
577 {
578         fsnode  *cur;
579         char    path[MAXPATHLEN + 1];
580
581         printf("dump_fsnodes: %s %p\n", root->path, root);
582         for (cur = root; cur != NULL; cur = cur->next) {
583                 if (snprintf(path, sizeof(path), "%s/%s", cur->path,
584                     cur->name) >= (int)sizeof(path))
585                         errx(1, "Pathname too long.");
586
587                 if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
588                         printf("cur=%8p parent=%8p first=%8p ",
589                             cur, cur->parent, cur->first);
590                 printf("%7s: %s", inode_type(cur->type), path);
591                 if (S_ISLNK(cur->type)) {
592                         assert(cur->symlink != NULL);
593                         printf(" -> %s", cur->symlink);
594                 } else {
595                         assert (cur->symlink == NULL);
596                 }
597                 if (cur->inode->nlink > 1)
598                         printf(", nlinks=%d", cur->inode->nlink);
599                 putchar('\n');
600
601                 if (cur->child) {
602                         assert (cur->type == S_IFDIR);
603                         dump_fsnodes(cur->child);
604                 }
605         }
606         printf("dump_fsnodes: finished %s/%s\n", root->path, root->name);
607 }
608
609
610 /*
611  * inode_type --
612  *      for a given inode type `mode', return a descriptive string.
613  *      for most cases, uses inotype() from mtree/misc.c
614  */
615 const char *
616 inode_type(mode_t mode)
617 {
618         if (S_ISREG(mode))
619                 return ("file");
620         if (S_ISLNK(mode))
621                 return ("symlink");
622         if (S_ISDIR(mode))
623                 return ("dir");
624         if (S_ISLNK(mode))
625                 return ("link");
626         if (S_ISFIFO(mode))
627                 return ("fifo");
628         if (S_ISSOCK(mode))
629                 return ("socket");
630         /* XXX should not happen but handle them */
631         if (S_ISCHR(mode))
632                 return ("char");
633         if (S_ISBLK(mode))
634                 return ("block");
635         return ("unknown");
636 }
637
638
639 /*
640  * link_check --
641  *      return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
642  *      otherwise add `entry' to table and return NULL
643  */
644 /* This was borrowed from du.c and tweaked to keep an fsnode 
645  * pointer instead. -- dbj@netbsd.org
646  */
647 static fsinode *
648 link_check(fsinode *entry)
649 {
650         static struct entry {
651                 fsinode *data;
652         } *htable;
653         static int htshift;  /* log(allocated size) */
654         static int htmask;   /* allocated size - 1 */
655         static int htused;   /* 2*number of insertions */
656         int h, h2;
657         uint64_t tmp;
658         /* this constant is (1<<64)/((1+sqrt(5))/2)
659          * aka (word size)/(golden ratio)
660          */
661         const uint64_t HTCONST = 11400714819323198485ULL;
662         const int HTBITS = 64;
663         
664         /* Never store zero in hashtable */
665         assert(entry);
666
667         /* Extend hash table if necessary, keep load under 0.5 */
668         if (htused<<1 >= htmask) {
669                 struct entry *ohtable;
670
671                 if (!htable)
672                         htshift = 10;   /* starting hashtable size */
673                 else
674                         htshift++;   /* exponential hashtable growth */
675
676                 htmask  = (1 << htshift) - 1;
677                 htused = 0;
678
679                 ohtable = htable;
680                 htable = calloc(htmask+1, sizeof(*htable));
681                 if (!htable)
682                         err(1, "Memory allocation error");
683
684                 /* populate newly allocated hashtable */
685                 if (ohtable) {
686                         int i;
687                         for (i = 0; i <= htmask>>1; i++)
688                                 if (ohtable[i].data)
689                                         link_check(ohtable[i].data);
690                         free(ohtable);
691                 }
692         }
693
694         /* multiplicative hashing */
695         tmp = entry->st.st_dev;
696         tmp <<= HTBITS>>1;
697         tmp |=  entry->st.st_ino;
698         tmp *= HTCONST;
699         h  = tmp >> (HTBITS - htshift);
700         h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
701         
702         /* open address hashtable search with double hash probing */
703         while (htable[h].data) {
704                 if ((htable[h].data->st.st_ino == entry->st.st_ino) &&
705                     (htable[h].data->st.st_dev == entry->st.st_dev)) {
706                         return htable[h].data;
707                 }
708                 h = (h + h2) & htmask;
709         }
710
711         /* Insert the current entry into hashtable */
712         htable[h].data = entry;
713         htused++;
714         return NULL;
715 }