1 /* $OpenBSD: file.c,v 1.33 2004/08/31 11:17:02 joris Exp $ */ 2 /* 3 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. The name of the author may not be used to endorse or promote products 13 * derived from this software without specific prior written permission. 14 * 15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 18 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include <sys/types.h> 28 #include <sys/queue.h> 29 #include <sys/stat.h> 30 31 #include <pwd.h> 32 #include <errno.h> 33 #include <stdio.h> 34 #include <fcntl.h> 35 #include <dirent.h> 36 #include <stdlib.h> 37 #include <unistd.h> 38 #include <string.h> 39 #include <fnmatch.h> 40 41 #include "cvs.h" 42 #include "log.h" 43 #include "file.h" 44 45 46 #define CVS_IGN_STATIC 0x01 /* pattern is static, no need to glob */ 47 48 #define CVS_CHAR_ISMETA(c) ((c == '*') || (c == '?') || (c == '[')) 49 50 51 /* ignore pattern */ 52 struct cvs_ignpat { 53 char ip_pat[MAXNAMLEN]; 54 int ip_flags; 55 TAILQ_ENTRY (cvs_ignpat) ip_list; 56 }; 57 58 59 /* 60 * Standard patterns to ignore. 61 */ 62 63 static const char *cvs_ign_std[] = { 64 ".", 65 "..", 66 "*.o", 67 "*.so", 68 "*.bak", 69 "*.orig", 70 "*.rej", 71 "*.exe", 72 "*.depend", 73 "CVS", 74 "core", 75 ".#*", 76 #ifdef OLD_SMELLY_CRUFT 77 "RCSLOG", 78 "tags", 79 "TAGS", 80 "RCS", 81 "SCCS", 82 "#*", 83 ",*", 84 #endif 85 }; 86 87 88 /* 89 * Entries in the CVS/Entries file with a revision of '0' have only been 90 * added. Compare against this revision to see if this is the case 91 */ 92 static RCSNUM *cvs_addedrev; 93 94 95 TAILQ_HEAD(, cvs_ignpat) cvs_ign_pats; 96 97 98 static int cvs_file_getdir (CVSFILE *, int); 99 static void cvs_file_freedir (struct cvs_dir *); 100 static int cvs_file_sort (struct cvs_flist *, u_int); 101 static int cvs_file_cmp (const void *, const void *); 102 static int cvs_file_cmpname (const char *, const char *); 103 static CVSFILE* cvs_file_alloc (const char *, u_int); 104 static CVSFILE* cvs_file_lget (const char *, int, CVSFILE *); 105 106 107 108 /* 109 * cvs_file_init() 110 * 111 */ 112 113 int 114 cvs_file_init(void) 115 { 116 int i; 117 size_t len; 118 char path[MAXPATHLEN], buf[MAXNAMLEN]; 119 FILE *ifp; 120 struct passwd *pwd; 121 122 TAILQ_INIT(&cvs_ign_pats); 123 124 cvs_addedrev = rcsnum_alloc(); 125 rcsnum_aton("0", NULL, cvs_addedrev); 126 127 /* standard patterns to ignore */ 128 for (i = 0; i < (int)(sizeof(cvs_ign_std)/sizeof(char *)); i++) 129 cvs_file_ignore(cvs_ign_std[i]); 130 131 /* read the cvsignore file in the user's home directory, if any */ 132 pwd = getpwuid(getuid()); 133 if (pwd != NULL) { 134 snprintf(path, sizeof(path), "%s/.cvsignore", pwd->pw_dir); 135 ifp = fopen(path, "r"); 136 if (ifp == NULL) { 137 if (errno != ENOENT) 138 cvs_log(LP_ERRNO, "failed to open `%s'", path); 139 } 140 else { 141 while (fgets(buf, sizeof(buf), ifp) != NULL) { 142 len = strlen(buf); 143 if (len == 0) 144 continue; 145 if (buf[len - 1] != '\n') { 146 cvs_log(LP_ERR, "line too long in `%s'", 147 path); 148 } 149 buf[--len] = '\0'; 150 cvs_file_ignore(buf); 151 } 152 (void)fclose(ifp); 153 } 154 } 155 156 return (0); 157 } 158 159 160 /* 161 * cvs_file_ignore() 162 * 163 * Add the pattern <pat> to the list of patterns for files to ignore. 164 * Returns 0 on success, or -1 on failure. 165 */ 166 167 int 168 cvs_file_ignore(const char *pat) 169 { 170 char *cp; 171 struct cvs_ignpat *ip; 172 173 ip = (struct cvs_ignpat *)malloc(sizeof(*ip)); 174 if (ip == NULL) { 175 cvs_log(LP_ERR, "failed to allocate space for ignore pattern"); 176 return (-1); 177 } 178 179 strlcpy(ip->ip_pat, pat, sizeof(ip->ip_pat)); 180 181 /* check if we will need globbing for that pattern */ 182 ip->ip_flags = CVS_IGN_STATIC; 183 for (cp = ip->ip_pat; *cp != '\0'; cp++) { 184 if (CVS_CHAR_ISMETA(*cp)) { 185 ip->ip_flags &= ~CVS_IGN_STATIC; 186 break; 187 } 188 } 189 190 TAILQ_INSERT_TAIL(&cvs_ign_pats, ip, ip_list); 191 192 return (0); 193 } 194 195 196 /* 197 * cvs_file_chkign() 198 * 199 * Returns 1 if the filename <file> is matched by one of the ignore 200 * patterns, or 0 otherwise. 201 */ 202 203 int 204 cvs_file_chkign(const char *file) 205 { 206 int flags; 207 struct cvs_ignpat *ip; 208 209 flags = FNM_PERIOD; 210 if (cvs_nocase) 211 flags |= FNM_CASEFOLD; 212 213 TAILQ_FOREACH(ip, &cvs_ign_pats, ip_list) { 214 if (ip->ip_flags & CVS_IGN_STATIC) { 215 if (cvs_file_cmpname(file, ip->ip_pat) == 0) 216 return (1); 217 } 218 else if (fnmatch(ip->ip_pat, file, flags) == 0) 219 return (1); 220 } 221 222 return (0); 223 } 224 225 226 /* 227 * cvs_file_create() 228 * 229 * Create a new file whose path is specified in <path> and of type <type>. 230 * If the type is DT_DIR, the CVS administrative repository and files will be 231 * created. 232 * Returns the created file on success, or NULL on failure. 233 */ 234 235 CVSFILE* 236 cvs_file_create(const char *path, u_int type, mode_t mode) 237 { 238 int fd; 239 CVSFILE *cfp; 240 241 cfp = cvs_file_alloc(path, type); 242 if (cfp == NULL) 243 return (NULL); 244 245 cfp->cf_type = type; 246 cfp->cf_mode = mode; 247 cfp->cf_ddat->cd_root = cvsroot_get(path); 248 cfp->cf_ddat->cd_repo = strdup(cfp->cf_path); 249 250 if (cfp->cf_ddat->cd_repo == NULL) { 251 cvs_file_free(cfp); 252 return (NULL); 253 } 254 255 if (type == DT_DIR) { 256 if ((mkdir(path, mode) == -1) || (cvs_mkadmin(cfp, mode) < 0)) { 257 cvs_file_free(cfp); 258 return (NULL); 259 } 260 261 cfp->cf_ddat->cd_ent = cvs_ent_open(path, O_RDWR); 262 } 263 else { 264 fd = open(path, O_WRONLY|O_CREAT|O_EXCL, mode); 265 if (fd == -1) { 266 cvs_file_free(cfp); 267 return (NULL); 268 } 269 (void)close(fd); 270 } 271 272 return (cfp); 273 } 274 275 276 /* 277 * cvs_file_get() 278 * 279 * Load a cvs_file structure with all the information pertaining to the file 280 * <path>. 281 * The <flags> parameter specifies various flags that alter the behaviour of 282 * the function. The CF_RECURSE flag causes the function to recursively load 283 * subdirectories when <path> is a directory. 284 * The CF_SORT flag causes the files to be sorted in alphabetical order upon 285 * loading. The special case of "." as a path specification generates 286 * recursion for a single level and is equivalent to calling cvs_file_get() on 287 * all files of that directory. 288 * Returns a pointer to the cvs file structure, which must later be freed 289 * with cvs_file_free(). 290 */ 291 292 CVSFILE* 293 cvs_file_get(const char *path, int flags) 294 { 295 return cvs_file_lget(path, flags, NULL); 296 } 297 298 299 /* 300 * cvs_file_getspec() 301 * 302 * Load a specific set of files whose paths are given in the vector <fspec>, 303 * whose size is given in <fsn>. 304 * Returns a pointer to the lowest common subdirectory to all specified 305 * files. 306 */ 307 308 CVSFILE* 309 cvs_file_getspec(char **fspec, int fsn, int flags) 310 { 311 int i; 312 char *sp, *np, pcopy[MAXPATHLEN]; 313 CVSFILE *base, *cf, *nf; 314 315 base = cvs_file_get(".", 0); 316 if (base == NULL) 317 return (NULL); 318 319 for (i = 0; i < fsn; i++) { 320 strlcpy(pcopy, fspec[i], sizeof(pcopy)); 321 cf = base; 322 sp = pcopy; 323 324 do { 325 np = strchr(sp, '/'); 326 if (np != NULL) 327 *np = '\0'; 328 nf = cvs_file_find(cf, sp); 329 if (nf == NULL) { 330 nf = cvs_file_lget(pcopy, 0, cf); 331 if (nf == NULL) { 332 cvs_file_free(base); 333 return (NULL); 334 } 335 336 cvs_file_attach(cf, nf); 337 } 338 339 if (np != NULL) { 340 *np = '/'; 341 sp = np + 1; 342 } 343 344 cf = nf; 345 } while (np != NULL); 346 } 347 348 return (base); 349 } 350 351 352 /* 353 * cvs_file_find() 354 * 355 * Find the pointer to a CVS file entry within the file hierarchy <hier>. 356 * The file's pathname <path> must be relative to the base of <hier>. 357 * Returns the entry on success, or NULL on failure. 358 */ 359 360 CVSFILE* 361 cvs_file_find(CVSFILE *hier, const char *path) 362 { 363 char *pp, *sp, pbuf[MAXPATHLEN]; 364 CVSFILE *sf, *cf; 365 366 strlcpy(pbuf, path, sizeof(pbuf)); 367 368 cf = hier; 369 pp = pbuf; 370 do { 371 sp = strchr(pp, '/'); 372 if (sp != NULL) 373 *(sp++) = '\0'; 374 375 /* special case */ 376 if (*pp == '.') { 377 if ((*(pp + 1) == '.') && (*(pp + 2) == '\0')) { 378 /* request to go back to parent */ 379 if (cf->cf_parent == NULL) { 380 cvs_log(LP_NOTICE, 381 "path %s goes back too far", path); 382 return (NULL); 383 } 384 cf = cf->cf_parent; 385 continue; 386 } 387 else if (*(pp + 1) == '\0') 388 continue; 389 } 390 391 TAILQ_FOREACH(sf, &(cf->cf_ddat->cd_files), cf_list) 392 if (cvs_file_cmpname(pp, sf->cf_name) == 0) 393 break; 394 if (sf == NULL) 395 return (NULL); 396 397 cf = sf; 398 pp = sp; 399 } while (sp != NULL); 400 401 return (cf); 402 } 403 404 405 /* 406 * cvs_file_attach() 407 * 408 * Attach the file <file> as one of the children of parent <parent>, which 409 * has to be a file of type DT_DIR. 410 * Returns 0 on success, or -1 on failure. 411 */ 412 413 int 414 cvs_file_attach(CVSFILE *parent, CVSFILE *file) 415 { 416 struct cvs_dir *dp; 417 418 if (parent->cf_type != DT_DIR) 419 return (-1); 420 421 dp = parent->cf_ddat; 422 423 TAILQ_INSERT_TAIL(&(dp->cd_files), file, cf_list); 424 dp->cd_nfiles++; 425 file->cf_parent = parent; 426 427 return (0); 428 } 429 430 431 /* 432 * cvs_file_getdir() 433 * 434 * Get a cvs directory structure for the directory whose path is <dir>. 435 * This function should not free the directory information on error, as this 436 * is performed by cvs_file_free(). 437 */ 438 439 static int 440 cvs_file_getdir(CVSFILE *cf, int flags) 441 { 442 int ret, fd; 443 u_int ndirs; 444 long base; 445 void *dp, *ep; 446 char fbuf[2048], pbuf[MAXPATHLEN]; 447 struct dirent *ent; 448 CVSFILE *cfp; 449 struct stat st; 450 struct cvs_dir *cdp; 451 struct cvs_flist dirs; 452 453 ndirs = 0; 454 TAILQ_INIT(&dirs); 455 cdp = cf->cf_ddat; 456 457 if (cf->cf_cvstat != CVS_FST_UNKNOWN) { 458 cdp->cd_root = cvsroot_get(cf->cf_path); 459 if (cdp->cd_root == NULL) 460 return (-1); 461 462 if (flags & CF_MKADMIN) 463 cvs_mkadmin(cf, 0755); 464 465 /* if the CVS administrative directory exists, load the info */ 466 snprintf(pbuf, sizeof(pbuf), "%s/" CVS_PATH_CVSDIR, 467 cf->cf_path); 468 if ((stat(pbuf, &st) == 0) && S_ISDIR(st.st_mode)) { 469 if (cvs_readrepo(cf->cf_path, pbuf, 470 sizeof(pbuf)) == 0) { 471 cdp->cd_repo = strdup(pbuf); 472 if (cdp->cd_repo == NULL) { 473 cvs_log(LP_ERRNO, 474 "failed to dup repository string"); 475 return (-1); 476 } 477 } 478 479 cdp->cd_ent = cvs_ent_open(cf->cf_path, O_RDWR); 480 } 481 } 482 483 if (!(flags & CF_RECURSE) || (cf->cf_cvstat == CVS_FST_UNKNOWN)) 484 return (0); 485 486 fd = open(cf->cf_path, O_RDONLY); 487 if (fd == -1) { 488 cvs_log(LP_ERRNO, "failed to open `%s'", cf->cf_path); 489 return (-1); 490 } 491 492 do { 493 ret = getdirentries(fd, fbuf, sizeof(fbuf), &base); 494 if (ret == -1) { 495 cvs_log(LP_ERRNO, "failed to get directory entries"); 496 (void)close(fd); 497 return (-1); 498 } 499 500 dp = fbuf; 501 ep = fbuf + (size_t)ret; 502 while (dp < ep) { 503 ent = (struct dirent *)dp; 504 dp += ent->d_reclen; 505 if (ent->d_fileno == 0) 506 continue; 507 508 if ((flags & CF_IGNORE) && cvs_file_chkign(ent->d_name)) 509 continue; 510 511 if ((flags & CF_NOSYMS) && (ent->d_type == DT_LNK)) 512 continue; 513 514 snprintf(pbuf, sizeof(pbuf), "%s/%s", 515 cf->cf_path, ent->d_name); 516 cfp = cvs_file_lget(pbuf, flags, cf); 517 if (cfp != NULL) { 518 if (cfp->cf_type == DT_DIR) { 519 TAILQ_INSERT_TAIL(&dirs, cfp, cf_list); 520 ndirs++; 521 } 522 else { 523 TAILQ_INSERT_TAIL(&(cdp->cd_files), cfp, 524 cf_list); 525 cdp->cd_nfiles++; 526 } 527 } 528 } 529 } while (ret > 0); 530 531 if (flags & CF_SORT) { 532 cvs_file_sort(&(cdp->cd_files), cdp->cd_nfiles); 533 cvs_file_sort(&dirs, ndirs); 534 } 535 536 while (!TAILQ_EMPTY(&dirs)) { 537 cfp = TAILQ_FIRST(&dirs); 538 TAILQ_REMOVE(&dirs, cfp, cf_list); 539 TAILQ_INSERT_TAIL(&(cdp->cd_files), cfp, cf_list); 540 } 541 cdp->cd_nfiles += ndirs; 542 543 (void)close(fd); 544 545 return (0); 546 } 547 548 549 /* 550 * cvs_file_free() 551 * 552 * Free a cvs_file structure and its contents. 553 */ 554 555 void 556 cvs_file_free(CVSFILE *cf) 557 { 558 if (cf->cf_path != NULL) 559 free(cf->cf_path); 560 if (cf->cf_ddat != NULL) 561 cvs_file_freedir(cf->cf_ddat); 562 free(cf); 563 } 564 565 566 /* 567 * cvs_file_examine() 568 * 569 * Examine the contents of the CVS file structure <cf> with the function 570 * <exam>. The function is called for all subdirectories and files of the 571 * root file. 572 */ 573 574 int 575 cvs_file_examine(CVSFILE *cf, int (*exam)(CVSFILE *, void *), void *arg) 576 { 577 int ret; 578 CVSFILE *fp; 579 580 if (cf->cf_type == DT_DIR) { 581 ret = (*exam)(cf, arg); 582 TAILQ_FOREACH(fp, &(cf->cf_ddat->cd_files), cf_list) { 583 ret = cvs_file_examine(fp, exam, arg); 584 if (ret == -1) 585 break; 586 } 587 } 588 else 589 ret = (*exam)(cf, arg); 590 591 return (ret); 592 } 593 594 595 /* 596 * cvs_file_freedir() 597 * 598 * Free a cvs_dir structure and its contents. 599 */ 600 601 static void 602 cvs_file_freedir(struct cvs_dir *cd) 603 { 604 CVSFILE *cfp; 605 606 if (cd->cd_root != NULL) 607 cvsroot_free(cd->cd_root); 608 if (cd->cd_repo != NULL) 609 free(cd->cd_repo); 610 611 if (cd->cd_ent != NULL) 612 cvs_ent_close(cd->cd_ent); 613 614 while (!TAILQ_EMPTY(&(cd->cd_files))) { 615 cfp = TAILQ_FIRST(&(cd->cd_files)); 616 TAILQ_REMOVE(&(cd->cd_files), cfp, cf_list); 617 cvs_file_free(cfp); 618 } 619 } 620 621 622 /* 623 * cvs_file_sort() 624 * 625 * Sort a list of cvs file structures according to their filename. The list 626 * <flp> is modified according to the sorting algorithm. The number of files 627 * in the list must be given by <nfiles>. 628 * Returns 0 on success, or -1 on failure. 629 */ 630 631 static int 632 cvs_file_sort(struct cvs_flist *flp, u_int nfiles) 633 { 634 int i; 635 size_t nb; 636 CVSFILE *cf, **cfvec; 637 638 cfvec = (CVSFILE **)calloc(nfiles, sizeof(CVSFILE *)); 639 if (cfvec == NULL) { 640 cvs_log(LP_ERRNO, "failed to allocate sorting vector"); 641 return (-1); 642 } 643 644 i = 0; 645 TAILQ_FOREACH(cf, flp, cf_list) { 646 if (i == (int)nfiles) { 647 cvs_log(LP_WARN, "too many files to sort"); 648 /* rebuild the list and abort sorting */ 649 while (--i >= 0) 650 TAILQ_INSERT_HEAD(flp, cfvec[i], cf_list); 651 free(cfvec); 652 return (-1); 653 } 654 cfvec[i++] = cf; 655 656 /* now unlink it from the list, 657 * we'll put it back in order later 658 */ 659 TAILQ_REMOVE(flp, cf, cf_list); 660 } 661 662 /* clear the list just in case */ 663 TAILQ_INIT(flp); 664 nb = (size_t)i; 665 666 heapsort(cfvec, nb, sizeof(cf), cvs_file_cmp); 667 668 /* rebuild the list from the bottom up */ 669 for (i = (int)nb - 1; i >= 0; i--) 670 TAILQ_INSERT_HEAD(flp, cfvec[i], cf_list); 671 672 free(cfvec); 673 return (0); 674 } 675 676 677 static int 678 cvs_file_cmp(const void *f1, const void *f2) 679 { 680 CVSFILE *cf1, *cf2; 681 cf1 = *(CVSFILE **)f1; 682 cf2 = *(CVSFILE **)f2; 683 return cvs_file_cmpname(cf1->cf_name, cf2->cf_name); 684 } 685 686 687 CVSFILE* 688 cvs_file_alloc(const char *path, u_int type) 689 { 690 size_t len; 691 char pbuf[MAXPATHLEN]; 692 CVSFILE *cfp; 693 struct cvs_dir *ddat; 694 695 cfp = (CVSFILE *)malloc(sizeof(*cfp)); 696 if (cfp == NULL) { 697 cvs_log(LP_ERRNO, "failed to allocate CVS file data"); 698 return (NULL); 699 } 700 memset(cfp, 0, sizeof(*cfp)); 701 702 /* ditch trailing slashes */ 703 strlcpy(pbuf, path, sizeof(pbuf)); 704 len = strlen(pbuf); 705 while (pbuf[len - 1] == '/') 706 pbuf[--len] = '\0'; 707 708 cfp->cf_path = strdup(pbuf); 709 if (cfp->cf_path == NULL) { 710 free(cfp); 711 return (NULL); 712 } 713 714 cfp->cf_name = strrchr(cfp->cf_path, '/'); 715 if (cfp->cf_name == NULL) 716 cfp->cf_name = cfp->cf_path; 717 else 718 cfp->cf_name++; 719 720 cfp->cf_type = type; 721 cfp->cf_cvstat = CVS_FST_UNKNOWN; 722 723 if (type == DT_DIR) { 724 ddat = (struct cvs_dir *)malloc(sizeof(*ddat)); 725 if (ddat == NULL) { 726 cvs_file_free(cfp); 727 return (NULL); 728 } 729 memset(ddat, 0, sizeof(*ddat)); 730 TAILQ_INIT(&(ddat->cd_files)); 731 cfp->cf_ddat = ddat; 732 } 733 return (cfp); 734 } 735 736 737 /* 738 * cvs_file_lget() 739 * 740 * Get the file and link it with the parent right away. 741 * Returns a pointer to the created file structure on success, or NULL on 742 * failure. 743 */ 744 745 static CVSFILE* 746 cvs_file_lget(const char *path, int flags, CVSFILE *parent) 747 { 748 int cwd; 749 struct stat st; 750 CVSFILE *cfp; 751 struct cvs_ent *ent; 752 753 ent = NULL; 754 755 if (strcmp(path, ".") == 0) 756 cwd = 1; 757 else 758 cwd = 0; 759 760 if (stat(path, &st) == -1) { 761 cvs_log(LP_ERRNO, "failed to stat %s", path); 762 return (NULL); 763 } 764 765 cfp = cvs_file_alloc(path, IFTODT(st.st_mode)); 766 if (cfp == NULL) { 767 cvs_log(LP_ERRNO, "failed to allocate CVS file data"); 768 return (NULL); 769 } 770 cfp->cf_parent = parent; 771 cfp->cf_mode = st.st_mode & ACCESSPERMS; 772 cfp->cf_mtime = st.st_mtime; 773 774 if ((parent != NULL) && (CVS_DIR_ENTRIES(parent) != NULL)) { 775 ent = cvs_ent_get(CVS_DIR_ENTRIES(parent), cfp->cf_name); 776 } 777 778 if (ent == NULL) { 779 cfp->cf_cvstat = (cwd == 1) ? 780 CVS_FST_UPTODATE : CVS_FST_UNKNOWN; 781 } 782 else { 783 /* always show directories as up-to-date */ 784 if (ent->ce_type == CVS_ENT_DIR) 785 cfp->cf_cvstat = CVS_FST_UPTODATE; 786 else if (rcsnum_cmp(ent->ce_rev, cvs_addedrev, 2) == 0) 787 cfp->cf_cvstat = CVS_FST_ADDED; 788 else { 789 /* check last modified time */ 790 if (ent->ce_mtime == st.st_mtime) 791 cfp->cf_cvstat = CVS_FST_UPTODATE; 792 else 793 cfp->cf_cvstat = CVS_FST_MODIFIED; 794 } 795 } 796 797 if ((cfp->cf_type == DT_DIR) && (cvs_file_getdir(cfp, flags) < 0)) { 798 cvs_file_free(cfp); 799 return (NULL); 800 } 801 802 return (cfp); 803 } 804 805 806 static int 807 cvs_file_cmpname(const char *name1, const char *name2) 808 { 809 return (cvs_nocase == 0) ? (strcmp(name1, name2)) : 810 (strcasecmp(name1, name2)); 811 } 812