xref: /openbsd-src/usr.bin/cvs/file.c (revision 4deeb87832e5d00dbfea5b08ed9c463296b435ef)
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