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