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