1 /* $OpenBSD: file.c,v 1.29 2004/08/13 13:28:53 jfb 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 (type == DT_DIR) { 251 if ((mkdir(path, mode) == -1) || (cvs_mkadmin(cfp, mode) < 0)) { 252 cvs_file_free(cfp); 253 return (NULL); 254 } 255 256 cfp->cf_ddat->cd_ent = cvs_ent_open(path, O_RDWR); 257 258 } 259 else { 260 fd = open(path, O_WRONLY|O_CREAT|O_EXCL, mode); 261 if (fd == -1) { 262 cvs_file_free(cfp); 263 return (NULL); 264 } 265 (void)close(fd); 266 } 267 268 return (cfp); 269 } 270 271 272 /* 273 * cvs_file_get() 274 * 275 * Load a cvs_file structure with all the information pertaining to the file 276 * <path>. 277 * The <flags> parameter specifies various flags that alter the behaviour of 278 * the function. The CF_RECURSE flag causes the function to recursively load 279 * subdirectories when <path> is a directory. 280 * The CF_SORT flag causes the files to be sorted in alphabetical order upon 281 * loading. The special case of "." as a path specification generates 282 * recursion for a single level and is equivalent to calling cvs_file_get() on 283 * all files of that directory. 284 * Returns a pointer to the cvs file structure, which must later be freed 285 * with cvs_file_free(). 286 */ 287 288 CVSFILE* 289 cvs_file_get(const char *path, int flags) 290 { 291 return cvs_file_lget(path, flags, NULL); 292 } 293 294 295 /* 296 * cvs_file_getspec() 297 * 298 * Load a specific set of files whose paths are given in the vector <fspec>, 299 * whose size is given in <fsn>. 300 * Returns a pointer to the lowest common subdirectory to all specified 301 * files. 302 */ 303 304 CVSFILE* 305 cvs_file_getspec(char **fspec, int fsn, int flags) 306 { 307 int i; 308 char *sp, *np, pcopy[MAXPATHLEN]; 309 CVSFILE *base, *cf, *nf; 310 311 base = cvs_file_get(".", 0); 312 if (base == NULL) 313 return (NULL); 314 315 for (i = 0; i < fsn; i++) { 316 strlcpy(pcopy, fspec[i], sizeof(pcopy)); 317 cf = base; 318 sp = pcopy; 319 320 do { 321 np = strchr(sp, '/'); 322 if (np != NULL) 323 *np = '\0'; 324 nf = cvs_file_find(cf, sp); 325 if (nf == NULL) { 326 nf = cvs_file_lget(pcopy, 0, cf); 327 if (nf == NULL) { 328 cvs_file_free(base); 329 return (NULL); 330 } 331 332 cvs_file_attach(cf, nf); 333 } 334 335 if (np != NULL) { 336 *np = '/'; 337 sp = np + 1; 338 } 339 340 cf = nf; 341 } while (np != NULL); 342 } 343 344 return (base); 345 } 346 347 348 /* 349 * cvs_file_find() 350 * 351 * Find the pointer to a CVS file entry within the file hierarchy <hier>. 352 * The file's pathname <path> must be relative to the base of <hier>. 353 * Returns the entry on success, or NULL on failure. 354 */ 355 356 CVSFILE* 357 cvs_file_find(CVSFILE *hier, const char *path) 358 { 359 char *pp, *sp, pbuf[MAXPATHLEN]; 360 CVSFILE *sf, *cf; 361 362 strlcpy(pbuf, path, sizeof(pbuf)); 363 364 cf = hier; 365 pp = pbuf; 366 do { 367 sp = strchr(pp, '/'); 368 if (sp != NULL) 369 *(sp++) = '\0'; 370 371 /* special case */ 372 if (*pp == '.') { 373 if ((*(pp + 1) == '.') && (*(pp + 2) == '\0')) { 374 /* request to go back to parent */ 375 if (cf->cf_parent == NULL) { 376 cvs_log(LP_NOTICE, 377 "path %s goes back too far", path); 378 return (NULL); 379 } 380 cf = cf->cf_parent; 381 continue; 382 } 383 else if (*(pp + 1) == '\0') 384 continue; 385 } 386 387 TAILQ_FOREACH(sf, &(cf->cf_ddat->cd_files), cf_list) 388 if (cvs_file_cmpname(pp, sf->cf_name) == 0) 389 break; 390 if (sf == NULL) 391 return (NULL); 392 393 cf = sf; 394 pp = sp; 395 } while (sp != NULL); 396 397 return (cf); 398 } 399 400 401 /* 402 * cvs_file_attach() 403 * 404 * Attach the file <file> as one of the children of parent <parent>, which 405 * has to be a file of type DT_DIR. 406 * Returns 0 on success, or -1 on failure. 407 */ 408 409 int 410 cvs_file_attach(CVSFILE *parent, CVSFILE *file) 411 { 412 struct cvs_dir *dp; 413 414 if (parent->cf_type != DT_DIR) 415 return (-1); 416 417 dp = parent->cf_ddat; 418 419 TAILQ_INSERT_TAIL(&(dp->cd_files), file, cf_list); 420 dp->cd_nfiles++; 421 file->cf_parent = parent; 422 423 return (0); 424 } 425 426 427 /* 428 * cvs_file_getdir() 429 * 430 * Get a cvs directory structure for the directory whose path is <dir>. 431 */ 432 433 static int 434 cvs_file_getdir(CVSFILE *cf, int flags) 435 { 436 int ret, fd; 437 u_int ndirs; 438 long base; 439 void *dp, *ep; 440 char fbuf[2048], pbuf[MAXPATHLEN]; 441 struct dirent *ent; 442 CVSFILE *cfp; 443 struct stat st; 444 struct cvs_dir *cdp; 445 struct cvs_flist dirs; 446 447 ndirs = 0; 448 TAILQ_INIT(&dirs); 449 cdp = cf->cf_ddat; 450 451 if (cf->cf_cvstat != CVS_FST_UNKNOWN) { 452 cdp->cd_root = cvsroot_get(cf->cf_path); 453 if (cdp->cd_root == NULL) { 454 cvs_file_freedir(cdp); 455 return (-1); 456 } 457 458 if (flags & CF_MKADMIN) 459 cvs_mkadmin(cf, 0755); 460 461 /* if the CVS administrative directory exists, load the info */ 462 snprintf(pbuf, sizeof(pbuf), "%s/" CVS_PATH_CVSDIR, 463 cf->cf_path); 464 if ((stat(pbuf, &st) == 0) && S_ISDIR(st.st_mode)) { 465 if (cvs_readrepo(cf->cf_path, pbuf, 466 sizeof(pbuf)) == 0) { 467 cdp->cd_repo = strdup(pbuf); 468 if (cdp->cd_repo == NULL) { 469 cvs_log(LP_ERRNO, 470 "failed to dup repository string"); 471 free(cdp); 472 return (-1); 473 } 474 } 475 476 cdp->cd_ent = cvs_ent_open(cf->cf_path, O_RDWR); 477 } 478 } 479 480 if (!(flags & CF_RECURSE) || (cf->cf_cvstat == CVS_FST_UNKNOWN)) 481 return (0); 482 483 fd = open(cf->cf_path, O_RDONLY); 484 if (fd == -1) { 485 cvs_log(LP_ERRNO, "failed to open `%s'", cf->cf_path); 486 cvs_file_freedir(cdp); 487 return (-1); 488 } 489 490 do { 491 ret = getdirentries(fd, fbuf, sizeof(fbuf), &base); 492 if (ret == -1) { 493 cvs_log(LP_ERRNO, "failed to get directory entries"); 494 (void)close(fd); 495 cvs_file_freedir(cdp); 496 return (-1); 497 } 498 499 dp = fbuf; 500 ep = fbuf + (size_t)ret; 501 while (dp < ep) { 502 ent = (struct dirent *)dp; 503 dp += ent->d_reclen; 504 505 if ((flags & CF_IGNORE) && cvs_file_chkign(ent->d_name)) 506 continue; 507 508 if ((flags & CF_NOSYMS) && (ent->d_type == DT_LNK)) 509 continue; 510 511 snprintf(pbuf, sizeof(pbuf), "%s/%s", 512 cf->cf_path, ent->d_name); 513 cfp = cvs_file_lget(pbuf, flags, cf); 514 if (cfp != NULL) { 515 if (cfp->cf_type == DT_DIR) { 516 TAILQ_INSERT_TAIL(&dirs, cfp, cf_list); 517 ndirs++; 518 } 519 else { 520 TAILQ_INSERT_TAIL(&(cdp->cd_files), cfp, 521 cf_list); 522 cdp->cd_nfiles++; 523 } 524 } 525 } 526 } while (ret > 0); 527 528 if (flags & CF_SORT) { 529 cvs_file_sort(&(cdp->cd_files), cdp->cd_nfiles); 530 cvs_file_sort(&dirs, ndirs); 531 } 532 533 while (!TAILQ_EMPTY(&dirs)) { 534 cfp = TAILQ_FIRST(&dirs); 535 TAILQ_REMOVE(&dirs, cfp, cf_list); 536 TAILQ_INSERT_TAIL(&(cdp->cd_files), cfp, cf_list); 537 } 538 cdp->cd_nfiles += ndirs; 539 540 (void)close(fd); 541 542 return (0); 543 } 544 545 546 /* 547 * cvs_file_free() 548 * 549 * Free a cvs_file structure and its contents. 550 */ 551 552 void 553 cvs_file_free(CVSFILE *cf) 554 { 555 if (cf->cf_path != NULL) 556 free(cf->cf_path); 557 if (cf->cf_ddat != NULL) 558 cvs_file_freedir(cf->cf_ddat); 559 free(cf); 560 } 561 562 563 /* 564 * cvs_file_examine() 565 * 566 * Examine the contents of the CVS file structure <cf> with the function 567 * <exam>. The function is called for all subdirectories and files of the 568 * root file. 569 */ 570 571 int 572 cvs_file_examine(CVSFILE *cf, int (*exam)(CVSFILE *, void *), void *arg) 573 { 574 int ret; 575 CVSFILE *fp; 576 577 if (cf->cf_type == DT_DIR) { 578 ret = (*exam)(cf, arg); 579 TAILQ_FOREACH(fp, &(cf->cf_ddat->cd_files), cf_list) { 580 ret = cvs_file_examine(fp, exam, arg); 581 if (ret == -1) 582 break; 583 } 584 } 585 else 586 ret = (*exam)(cf, arg); 587 588 return (ret); 589 } 590 591 592 /* 593 * cvs_file_freedir() 594 * 595 * Free a cvs_dir structure and its contents. 596 */ 597 598 static void 599 cvs_file_freedir(struct cvs_dir *cd) 600 { 601 CVSFILE *cfp; 602 603 if (cd->cd_root != NULL) 604 cvsroot_free(cd->cd_root); 605 if (cd->cd_repo != NULL) 606 free(cd->cd_repo); 607 608 if (cd->cd_ent != NULL) 609 cvs_ent_close(cd->cd_ent); 610 611 while (!TAILQ_EMPTY(&(cd->cd_files))) { 612 cfp = TAILQ_FIRST(&(cd->cd_files)); 613 TAILQ_REMOVE(&(cd->cd_files), cfp, cf_list); 614 cvs_file_free(cfp); 615 } 616 } 617 618 619 /* 620 * cvs_file_sort() 621 * 622 * Sort a list of cvs file structures according to their filename. The list 623 * <flp> is modified according to the sorting algorithm. The number of files 624 * in the list must be given by <nfiles>. 625 * Returns 0 on success, or -1 on failure. 626 */ 627 628 static int 629 cvs_file_sort(struct cvs_flist *flp, u_int nfiles) 630 { 631 int i; 632 size_t nb; 633 CVSFILE *cf, **cfvec; 634 635 cfvec = (CVSFILE **)calloc(nfiles, sizeof(CVSFILE *)); 636 if (cfvec == NULL) { 637 cvs_log(LP_ERRNO, "failed to allocate sorting vector"); 638 return (-1); 639 } 640 641 i = 0; 642 TAILQ_FOREACH(cf, flp, cf_list) { 643 if (i == (int)nfiles) { 644 cvs_log(LP_WARN, "too many files to sort"); 645 /* rebuild the list and abort sorting */ 646 while (--i >= 0) 647 TAILQ_INSERT_HEAD(flp, cfvec[i], cf_list); 648 free(cfvec); 649 return (-1); 650 } 651 cfvec[i++] = cf; 652 653 /* now unlink it from the list, 654 * we'll put it back in order later 655 */ 656 TAILQ_REMOVE(flp, cf, cf_list); 657 } 658 659 /* clear the list just in case */ 660 TAILQ_INIT(flp); 661 nb = (size_t)i; 662 663 heapsort(cfvec, nb, sizeof(cf), cvs_file_cmp); 664 665 /* rebuild the list from the bottom up */ 666 for (i = (int)nb - 1; i >= 0; i--) 667 TAILQ_INSERT_HEAD(flp, cfvec[i], cf_list); 668 669 free(cfvec); 670 return (0); 671 } 672 673 674 static int 675 cvs_file_cmp(const void *f1, const void *f2) 676 { 677 CVSFILE *cf1, *cf2; 678 cf1 = *(CVSFILE **)f1; 679 cf2 = *(CVSFILE **)f2; 680 return cvs_file_cmpname(cf1->cf_name, cf2->cf_name); 681 } 682 683 684 CVSFILE* 685 cvs_file_alloc(const char *path, u_int type) 686 { 687 size_t len; 688 char pbuf[MAXPATHLEN]; 689 CVSFILE *cfp; 690 struct cvs_dir *ddat; 691 692 cfp = (CVSFILE *)malloc(sizeof(*cfp)); 693 if (cfp == NULL) { 694 cvs_log(LP_ERRNO, "failed to allocate CVS file data"); 695 return (NULL); 696 } 697 memset(cfp, 0, sizeof(*cfp)); 698 699 /* ditch trailing slashes */ 700 strlcpy(pbuf, path, sizeof(pbuf)); 701 len = strlen(pbuf); 702 while (pbuf[len - 1] == '/') 703 pbuf[--len] = '\0'; 704 705 cfp->cf_path = strdup(pbuf); 706 if (cfp->cf_path == NULL) { 707 free(cfp); 708 return (NULL); 709 } 710 711 cfp->cf_name = strrchr(cfp->cf_path, '/'); 712 if (cfp->cf_name == NULL) 713 cfp->cf_name = cfp->cf_path; 714 else 715 cfp->cf_name++; 716 717 cfp->cf_type = type; 718 cfp->cf_cvstat = CVS_FST_UNKNOWN; 719 720 if (type == DT_DIR) { 721 ddat = (struct cvs_dir *)malloc(sizeof(*ddat)); 722 if (ddat == NULL) { 723 cvs_file_free(cfp); 724 return (NULL); 725 } 726 memset(ddat, 0, sizeof(*ddat)); 727 TAILQ_INIT(&(ddat->cd_files)); 728 cfp->cf_ddat = ddat; 729 } 730 return (cfp); 731 } 732 733 734 /* 735 * cvs_file_lget() 736 * 737 * Get the file and link it with the parent right away. 738 * Returns a pointer to the created file structure on success, or NULL on 739 * failure. 740 */ 741 742 static CVSFILE* 743 cvs_file_lget(const char *path, int flags, CVSFILE *parent) 744 { 745 int cwd; 746 struct stat st; 747 CVSFILE *cfp; 748 struct cvs_ent *ent; 749 750 ent = NULL; 751 752 if (strcmp(path, ".") == 0) 753 cwd = 1; 754 else 755 cwd = 0; 756 757 if (stat(path, &st) == -1) { 758 cvs_log(LP_ERRNO, "failed to stat %s", path); 759 return (NULL); 760 } 761 762 cfp = cvs_file_alloc(path, IFTODT(st.st_mode)); 763 if (cfp == NULL) { 764 cvs_log(LP_ERRNO, "failed to allocate CVS file data"); 765 return (NULL); 766 } 767 cfp->cf_parent = parent; 768 cfp->cf_mode = st.st_mode & ACCESSPERMS; 769 cfp->cf_mtime = st.st_mtime; 770 771 if ((parent != NULL) && (CVS_DIR_ENTRIES(parent) != NULL)) { 772 ent = cvs_ent_get(CVS_DIR_ENTRIES(parent), cfp->cf_name); 773 } 774 775 if (ent == NULL) { 776 cfp->cf_cvstat = (cwd == 1) ? 777 CVS_FST_UPTODATE : CVS_FST_UNKNOWN; 778 } 779 else { 780 /* always show directories as up-to-date */ 781 if (ent->ce_type == CVS_ENT_DIR) 782 cfp->cf_cvstat = CVS_FST_UPTODATE; 783 else if (rcsnum_cmp(ent->ce_rev, cvs_addedrev, 2) == 0) 784 cfp->cf_cvstat = CVS_FST_ADDED; 785 else { 786 /* check last modified time */ 787 if (ent->ce_mtime == st.st_mtime) 788 cfp->cf_cvstat = CVS_FST_UPTODATE; 789 else 790 cfp->cf_cvstat = CVS_FST_MODIFIED; 791 } 792 } 793 794 if ((cfp->cf_type == DT_DIR) && (cvs_file_getdir(cfp, flags) < 0)) { 795 cvs_file_free(cfp); 796 return (NULL); 797 } 798 799 return (cfp); 800 } 801 802 803 static int 804 cvs_file_cmpname(const char *name1, const char *name2) 805 { 806 return (cvs_nocase == 0) ? (strcmp(name1, name2)) : 807 (strcasecmp(name1, name2)); 808 } 809