xref: /openbsd-src/usr.bin/cvs/file.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: file.c,v 1.181 2007/01/28 03:03:35 joris Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. The name of the author may not be used to endorse or promote products
14  *    derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "includes.h"
29 
30 #include <sys/mman.h>
31 
32 #include "cvs.h"
33 #include "file.h"
34 #include "log.h"
35 
36 #define CVS_IGN_STATIC	0x01	/* pattern is static, no need to glob */
37 
38 #define CVS_CHAR_ISMETA(c)	((c == '*') || (c == '?') || (c == '['))
39 
40 /*
41  * Standard patterns to ignore.
42  */
43 static const char *cvs_ign_std[] = {
44 	".",
45 	"..",
46 	"*.o",
47 	"*.a",
48 	"*.bak",
49 	"*.orig",
50 	"*.rej",
51 	"*.old",
52 	"*.exe",
53 	"*.depend",
54 	"*.obj",
55 	"*.elc",
56 	"*.ln",
57 	"*.olb",
58 	"CVS",
59 	"core",
60 	"cvslog*",
61 	"*.core",
62 	".#*",
63 	"*~",
64 	"_$*",
65 	"*$",
66 };
67 
68 struct ignore_head cvs_ign_pats;
69 struct ignore_head dir_ign_pats;
70 
71 void
72 cvs_file_init(void)
73 {
74 	int i, l;
75 	FILE *ifp;
76 	size_t len;
77 	char path[MAXPATHLEN], buf[MAXNAMLEN];
78 
79 	TAILQ_INIT(&cvs_ign_pats);
80 	TAILQ_INIT(&dir_ign_pats);
81 
82 	/* standard patterns to ignore */
83 	for (i = 0; i < (int)(sizeof(cvs_ign_std)/sizeof(char *)); i++)
84 		cvs_file_ignore(cvs_ign_std[i], &cvs_ign_pats);
85 
86 	/* read the cvsignore file in the user's home directory, if any */
87 	l = snprintf(path, MAXPATHLEN, "%s/.cvsignore", cvs_homedir);
88 	if (l == -1 || l >= MAXPATHLEN)
89 		fatal("overflow in cvs_file_init");
90 
91 	ifp = fopen(path, "r");
92 	if (ifp == NULL) {
93 		if (errno != ENOENT)
94 			cvs_log(LP_ERRNO,
95 			    "failed to open user's cvsignore file `%s'", path);
96 	} else {
97 		while (fgets(buf, MAXNAMLEN, ifp) != NULL) {
98 			len = strlen(buf);
99 			if (len == 0)
100 				continue;
101 			if (buf[len - 1] == '\n')
102 				buf[len - 1] = '\0';
103 
104 			cvs_file_ignore(buf, &cvs_ign_pats);
105 		}
106 
107 		(void)fclose(ifp);
108 	}
109 }
110 
111 void
112 cvs_file_ignore(const char *pat, struct ignore_head *list)
113 {
114 	char *cp;
115 	size_t len;
116 	struct cvs_ignpat *ip;
117 
118 	ip = xmalloc(sizeof(*ip));
119 	len = strlcpy(ip->ip_pat, pat, sizeof(ip->ip_pat));
120 	if (len >= sizeof(ip->ip_pat))
121 		fatal("cvs_file_ignore: truncation of pattern '%s'", pat);
122 
123 	/* check if we will need globbing for that pattern */
124 	ip->ip_flags = CVS_IGN_STATIC;
125 	for (cp = ip->ip_pat; *cp != '\0'; cp++) {
126 		if (CVS_CHAR_ISMETA(*cp)) {
127 			ip->ip_flags &= ~CVS_IGN_STATIC;
128 			break;
129 		}
130 	}
131 
132 	TAILQ_INSERT_TAIL(list, ip, ip_list);
133 }
134 
135 int
136 cvs_file_chkign(const char *file)
137 {
138 	int flags;
139 	struct cvs_ignpat *ip;
140 
141 	flags = FNM_PERIOD;
142 	if (cvs_nocase)
143 		flags |= FNM_CASEFOLD;
144 
145 	TAILQ_FOREACH(ip, &cvs_ign_pats, ip_list) {
146 		if (ip->ip_flags & CVS_IGN_STATIC) {
147 			if (cvs_file_cmpname(file, ip->ip_pat) == 0)
148 				return (1);
149 		} else if (fnmatch(ip->ip_pat, file, flags) == 0)
150 			return (1);
151 	}
152 
153 	TAILQ_FOREACH(ip, &dir_ign_pats, ip_list) {
154 		if (ip->ip_flags & CVS_IGN_STATIC) {
155 			if (cvs_file_cmpname(file, ip->ip_pat) == 0)
156 				return (1);
157 		} else if (fnmatch(ip->ip_pat, file, flags) == 0)
158 			return (1);
159 	}
160 
161 	return (0);
162 }
163 
164 void
165 cvs_file_run(int argc, char **argv, struct cvs_recursion *cr)
166 {
167 	int i;
168 	struct cvs_flisthead fl;
169 
170 	TAILQ_INIT(&fl);
171 
172 	for (i = 0; i < argc; i++)
173 		cvs_file_get(argv[i], &fl);
174 
175 	cvs_file_walklist(&fl, cr);
176 	cvs_file_freelist(&fl);
177 }
178 
179 struct cvs_filelist *
180 cvs_file_get(const char *name, struct cvs_flisthead *fl)
181 {
182 	const char *p;
183 	struct cvs_filelist *l;
184 
185 	for (p = name; p[0] == '.' && p[1] == '/';)
186 		p += 2;
187 
188 	TAILQ_FOREACH(l, fl, flist)
189 		if (!strcmp(l->file_path, p))
190 			return (l);
191 
192 	l = (struct cvs_filelist *)xmalloc(sizeof(*l));
193 	l->file_path = xstrdup(p);
194 
195 	TAILQ_INSERT_TAIL(fl, l, flist);
196 	return (l);
197 }
198 
199 struct cvs_file *
200 cvs_file_get_cf(const char *d, const char *f, int fd, int type)
201 {
202 	int l;
203 	struct cvs_file *cf;
204 	char *p, rpath[MAXPATHLEN];
205 
206 	l = snprintf(rpath, MAXPATHLEN, "%s/%s", d, f);
207 	if (l == -1 || l >= MAXPATHLEN)
208 		fatal("cvs_file_get_cf: overflow");
209 
210 	for (p = rpath; p[0] == '.' && p[1] == '/';)
211 		p += 2;
212 
213 	cf = (struct cvs_file *)xmalloc(sizeof(*cf));
214 	memset(cf, 0, sizeof(*cf));
215 
216 	cf->file_name = xstrdup(f);
217 	cf->file_wd = xstrdup(d);
218 	cf->file_path = xstrdup(p);
219 	cf->fd = fd;
220 	cf->repo_fd = -1;
221 	cf->file_type = type;
222 	cf->file_status = cf->file_flags = 0;
223 	cf->file_ent = NULL;
224 
225 	return (cf);
226 }
227 
228 void
229 cvs_file_walklist(struct cvs_flisthead *fl, struct cvs_recursion *cr)
230 {
231 	int len, fd, type;
232 	struct stat st;
233 	struct cvs_file *cf;
234 	struct cvs_filelist *l, *nxt;
235 	char *d, *f, repo[MAXPATHLEN], fpath[MAXPATHLEN];
236 
237 	for (l = TAILQ_FIRST(fl); l != NULL; l = nxt) {
238 		if (cvs_quit)
239 			fatal("received signal %d", sig_received);
240 
241 		cvs_log(LP_TRACE, "cvs_file_walklist: element '%s'",
242 		    l->file_path);
243 
244 		if ((f = basename(l->file_path)) == NULL)
245 			fatal("cvs_file_walklist: basename failed");
246 		if ((d = dirname(l->file_path)) == NULL)
247 			fatal("cvs_file_walklist: dirname failed");
248 
249 		type = CVS_FILE;
250 		if ((fd = open(l->file_path, O_RDONLY)) != -1) {
251 			if (fstat(fd, &st) == -1) {
252 				cvs_log(LP_ERRNO, "%s", l->file_path);
253 				(void)close(fd);
254 				goto next;
255 			}
256 
257 			if (S_ISDIR(st.st_mode))
258 				type = CVS_DIR;
259 			else if (S_ISREG(st.st_mode))
260 				type = CVS_FILE;
261 			else {
262 				cvs_log(LP_ERR,
263 				    "ignoring bad file type for %s",
264 				    l->file_path);
265 				(void)close(fd);
266 				goto next;
267 			}
268 		} else if (current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
269 			if (stat(d, &st) == -1) {
270 				cvs_log(LP_ERRNO, "%s", d);
271 				goto next;
272 			}
273 
274 			cvs_get_repository_path(d, repo, MAXPATHLEN);
275 			len = snprintf(fpath, MAXPATHLEN, "%s/%s",
276 			    repo, f);
277 			if (len == -1 || len >= MAXPATHLEN)
278 				fatal("cvs_file_walklist: overflow");
279 
280 			if ((fd = open(fpath, O_RDONLY)) == -1) {
281 				strlcat(fpath, RCS_FILE_EXT, MAXPATHLEN);
282 				fd = open(fpath, O_RDONLY);
283 			}
284 
285 			if (fd != -1) {
286 				if (fstat(fd, &st) == -1)
287 					fatal("cvs_file_walklist: %s: %s",
288 					     fpath, strerror(errno));
289 
290 				if (S_ISDIR(st.st_mode))
291 					type = CVS_DIR;
292 				else if (S_ISREG(st.st_mode))
293 					type = CVS_FILE;
294 				else {
295 					cvs_log(LP_ERR,
296 					    "ignoring bad file type for %s",
297 					    l->file_path);
298 					(void)close(fd);
299 					goto next;
300 				}
301 
302 				/* this file is not in our working copy yet */
303 				(void)close(fd);
304 				fd = -1;
305 			}
306 		}
307 
308 		cf = cvs_file_get_cf(d, f, fd, type);
309 		if (cf->file_type == CVS_DIR) {
310 			cvs_file_walkdir(cf, cr);
311 		} else {
312 			if (cr->fileproc != NULL)
313 				cr->fileproc(cf);
314 		}
315 
316 		cvs_file_free(cf);
317 
318 next:
319 		nxt = TAILQ_NEXT(l, flist);
320 		TAILQ_REMOVE(fl, l, flist);
321 
322 		xfree(l->file_path);
323 		xfree(l);
324 	}
325 }
326 
327 void
328 cvs_file_walkdir(struct cvs_file *cf, struct cvs_recursion *cr)
329 {
330 	int l, type;
331 	FILE *fp;
332 	int nbytes;
333 	size_t len;
334 	long base;
335 	size_t bufsize;
336 	struct stat st;
337 	struct dirent *dp;
338 	struct cvs_ent *ent;
339 	struct cvs_ignpat *ip;
340 	struct cvs_ent_line *line;
341 	struct cvs_flisthead fl, dl;
342 	CVSENTRIES *entlist;
343 	char *buf, *ebuf, *cp, repo[MAXPATHLEN], fpath[MAXPATHLEN];
344 
345 	cvs_log(LP_TRACE, "cvs_file_walkdir(%s)", cf->file_path);
346 
347 	if (cr->enterdir != NULL)
348 		cr->enterdir(cf);
349 
350 	if (cr->fileproc != NULL)
351 		cr->fileproc(cf);
352 
353 	if (cf->file_status == FILE_SKIP)
354 		return;
355 
356 	/*
357 	 * If we do not have a admin directory inside here, dont bother,
358 	 * unless we are running import.
359 	 */
360 	l = snprintf(fpath, MAXPATHLEN, "%s/%s", cf->file_path,
361 	    CVS_PATH_CVSDIR);
362 	if (l == -1 || l >= MAXPATHLEN)
363 		fatal("cvs_file_walkdir: overflow");
364 
365 	l = stat(fpath, &st);
366 	if (cvs_cmdop != CVS_OP_IMPORT &&
367 	    (l == -1 || (l == 0 && !S_ISDIR(st.st_mode)))) {
368 		return;
369 	}
370 
371 	/*
372 	 * check for a local .cvsignore file
373 	 */
374 	l = snprintf(fpath, MAXPATHLEN, "%s/.cvsignore", cf->file_path);
375 	if (l == -1 || l >= MAXPATHLEN)
376 		fatal("cvs_file_walkdir: overflow");
377 
378 	if ((fp = fopen(fpath, "r")) != NULL) {
379 		while (fgets(fpath, MAXPATHLEN, fp) != NULL) {
380 			len = strlen(fpath);
381 			if (len == 0)
382 				continue;
383 			if (fpath[len - 1] == '\n')
384 				fpath[len - 1] = '\0';
385 
386 			cvs_file_ignore(fpath, &dir_ign_pats);
387 		}
388 
389 		(void)fclose(fp);
390 	}
391 
392 	if (fstat(cf->fd, &st) == -1)
393 		fatal("cvs_file_walkdir: %s %s", cf->file_path,
394 		    strerror(errno));
395 
396 	bufsize = st.st_size;
397 	if (bufsize < st.st_blksize)
398 		bufsize = st.st_blksize;
399 
400 	buf = xmalloc(bufsize);
401 	TAILQ_INIT(&fl);
402 	TAILQ_INIT(&dl);
403 
404 	while ((nbytes = getdirentries(cf->fd, buf, bufsize, &base)) > 0) {
405 		ebuf = buf + nbytes;
406 		cp = buf;
407 
408 		while (cp < ebuf) {
409 			dp = (struct dirent *)cp;
410 			if (!strcmp(dp->d_name, ".") ||
411 			    !strcmp(dp->d_name, "..") ||
412 			    !strcmp(dp->d_name, CVS_PATH_CVSDIR) ||
413 			    dp->d_reclen == 0) {
414 				cp += dp->d_reclen;
415 				continue;
416 			}
417 
418 			if (cvs_file_chkign(dp->d_name)) {
419 				cp += dp->d_reclen;
420 				continue;
421 			}
422 
423 			l = snprintf(fpath, MAXPATHLEN, "%s/%s",
424 			    cf->file_path, dp->d_name);
425 			if (l == -1 || l >= MAXPATHLEN)
426 				fatal("cvs_file_walkdir: overflow");
427 
428 			/*
429 			 * nfs and afs will show d_type as DT_UNKNOWN
430 			 * for files and/or directories so when we encounter
431 			 * this we call stat() on the path to be sure.
432 			 */
433 			if (dp->d_type == DT_UNKNOWN) {
434 				if (stat(fpath, &st) == -1)
435 					fatal("'%s': %s", fpath,
436 					    strerror(errno));
437 
438 				switch (st.st_mode & S_IFMT) {
439 				case S_IFDIR:
440 					type = CVS_DIR;
441 					break;
442 				case S_IFREG:
443 					type = CVS_FILE;
444 					break;
445 				default:
446 					type = FILE_SKIP;
447 					break;
448 				}
449 			} else {
450 				switch (dp->d_type) {
451 				case DT_DIR:
452 					type = CVS_DIR;
453 					break;
454 				case DT_REG:
455 					type = CVS_FILE;
456 					break;
457 				default:
458 					type = FILE_SKIP;
459 					break;
460 				}
461 			}
462 
463 			if (type == FILE_SKIP) {
464 				if (verbosity > 1) {
465 					cvs_log(LP_NOTICE, "ignoring `%s'",
466 					    dp->d_name);
467 				}
468 				cp += dp->d_reclen;
469 				continue;
470 			}
471 
472 			if (!(cr->flags & CR_RECURSE_DIRS) &&
473 			    type == CVS_DIR) {
474 				cp += dp->d_reclen;
475 				continue;
476 			}
477 
478 			switch (type) {
479 			case CVS_DIR:
480 				cvs_file_get(fpath, &dl);
481 				break;
482 			case CVS_FILE:
483 				cvs_file_get(fpath, &fl);
484 				break;
485 			default:
486 				fatal("type %d unknown, shouldn't happen",
487 				    type);
488 			}
489 
490 			cp += dp->d_reclen;
491 		}
492 	}
493 
494 	if (nbytes == -1)
495 		fatal("cvs_file_walkdir: %s %s", cf->file_path,
496 		    strerror(errno));
497 
498 	xfree(buf);
499 
500 	while ((ip = TAILQ_FIRST(&dir_ign_pats)) != NULL) {
501 		TAILQ_REMOVE(&dir_ign_pats, ip, ip_list);
502 		xfree(ip);
503 	}
504 
505 	entlist = cvs_ent_open(cf->file_path);
506 	TAILQ_FOREACH(line, &(entlist->cef_ent), entries_list) {
507 		ent = cvs_ent_parse(line->buf);
508 
509 		l = snprintf(fpath, MAXPATHLEN, "%s/%s", cf->file_path,
510 		    ent->ce_name);
511 		if (l == -1 || l >= MAXPATHLEN)
512 			fatal("cvs_file_walkdir: overflow");
513 
514 		if (!(cr->flags & CR_RECURSE_DIRS) &&
515 		    ent->ce_type == CVS_ENT_DIR)
516 			continue;
517 
518 		if (ent->ce_type == CVS_ENT_DIR)
519 			cvs_file_get(fpath, &dl);
520 		else if (ent->ce_type == CVS_ENT_FILE)
521 			cvs_file_get(fpath, &fl);
522 
523 		cvs_ent_free(ent);
524 	}
525 
526 	cvs_ent_close(entlist, ENT_NOSYNC);
527 
528 	if (cr->flags & CR_REPO) {
529 		cvs_get_repository_path(cf->file_path, repo, MAXPATHLEN);
530 		cvs_repository_lock(repo);
531 
532 		cvs_repository_getdir(repo, cf->file_path, &fl, &dl,
533 		    (cr->flags & CR_RECURSE_DIRS));
534 	}
535 
536 	cvs_file_walklist(&fl, cr);
537 	cvs_file_freelist(&fl);
538 
539 	if (cr->flags & CR_REPO)
540 		cvs_repository_unlock(repo);
541 
542 	cvs_file_walklist(&dl, cr);
543 	cvs_file_freelist(&dl);
544 
545 	if (cr->leavedir != NULL)
546 		cr->leavedir(cf);
547 }
548 
549 void
550 cvs_file_freelist(struct cvs_flisthead *fl)
551 {
552 	struct cvs_filelist *f;
553 
554 	while ((f = TAILQ_FIRST(fl)) != NULL) {
555 		TAILQ_REMOVE(fl, f, flist);
556 		xfree(f->file_path);
557 		xfree(f);
558 	}
559 }
560 
561 void
562 cvs_file_classify(struct cvs_file *cf, const char *tag, int loud)
563 {
564 	size_t len;
565 	struct stat st;
566 	BUF *b1, *b2;
567 	int rflags, l, ismodified, rcsdead;
568 	CVSENTRIES *entlist = NULL;
569 	const char *state;
570 	char repo[MAXPATHLEN], rcsfile[MAXPATHLEN], r1[16], r2[16];
571 
572 	cvs_log(LP_TRACE, "cvs_file_classify(%s)", cf->file_path);
573 
574 	if (!strcmp(cf->file_path, ".")) {
575 		cf->file_status = FILE_UPTODATE;
576 		return;
577 	}
578 
579 	cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
580 	l = snprintf(rcsfile, MAXPATHLEN, "%s/%s",
581 	    repo, cf->file_name);
582 	if (l == -1 || l >= MAXPATHLEN)
583 		fatal("cvs_file_classify: overflow");
584 
585 	if (cf->file_type == CVS_FILE) {
586 		len = strlcat(rcsfile, RCS_FILE_EXT, MAXPATHLEN);
587 		if (len >= MAXPATHLEN)
588 			fatal("cvs_file_classify: truncation");
589 	}
590 
591 	cf->file_rpath = xstrdup(rcsfile);
592 
593 	entlist = cvs_ent_open(cf->file_wd);
594 	cf->file_ent = cvs_ent_get(entlist, cf->file_name);
595 
596 	if (cf->file_ent != NULL) {
597 		if (cf->file_ent->ce_type == CVS_ENT_DIR &&
598 		    cf->file_type != CVS_DIR)
599 			fatal("%s is supposed to be a directory, but it is not",
600 			    cf->file_path);
601 		if (cf->file_ent->ce_type == CVS_ENT_FILE &&
602 		    cf->file_type != CVS_FILE)
603 			fatal("%s is supposed to be a file, but it is not",
604 			    cf->file_path);
605 	}
606 
607 	if (cf->file_type == CVS_DIR) {
608 		if (cf->fd == -1 && stat(rcsfile, &st) != -1)
609 			cf->file_status = DIR_CREATE;
610 		else if (cf->file_ent != NULL)
611 			cf->file_status = FILE_UPTODATE;
612 		else
613 			cf->file_status = FILE_UNKNOWN;
614 
615 		cvs_ent_close(entlist, ENT_NOSYNC);
616 		return;
617 	}
618 
619 	rflags = RCS_READ;
620 	switch (cvs_cmdop) {
621 	case CVS_OP_COMMIT:
622 		rflags = RCS_WRITE;
623 		break;
624 	case CVS_OP_IMPORT:
625 	case CVS_OP_LOG:
626 		rflags |= RCS_PARSE_FULLY;
627 		break;
628 	}
629 
630 	cf->repo_fd = open(cf->file_rpath, O_RDONLY);
631 	if (cf->repo_fd != -1) {
632 		cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd, rflags);
633 		if (cf->file_rcs == NULL)
634 			fatal("cvs_file_classify: failed to parse RCS");
635 		cf->file_rcs->rf_inattic = 0;
636 	} else if (cvs_cmdop != CVS_OP_CHECKOUT) {
637 		l = snprintf(rcsfile, MAXPATHLEN, "%s/%s/%s%s",
638 		    repo, CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
639 		if (l == -1 || l >= MAXPATHLEN)
640 			fatal("cvs_file_classify: overflow");
641 
642 		cf->repo_fd = open(rcsfile, O_RDONLY);
643 		if (cf->repo_fd != -1) {
644 			xfree(cf->file_rpath);
645 			cf->file_rpath = xstrdup(rcsfile);
646 			cf->file_rcs = rcs_open(cf->file_rpath,
647 			     cf->repo_fd, rflags);
648 			if (cf->file_rcs == NULL)
649 				fatal("cvs_file_classify: failed to parse RCS");
650 			cf->file_rcs->rf_inattic = 1;
651 		} else {
652 			cf->file_rcs = NULL;
653 		}
654 	} else
655 		cf->file_rcs = NULL;
656 
657 	if (tag != NULL && cf->file_rcs != NULL) {
658 		if ((cf->file_rcsrev = rcs_translate_tag(tag, cf->file_rcs)) == NULL)
659 			fatal("cvs_file_classify: could not translate tag `%s'", tag);
660 	} else if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL) {
661 		cf->file_rcsrev = rcsnum_alloc();
662 		rcsnum_cpy(cf->file_ent->ce_rev, cf->file_rcsrev, 0);
663 	} else if (cf->file_rcs != NULL)
664 		cf->file_rcsrev = rcs_head_get(cf->file_rcs);
665 	else
666 		cf->file_rcsrev = NULL;
667 
668 	if (cf->file_ent != NULL)
669 		rcsnum_tostr(cf->file_ent->ce_rev, r1, sizeof(r1));
670 	if (cf->file_rcsrev != NULL)
671 		rcsnum_tostr(cf->file_rcsrev, r2, sizeof(r2));
672 
673 	ismodified = rcsdead = 0;
674 	if (cf->fd != -1 && cf->file_ent != NULL) {
675 		if (fstat(cf->fd, &st) == -1)
676 			fatal("cvs_file_classify: %s", strerror(errno));
677 
678 		if (st.st_mtime != cf->file_ent->ce_mtime)
679 			ismodified = 1;
680 	}
681 
682 	if (ismodified == 1 && cf->fd != -1 && cf->file_rcs != NULL) {
683 		b1 = rcs_rev_getbuf(cf->file_rcs, cf->file_rcsrev, 0);
684 		if (b1 == NULL)
685 			fatal("failed to get HEAD revision for comparison");
686 
687 		b2 = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT);
688 		if (b2 == NULL)
689 			fatal("failed to get file content for comparison");
690 
691 		/* b1 and b2 get released in cvs_buf_differ */
692 		if (cvs_buf_differ(b1, b2))
693 			ismodified = 1;
694 		else
695 			ismodified = 0;
696 	}
697 
698 	if (cf->file_rcs != NULL && cf->file_rcsrev != NULL) {
699 		state = rcs_state_get(cf->file_rcs, cf->file_rcsrev);
700 		if (state == NULL)
701 			fatal("failed to get state for HEAD for %s",
702 			    cf->file_path);
703 		if (!strcmp(state, RCS_STATE_DEAD))
704 			rcsdead = 1;
705 
706 		cf->file_rcs->rf_dead = rcsdead;
707 	}
708 
709 	/*
710 	 * 10 Sin
711 	 * 20 Goto hell
712 	 * (I welcome you if-else hell)
713 	 */
714 	if (cf->file_ent == NULL) {
715 		if (cf->file_rcs == NULL) {
716 			if (cf->fd == -1) {
717 				cvs_log(LP_NOTICE,
718 				    "nothing known about '%s'",
719 				    cf->file_path);
720 			} else if (cvs_cmdop != CVS_OP_ADD) {
721 				cvs_log(LP_NOTICE,
722 				    "use add to create an entry for %s",
723 				    cf->file_path);
724 			}
725 
726 			cf->file_status = FILE_UNKNOWN;
727 		} else if (rcsdead == 1) {
728 			if (cf->fd == -1) {
729 				cf->file_status = FILE_UPTODATE;
730 			} else if (cvs_cmdop != CVS_OP_ADD) {
731 				cvs_log(LP_NOTICE,
732 				    "use add to create an entry for %s",
733 				    cf->file_path);
734 				cf->file_status = FILE_UNKNOWN;
735 			}
736 		} else {
737 			cf->file_status = FILE_CHECKOUT;
738 		}
739 	} else if (cf->file_ent->ce_status == CVS_ENT_ADDED) {
740 		if (cf->fd == -1) {
741 			if (cvs_cmdop != CVS_OP_REMOVE) {
742 				cvs_log(LP_NOTICE,
743 				    "warning: new-born %s has disappeared",
744 				    cf->file_path);
745 			}
746 			cf->file_status = FILE_REMOVE_ENTRY;
747 		} else if (cf->file_rcs == NULL || rcsdead == 1) {
748 			cf->file_status = FILE_ADDED;
749 		} else {
750 			cvs_log(LP_NOTICE,
751 			    "conflict: %s already created by others",
752 			    cf->file_path);
753 			cf->file_status = FILE_CONFLICT;
754 		}
755 	} else if (cf->file_ent->ce_status == CVS_ENT_REMOVED) {
756 		if (cf->fd != -1) {
757 			cvs_log(LP_NOTICE,
758 			    "%s should be removed but is still there",
759 			    cf->file_path);
760 			cf->file_status = FILE_REMOVED;
761 		} else if (cf->file_rcs == NULL || rcsdead == 1) {
762 			cf->file_status = FILE_REMOVE_ENTRY;
763 		} else {
764 			if (strcmp(r1, r2)) {
765 				cvs_log(LP_NOTICE,
766 				    "conflict: removed %s was modified"
767 				    " by a second party",
768 				    cf->file_path);
769 				cf->file_status = FILE_CONFLICT;
770 			} else {
771 				cf->file_status = FILE_REMOVED;
772 			}
773 		}
774 	} else if (cf->file_ent->ce_status == CVS_ENT_REG) {
775 		if (cf->file_rcs == NULL || rcsdead == 1) {
776 			if (cf->fd == -1) {
777 				cvs_log(LP_NOTICE,
778 				    "warning: %s's entry exists but"
779 				    " there is no longer a file"
780 				    " in the repository,"
781 				    " removing entry",
782 				     cf->file_path);
783 				cf->file_status = FILE_REMOVE_ENTRY;
784 			} else {
785 				if (ismodified) {
786 					cvs_log(LP_NOTICE,
787 					    "conflict: %s is no longer "
788 					    "in the repository but is "
789 					    "locally modified",
790 					    cf->file_path);
791 					cf->file_status = FILE_CONFLICT;
792 				} else {
793 					cvs_log(LP_NOTICE,
794 					    "%s is no longer in the "
795 					    "repository",
796 					    cf->file_path);
797 
798 					cf->file_status = FILE_UNLINK;
799 				}
800 			}
801 		} else {
802 			if (cf->fd == -1) {
803 				if (cvs_cmdop != CVS_OP_REMOVE) {
804 					cvs_log(LP_NOTICE,
805 					    "warning: %s was lost",
806 					    cf->file_path);
807 				}
808 				cf->file_status = FILE_LOST;
809 			} else {
810 				if (ismodified == 1)
811 					cf->file_status = FILE_MODIFIED;
812 				else
813 					cf->file_status = FILE_UPTODATE;
814 
815 				if (strcmp(r1, r2)) {
816 					if (cf->file_status == FILE_MODIFIED)
817 						cf->file_status = FILE_MERGE;
818 					else
819 						cf->file_status = FILE_PATCH;
820 				}
821 			}
822 		}
823 	}
824 
825 	cvs_ent_close(entlist, ENT_NOSYNC);
826 }
827 
828 void
829 cvs_file_free(struct cvs_file *cf)
830 {
831 	xfree(cf->file_name);
832 	xfree(cf->file_wd);
833 	xfree(cf->file_path);
834 
835 	if (cf->file_rcsrev != NULL)
836 		rcsnum_free(cf->file_rcsrev);
837 	if (cf->file_rpath != NULL)
838 		xfree(cf->file_rpath);
839 	if (cf->file_ent != NULL)
840 		cvs_ent_free(cf->file_ent);
841 	if (cf->file_rcs != NULL)
842 		rcs_close(cf->file_rcs);
843 	if (cf->fd != -1)
844 		(void)close(cf->fd);
845 	if (cf->repo_fd != -1)
846 		(void)close(cf->repo_fd);
847 	xfree(cf);
848 }
849 
850 int
851 cvs_file_cmpname(const char *name1, const char *name2)
852 {
853 	return (cvs_nocase == 0) ? (strcmp(name1, name2)) :
854 	    (strcasecmp(name1, name2));
855 }
856 
857 int
858 cvs_file_cmp(const char *file1, const char *file2)
859 {
860 	struct stat stb1, stb2;
861 	int fd1, fd2, ret;
862 
863 	ret = 0;
864 
865 	if ((fd1 = open(file1, O_RDONLY|O_NOFOLLOW, 0)) == -1)
866 		fatal("cvs_file_cmp: open: `%s': %s", file1, strerror(errno));
867 	if ((fd2 = open(file2, O_RDONLY|O_NOFOLLOW, 0)) == -1)
868 		fatal("cvs_file_cmp: open: `%s': %s", file2, strerror(errno));
869 
870 	if (fstat(fd1, &stb1) == -1)
871 		fatal("cvs_file_cmp: `%s': %s", file1, strerror(errno));
872 	if (fstat(fd2, &stb2) == -1)
873 		fatal("cvs_file_cmp: `%s': %s", file2, strerror(errno));
874 
875 	if (stb1.st_size != stb2.st_size ||
876 	    (stb1.st_mode & S_IFMT) != (stb2.st_mode & S_IFMT)) {
877 		ret = 1;
878 		goto out;
879 	}
880 
881 	if (S_ISBLK(stb1.st_mode) || S_ISCHR(stb1.st_mode)) {
882 		if (stb1.st_rdev != stb2.st_rdev)
883 			ret = 1;
884 		goto out;
885 	}
886 
887 	if (S_ISREG(stb1.st_mode)) {
888 		void *p1, *p2;
889 
890 		if (stb1.st_size > SIZE_MAX) {
891 			ret = 1;
892 			goto out;
893 		}
894 
895 		if ((p1 = mmap(NULL, stb1.st_size, PROT_READ,
896 		    MAP_FILE, fd1, (off_t)0)) == MAP_FAILED)
897 			fatal("cvs_file_cmp: mmap failed");
898 
899 		if ((p2 = mmap(NULL, stb1.st_size, PROT_READ,
900 		    MAP_FILE, fd2, (off_t)0)) == MAP_FAILED)
901 			fatal("cvs_file_cmp: mmap failed");
902 
903 		madvise(p1, stb1.st_size, MADV_SEQUENTIAL);
904 		madvise(p2, stb1.st_size, MADV_SEQUENTIAL);
905 
906 		ret = memcmp(p1, p2, stb1.st_size);
907 
908 		(void)munmap(p1, stb1.st_size);
909 		(void)munmap(p2, stb1.st_size);
910 	}
911 
912 out:
913 	(void)close(fd1);
914 	(void)close(fd2);
915 
916 	return (ret);
917 }
918 
919 int
920 cvs_file_copy(const char *from, const char *to)
921 {
922 	struct stat st;
923 	struct timeval tv[2];
924 	time_t atime, mtime;
925 	int src, dst, ret;
926 
927 	ret = 0;
928 
929 	cvs_log(LP_TRACE, "cvs_file_copy(%s,%s)", from, to);
930 
931 	if (cvs_noexec == 1)
932 		return (0);
933 
934 	if ((src = open(from, O_RDONLY, 0)) == -1)
935 		fatal("cvs_file_copy: open: `%s': %s", from, strerror(errno));
936 
937 	if (fstat(src, &st) == -1)
938 		fatal("cvs_file_copy: `%s': %s", from, strerror(errno));
939 
940 	atime = st.st_atimespec.tv_sec;
941 	mtime = st.st_mtimespec.tv_sec;
942 
943 	if (S_ISREG(st.st_mode)) {
944 		size_t sz;
945 		ssize_t nw;
946 		char *p, *buf;
947 		int saved_errno;
948 
949 		if (st.st_size > SIZE_MAX) {
950 			ret = -1;
951 			goto out;
952 		}
953 
954 		if ((dst = open(to, O_CREAT|O_TRUNC|O_WRONLY,
955 		    st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO))) == -1)
956 			fatal("cvs_file_copy: open `%s': %s",
957 			    to, strerror(errno));
958 
959 		if ((p = mmap(NULL, st.st_size, PROT_READ,
960 		    MAP_FILE, src, (off_t)0)) == MAP_FAILED) {
961 			saved_errno = errno;
962 			(void)unlink(to);
963 			fatal("cvs_file_copy: mmap: %s", strerror(saved_errno));
964 		}
965 
966 		madvise(p, st.st_size, MADV_SEQUENTIAL);
967 
968 		sz = st.st_size;
969 		buf = p;
970 
971 		while (sz > 0) {
972 			if ((nw = write(dst, p, sz)) == -1) {
973 				saved_errno = errno;
974 				(void)unlink(to);
975 				fatal("cvs_file_copy: `%s': %s",
976 				    from, strerror(saved_errno));
977 			}
978 			buf += nw;
979 			sz -= nw;
980 		}
981 
982 		(void)munmap(p, st.st_size);
983 
984 		tv[0].tv_sec = atime;
985 		tv[1].tv_sec = mtime;
986 
987 		if (futimes(dst, tv) == -1) {
988 			saved_errno = errno;
989 			(void)unlink(to);
990 			fatal("cvs_file_copy: futimes: %s",
991 			    strerror(saved_errno));
992 		}
993 		(void)close(dst);
994 	}
995 out:
996 	(void)close(src);
997 
998 	return (ret);
999 }
1000