co.c revision 1.103
1/*	$OpenBSD: co.c,v 1.103 2007/02/21 18:12:36 niallo Exp $	*/
2/*
3 * Copyright (c) 2005 Joris Vink <joris@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 "includes.h"
28
29#include "rcsprog.h"
30#include "diff.h"
31
32#define CO_OPTSTRING	"d:f::I::k:l::M::p::q::r::s:Tu::Vw::x::z::"
33
34static void	checkout_err_nobranch(RCSFILE *, const char *, const char *,
35    const char *, int);
36static int	checkout_file_has_diffs(RCSFILE *, RCSNUM *, const char *);
37
38int
39checkout_main(int argc, char **argv)
40{
41	int fd, i, ch, flags, kflag, status;
42	RCSNUM *rev;
43	RCSFILE *file;
44	const char *author, *date, *state;
45	char fpath[MAXPATHLEN];
46	char *rev_str, *username;
47	time_t rcs_mtime = -1;
48
49	flags = status = 0;
50	kflag = RCS_KWEXP_ERR;
51	rev = RCS_HEAD_REV;
52	rev_str = NULL;
53	author = date = state = NULL;
54
55	while ((ch = rcs_getopt(argc, argv, CO_OPTSTRING)) != -1) {
56		switch (ch) {
57		case 'd':
58			date = rcs_optarg;
59			break;
60		case 'f':
61			rcs_setrevstr(&rev_str, rcs_optarg);
62			flags |= FORCE;
63			break;
64		case 'I':
65			rcs_setrevstr(&rev_str, rcs_optarg);
66			flags |= INTERACTIVE;
67			break;
68
69		case 'k':
70			kflag = rcs_kflag_get(rcs_optarg);
71			if (RCS_KWEXP_INVAL(kflag)) {
72				warnx("invalid RCS keyword substitution mode");
73				(usage)();
74				exit(1);
75			}
76			break;
77		case 'l':
78			if (flags & CO_UNLOCK) {
79				warnx("warning: -u overridden by -l");
80				flags &= ~CO_UNLOCK;
81			}
82			rcs_setrevstr(&rev_str, rcs_optarg);
83			flags |= CO_LOCK;
84			break;
85		case 'M':
86			rcs_setrevstr(&rev_str, rcs_optarg);
87			flags |= CO_REVDATE;
88			break;
89		case 'p':
90			rcs_setrevstr(&rev_str, rcs_optarg);
91			flags |= PIPEOUT;
92			break;
93		case 'q':
94			rcs_setrevstr(&rev_str, rcs_optarg);
95			flags |= QUIET;
96			break;
97		case 'r':
98			rcs_setrevstr(&rev_str, rcs_optarg);
99			break;
100		case 's':
101			state = rcs_optarg;
102			flags |= CO_STATE;
103			break;
104		case 'T':
105			flags |= PRESERVETIME;
106			break;
107		case 'u':
108			rcs_setrevstr(&rev_str, rcs_optarg);
109			if (flags & CO_LOCK) {
110				warnx("warning: -l overridden by -u");
111				flags &= ~CO_LOCK;
112			}
113			flags |= CO_UNLOCK;
114			break;
115		case 'V':
116			printf("%s\n", rcs_version);
117			exit(0);
118		case 'w':
119			/* if no argument, assume current user */
120			if (rcs_optarg == NULL) {
121				if ((author = getlogin()) == NULL)
122					err(1, "getlogin");
123			} else
124				author = rcs_optarg;
125			flags |= CO_AUTHOR;
126			break;
127		case 'x':
128			/* Use blank extension if none given. */
129			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
130			break;
131		case 'z':
132			timezone_flag = rcs_optarg;
133			break;
134		default:
135			(usage)();
136			exit(1);
137		}
138	}
139
140	argc -= rcs_optind;
141	argv += rcs_optind;
142
143	if (argc == 0) {
144		warnx("no input file");
145		(usage)();
146		exit (1);
147	}
148
149	if ((username = getlogin()) == NULL)
150		err(1, "getlogin");
151
152	for (i = 0; i < argc; i++) {
153		fd = rcs_choosefile(argv[i], fpath, sizeof(fpath));
154		if (fd < 0) {
155			warn("%s", fpath);
156			continue;
157		}
158
159		if (!(flags & QUIET))
160			(void)fprintf(stderr, "%s  -->  %s\n", fpath,
161			    (flags & PIPEOUT) ? "standard output" : argv[i]);
162
163		if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) {
164			warnx("%s: cannot combine -kv and -l", fpath);
165			(void)close(fd);
166			continue;
167		}
168
169		if ((file = rcs_open(fpath, fd,
170		    RCS_RDWR|RCS_PARSE_FULLY)) == NULL)
171			continue;
172
173		if (flags & PRESERVETIME)
174			rcs_mtime = rcs_get_mtime(file);
175
176		rcs_kwexp_set(file, kflag);
177
178		if (rev_str != NULL) {
179			if ((rev = rcs_getrevnum(rev_str, file)) == NULL)
180				errx(1, "invalid revision: %s", rev_str);
181		} else {
182			/* no revisions in RCS file, generate empty 0.0 */
183			if (file->rf_ndelta == 0) {
184				rev = rcsnum_parse("0.0");
185				if (rev == NULL)
186					errx(1, "failed to generate rev 0.0");
187			} else {
188				rev = rcsnum_alloc();
189				rcsnum_cpy(file->rf_head, rev, 0);
190			}
191		}
192
193		if ((status = checkout_rev(file, rev, argv[i], flags,
194		    username, author, state, date)) < 0) {
195			rcs_close(file);
196			rcsnum_free(rev);
197			continue;
198		}
199
200		if (!(flags & QUIET))
201			(void)fprintf(stderr, "done\n");
202
203		rcsnum_free(rev);
204
205		rcs_write(file);
206		if (flags & PRESERVETIME)
207			rcs_set_mtime(file, rcs_mtime);
208		rcs_close(file);
209	}
210
211	return (status);
212}
213
214void
215checkout_usage(void)
216{
217	fprintf(stderr,
218	    "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n"
219	    "          [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n"
220	    "          [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n");
221}
222
223/*
224 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst>
225 * Currently recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE.
226 *
227 * Looks up revision based upon <lockname>, <author>, <state> and <date>
228 *
229 * Returns 0 on success, -1 on failure.
230 */
231int
232checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
233    const char *lockname, const char *author, const char *state,
234    const char *date)
235{
236	BUF *bp;
237	u_int i;
238	int fd, lcount;
239	char buf[16];
240	mode_t mode = DEFFILEMODE;
241	struct stat st;
242	struct rcs_delta *rdp;
243	struct rcs_lock *lkp;
244	char *fdate;
245	const char *fstatus;
246	time_t rcsdate, givendate;
247	RCSNUM *rev;
248
249	rcsdate = givendate = -1;
250	if (date != NULL)
251		givendate = rcs_date_parse(date);
252
253	if (file->rf_ndelta == 0 && !(flags & QUIET))
254		(void)fprintf(stderr,
255		    "no revisions present; generating empty revision 0.0\n");
256
257	/* XXX rcsnum_cmp()
258	 * Check out the latest revision if <frev> is greater than HEAD
259	 */
260	if (file->rf_ndelta != 0) {
261		for (i = 0; i < file->rf_head->rn_len; i++) {
262			if (file->rf_head->rn_id[i] < frev->rn_id[i]) {
263				frev = file->rf_head;
264				break;
265			}
266		}
267	}
268
269	lcount = 0;
270	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
271		if (!strcmp(lkp->rl_name, lockname))
272			lcount++;
273	}
274
275	/*
276	 * If the user didn't specify any revision, we cycle through
277	 * revisions to lookup the first one that matches what he specified.
278	 *
279	 * If we cannot find one, we return an error.
280	 */
281	rdp = NULL;
282	if (file->rf_ndelta != 0 && frev == file->rf_head) {
283		if (lcount > 1) {
284			warnx("multiple revisions locked by %s; "
285			    "please specify one", lockname);
286			return (-1);
287		}
288
289		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) {
290			if (date != NULL) {
291				fdate = asctime(&rdp->rd_date);
292				rcsdate = rcs_date_parse(fdate);
293				if (givendate <= rcsdate)
294					continue;
295			}
296
297			if (author != NULL &&
298			    strcmp(rdp->rd_author, author))
299				continue;
300
301			if (state != NULL &&
302			    strcmp(rdp->rd_state, state))
303				continue;
304
305			frev = rdp->rd_num;
306			break;
307		}
308	} else if (file->rf_ndelta != 0) {
309		rdp = rcs_findrev(file, frev);
310	}
311
312	if (file->rf_ndelta != 0 && rdp == NULL) {
313		checkout_err_nobranch(file, author, date, state, flags);
314		return (-1);
315	}
316
317	if (file->rf_ndelta == 0)
318		rev = frev;
319	else
320		rev = rdp->rd_num;
321
322	rcsnum_tostr(rev, buf, sizeof(buf));
323
324	if (file->rf_ndelta != 0 && rdp->rd_locker != NULL) {
325		if (strcmp(lockname, rdp->rd_locker)) {
326			warnx("Revision %s is already locked by %s; %s",
327			    buf, rdp->rd_locker,
328			    (flags & CO_UNLOCK) ? "use co -r or rcs -u" : "");
329			return (-1);
330		}
331	}
332
333	if (!(flags & QUIET) && !(flags & NEWFILE) &&
334	    !(flags & CO_REVERT) && file->rf_ndelta != 0)
335		(void)fprintf(stderr, "revision %s", buf);
336
337	if (file->rf_ndelta != 0) {
338		if ((bp = rcs_getrev(file, rev)) == NULL) {
339			warnx("cannot find revision `%s'", buf);
340			return (-1);
341		}
342	} else {
343		bp = rcs_buf_alloc(1, 0);
344	}
345
346	/*
347	 * Do keyword expansion if required.
348	 */
349	if (file->rf_ndelta != 0)
350		bp = rcs_kwexp_buf(bp, file, rev);
351	/*
352	 * File inherits permissions from its ,v file
353	 */
354	if (file->rf_fd != -1) {
355		if (fstat(file->rf_fd, &st) == -1)
356			err(1, "%s", file->rf_path);
357		file->rf_mode = mode = st.st_mode;
358	} else {
359		mode = file->rf_mode;
360	}
361
362	if (flags & CO_LOCK) {
363		if (file->rf_ndelta != 0) {
364			if (lockname != NULL &&
365			    rcs_lock_add(file, lockname, rev) < 0) {
366				if (rcs_errno != RCS_ERR_DUPENT)
367					return (-1);
368			}
369		}
370
371		/* File should only be writable by owner. */
372		mode &= ~(S_IWGRP|S_IWOTH);
373		mode |= S_IWUSR;
374
375		if (file->rf_ndelta != 0) {
376			if (!(flags & QUIET) && !(flags & NEWFILE) &&
377			    !(flags & CO_REVERT))
378				(void)fprintf(stderr, " (locked)");
379		}
380	} else if (flags & CO_UNLOCK) {
381		if (file->rf_ndelta != 0) {
382			if (rcs_lock_remove(file, lockname, rev) < 0) {
383				if (rcs_errno != RCS_ERR_NOENT)
384					return (-1);
385			}
386		}
387
388		/* Strip all write bits from mode */
389		mode &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
390
391		if (file->rf_ndelta != 0) {
392			if (!(flags & QUIET) && !(flags & NEWFILE) &&
393			    !(flags & CO_REVERT))
394				(void)fprintf(stderr, " (unlocked)");
395		}
396	}
397
398	if (file->rf_ndelta == 0 && !(flags & QUIET) &&
399	    ((flags & CO_LOCK) || (flags & CO_UNLOCK))) {
400		(void)fprintf(stderr, "no revisions, so nothing can be %s\n",
401		    (flags & CO_LOCK) ? "locked" : "unlocked");
402	} else if (file->rf_ndelta != 0) {
403		/* XXX - Not a good way to detect if a newline is needed. */
404		if (!(flags & QUIET) && !(flags & NEWFILE) &&
405		    !(flags & CO_REVERT))
406			(void)fprintf(stderr, "\n");
407	}
408
409	if (flags & CO_LOCK) {
410		if (rcs_errno != RCS_ERR_DUPENT)
411			lcount++;
412		if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT))
413			warnx("%s: warning: You now have %d locks.",
414			    file->rf_path, lcount);
415	}
416
417	if ((flags & (PIPEOUT|FORCE)) == 0 && stat(dst, &st) != -1) {
418		/*
419		 * Prompt the user if the file is writable or the file is
420		 * not writable but is different from the RCS head version.
421		 * This is different from GNU which will silently overwrite
422		 * the file regardless of its contents so long as it is
423		 * read-only.
424		 */
425		if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
426			fstatus = "writable";
427		else if (checkout_file_has_diffs(file, frev, dst) != D_SAME)
428			fstatus = "modified";
429		else
430			fstatus = NULL;
431		if (fstatus) {
432			(void)fprintf(stderr, "%s %s exists%s; ", fstatus, dst,
433			    (getuid() == st.st_uid) ? "" :
434			    ", and you do not own it");
435			(void)fprintf(stderr, "remove it? [ny](n): ");
436			if (rcs_yesno('n') == 'n') {
437				if (!(flags & QUIET) && isatty(STDIN_FILENO))
438					warnx("%s %s exists; checkout aborted",
439					    fstatus, dst);
440				else
441					warnx("checkout aborted");
442				return (-1);
443			}
444		}
445	}
446
447	if (flags & PIPEOUT)
448		rcs_buf_write_fd(bp, STDOUT_FILENO);
449	else {
450		(void)unlink(dst);
451
452		if ((fd = open(dst, O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0)
453			err(1, "%s", dst);
454
455		if (rcs_buf_write_fd(bp, fd) < 0) {
456			warnx("failed to write revision to file");
457			rcs_buf_free(bp);
458			(void)close(fd);
459			return (-1);
460		}
461
462		if (fchmod(fd, mode) == -1)
463			warn("%s", dst);
464
465		if (flags & CO_REVDATE) {
466			struct timeval tv[2];
467			memset(&tv, 0, sizeof(tv));
468			tv[0].tv_sec = (long)rcs_rev_getdate(file, rev);
469			tv[1].tv_sec = tv[0].tv_sec;
470			if (futimes(fd, (const struct timeval *)&tv) < 0)
471				warn("utimes");
472		}
473
474		(void)close(fd);
475	}
476
477	rcs_buf_free(bp);
478
479	return (0);
480}
481
482/*
483 * checkout_err_nobranch()
484 *
485 * XXX - should handle the dates too.
486 */
487static void
488checkout_err_nobranch(RCSFILE *file, const char *author, const char *date,
489    const char *state, int flags)
490{
491	if (!(flags & CO_AUTHOR))
492		author = NULL;
493	if (!(flags & CO_STATE))
494		state = NULL;
495
496	warnx("%s: No revision on branch has%s%s%s%s%s%s.",
497	    file->rf_path,
498	    date ? " a date before " : "",
499	    date ? date : "",
500	    author ? " and author " + (date ? 0:4 ) : "",
501	    author ? author : "",
502	    state  ? " and state " + (date || author ? 0:4) : "",
503	    state  ? state : "");
504}
505
506/*
507 * checkout_file_has_diffs()
508 *
509 * Check for diffs between the working file and its current revision.
510 * Same return values as rcs_diffreg()
511 */
512static int
513checkout_file_has_diffs(RCSFILE *rfp, RCSNUM *frev, const char *dst)
514{
515	char *tempfile;
516	BUF *bp;
517	int ret;
518
519	tempfile = NULL;
520
521	if ((bp = rcs_getrev(rfp, frev)) == NULL) {
522		warnx("failed to load revision");
523		return (D_ERROR);
524	}
525	if ((bp = rcs_kwexp_buf(bp, rfp, frev)) == NULL) {
526		warnx("failed to expand tags");
527		return (D_ERROR);
528	}
529
530	(void)xasprintf(&tempfile, "%s/diff.XXXXXXXXXX", rcs_tmpdir);
531	rcs_buf_write_stmp(bp, tempfile);
532	rcs_buf_empty(bp);
533
534	diff_format = D_RCSDIFF;
535	ret = rcs_diffreg(dst, tempfile, bp, 0);
536
537	rcs_buf_free(bp);
538	unlink(tempfile);
539	xfree(tempfile);
540
541	return (ret);
542}
543