co.c revision 1.85
1/*	$OpenBSD: co.c,v 1.85 2006/04/26 02:55:13 joris 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
31#define CO_OPTSTRING	"d:f::I::k:l::M::p::q::r::s:Tu::Vw::x::z::"
32
33static void	checkout_err_nobranch(RCSFILE *, const char *, const char *,
34    const char *, int);
35
36int
37checkout_main(int argc, char **argv)
38{
39	int i, ch, flags, kflag, status, warg;
40	RCSNUM *rev;
41	RCSFILE *file;
42	char fpath[MAXPATHLEN];
43	char *author, *date, *rev_str, *username, *state;
44	time_t rcs_mtime = -1;
45
46	warg = flags = status = 0;
47	kflag = RCS_KWEXP_ERR;
48	rev = RCS_HEAD_REV;
49	rev_str = NULL;
50	state = NULL;
51	author = NULL;
52	date = NULL;
53
54	while ((ch = rcs_getopt(argc, argv, CO_OPTSTRING)) != -1) {
55		switch (ch) {
56		case 'd':
57			date = xstrdup(rcs_optarg);
58			break;
59		case 'f':
60			rcs_setrevstr(&rev_str, rcs_optarg);
61			flags |= FORCE;
62			break;
63		case 'I':
64			rcs_setrevstr(&rev_str, rcs_optarg);
65			flags |= INTERACTIVE;
66			break;
67
68		case 'k':
69			kflag = rcs_kflag_get(rcs_optarg);
70			if (RCS_KWEXP_INVAL(kflag)) {
71				warnx("invalid RCS keyword substitution mode");
72				(usage)();
73				exit(1);
74			}
75			break;
76		case 'l':
77			if (flags & CO_UNLOCK) {
78				warnx("warning: -u overridden by -l");
79				flags &= ~CO_UNLOCK;
80			}
81			rcs_setrevstr(&rev_str, rcs_optarg);
82			flags |= CO_LOCK;
83			break;
84		case 'M':
85			rcs_setrevstr(&rev_str, rcs_optarg);
86			flags |= CO_REVDATE;
87			break;
88		case 'p':
89			rcs_setrevstr(&rev_str, rcs_optarg);
90			flags |= PIPEOUT;
91			break;
92		case 'q':
93			rcs_setrevstr(&rev_str, rcs_optarg);
94			flags |= QUIET;
95			break;
96		case 'r':
97			rcs_setrevstr(&rev_str, rcs_optarg);
98			break;
99		case 's':
100			state = xstrdup(rcs_optarg);
101			flags |= CO_STATE;
102			break;
103		case 'T':
104			flags |= PRESERVETIME;
105			break;
106		case 'u':
107			rcs_setrevstr(&rev_str, rcs_optarg);
108			if (flags & CO_LOCK) {
109				warnx("warning: -l overridden by -u");
110				flags &= ~CO_LOCK;
111			}
112			flags |= CO_UNLOCK;
113			break;
114		case 'V':
115			printf("%s\n", rcs_version);
116			exit(0);
117			/* NOTREACHED */
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 = xstrdup(rcs_optarg);
125				warg = 1;
126			}
127			flags |= CO_AUTHOR;
128			break;
129		case 'x':
130			/* Use blank extension if none given. */
131			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
132			break;
133		case 'z':
134			timezone_flag = rcs_optarg;
135			break;
136		default:
137			(usage)();
138			exit(1);
139		}
140	}
141
142	argc -= rcs_optind;
143	argv += rcs_optind;
144
145	if (argc == 0) {
146		warnx("no input file");
147		(usage)();
148		exit (1);
149	}
150
151	if ((username = getlogin()) == NULL)
152		err(1, "getlogin");
153
154	for (i = 0; i < argc; i++) {
155		if (rcs_statfile(argv[i], fpath, sizeof(fpath), flags) < 0)
156			continue;
157
158		if (!(flags & QUIET))
159			printf("%s  -->  %s\n", fpath,
160			    (flags & PIPEOUT) ? "standard output" : argv[i]);
161
162		if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) {
163			warnx("%s: cannot combine -kv and -l", fpath);
164			continue;
165		}
166
167		if ((file = rcs_open(fpath, RCS_RDWR|RCS_PARSE_FULLY)) == NULL)
168			continue;
169
170		if (flags & PRESERVETIME)
171			rcs_mtime = rcs_get_mtime(file->rf_path);
172
173		rcs_kwexp_set(file, kflag);
174
175		if (rev_str != NULL) {
176			if ((rev = rcs_getrevnum(rev_str, file)) == NULL)
177				errx(1, "invalid revision: %s", rev_str);
178		} else {
179			/* no revisions in RCS file, generate empty 0.0 */
180			if (file->rf_ndelta == 0) {
181				rev = rcsnum_parse("0.0");
182				if (rev == NULL)
183					errx(1, "failed to generate rev 0.0");
184			} else {
185				rev = rcsnum_alloc();
186				rcsnum_cpy(file->rf_head, rev, 0);
187			}
188		}
189
190		if ((status = checkout_rev(file, rev, argv[i], flags,
191		    username, author, state, date)) < 0) {
192			rcs_close(file);
193			rcsnum_free(rev);
194			continue;
195		}
196
197		if (!(flags & QUIET))
198			printf("done\n");
199
200		rcs_close(file);
201		rcsnum_free(rev);
202
203		if (flags & PRESERVETIME)
204			rcs_set_mtime(fpath, rcs_mtime);
205	}
206
207	if (author != NULL && warg)
208		xfree(author);
209
210	if (date != NULL)
211		xfree(date);
212
213	if (state != NULL)
214		xfree(state);
215
216	return (status);
217}
218
219void
220checkout_usage(void)
221{
222	fprintf(stderr,
223	    "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n"
224	    "          [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n"
225	    "          [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n");
226}
227
228/*
229 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst>
230 * Currenly recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE.
231 *
232 * Looks up revision based upon <lockname>, <author>, <state> and <date>
233 *
234 * Returns 0 on success, -1 on failure.
235 */
236int
237checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
238    const char *lockname, const char *author, const char *state,
239    const char *date)
240{
241	BUF *bp;
242	u_int i;
243	int lcount;
244	char buf[16];
245	mode_t mode = 0444;
246	struct stat st;
247	struct rcs_delta *rdp;
248	struct rcs_lock *lkp;
249	char *content, msg[128], *fdate;
250	time_t rcsdate, givendate;
251	RCSNUM *rev;
252
253	rcsdate = givendate = -1;
254	if (date != NULL)
255		givendate = rcs_date_parse(date);
256
257	if (file->rf_ndelta == 0)
258		printf("no revisions present; generating empty revision 0.0\n");
259
260	/* XXX rcsnum_cmp()
261	 * Check out the latest revision if <frev> is greater than HEAD
262	 */
263	if (file->rf_ndelta != 0) {
264		for (i = 0; i < file->rf_head->rn_len; i++) {
265			if (file->rf_head->rn_id[i] < frev->rn_id[i]) {
266				frev = file->rf_head;
267				break;
268			}
269		}
270	}
271
272	lcount = 0;
273	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
274		if (!strcmp(lkp->rl_name, lockname))
275			lcount++;
276	}
277
278	/*
279	 * If the user didn't specify any revision, we cycle through
280	 * revisions to lookup the first one that matches what he specified.
281	 *
282	 * If we cannot find one, we return an error.
283	 */
284	rdp = NULL;
285	if (file->rf_ndelta != 0 && frev == file->rf_head) {
286		if (lcount > 1) {
287			warnx("multiple revisions locked by %s; "
288			    "please specify one", lockname);
289			return (-1);
290		}
291
292		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) {
293			if (date != NULL) {
294				fdate = asctime(&rdp->rd_date);
295				rcsdate = rcs_date_parse(fdate);
296				if (givendate <= rcsdate)
297					continue;
298			}
299
300			if (author != NULL &&
301			    strcmp(rdp->rd_author, author))
302				continue;
303
304			if (state != NULL &&
305			    strcmp(rdp->rd_state, state))
306				continue;
307
308			frev = rdp->rd_num;
309			break;
310		}
311	} else if (file->rf_ndelta != 0) {
312		rdp = rcs_findrev(file, frev);
313	}
314
315	if (file->rf_ndelta != 0 && rdp == NULL) {
316		checkout_err_nobranch(file, author, date, state, flags);
317		return (-1);
318	}
319
320	if (file->rf_ndelta == 0)
321		rev = frev;
322	else
323		rev = rdp->rd_num;
324
325	rcsnum_tostr(rev, buf, sizeof(buf));
326
327	if (file->rf_ndelta != 0 && rdp->rd_locker != NULL) {
328		if (strcmp(lockname, rdp->rd_locker)) {
329			strlcpy(msg, "Revision %s is already locked by %s; ",
330			    sizeof(msg));
331
332			if (flags & CO_UNLOCK) {
333				strlcat(msg, "use co -r or rcs -u",
334				    sizeof(msg));
335			}
336
337			warnx(msg, buf, rdp->rd_locker);
338			return (-1);
339		}
340	}
341
342	if (!(flags & QUIET) && !(flags & NEWFILE) &&
343	    !(flags & CO_REVERT) && file->rf_ndelta != 0)
344		printf("revision %s", buf);
345
346	if (!(flags & QUIET) && (flags & CO_REVERT))
347		printf("done");
348
349	if (file->rf_ndelta != 0) {
350		if ((bp = rcs_getrev(file, rev)) == NULL) {
351			warnx("cannot find revision `%s'", buf);
352			return (-1);
353		}
354	} else {
355		bp = rcs_buf_alloc(1, 0);
356	}
357
358	/*
359	 * Do keyword expansion if required.
360	 */
361	if (file->rf_ndelta != 0)
362		bp = rcs_kwexp_buf(bp, file, rev);
363
364	/*
365	 * File inherits permissions from its ,v file
366	 */
367	if (stat(file->rf_path, &st) == -1)
368		err(1, "%s", file->rf_path);
369
370	mode = st.st_mode;
371
372	if (flags & CO_LOCK) {
373		if (file->rf_ndelta != 0) {
374			if (lockname != NULL &&
375			    rcs_lock_add(file, lockname, rev) < 0) {
376				if (rcs_errno != RCS_ERR_DUPENT)
377					return (-1);
378			}
379		}
380
381		/* Strip all write bits from mode */
382		mode = st.st_mode &
383		    (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
384		mode |= S_IWUSR;
385
386		if (file->rf_ndelta != 0) {
387			if (!(flags & QUIET) && !(flags & NEWFILE) &&
388			    !(flags & CO_REVERT))
389				printf(" (locked)");
390		}
391	} else if (flags & CO_UNLOCK) {
392		if (file->rf_ndelta != 0) {
393			if (rcs_lock_remove(file, lockname, rev) < 0) {
394				if (rcs_errno != RCS_ERR_NOENT)
395					return (-1);
396			}
397		}
398
399		/* Strip all write bits from mode */
400		mode = st.st_mode &
401		    (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
402
403		if (file->rf_ndelta != 0) {
404			if (!(flags & QUIET) && !(flags & NEWFILE) &&
405			    !(flags & CO_REVERT))
406				printf(" (unlocked)");
407		}
408	}
409
410	if (file->rf_ndelta == 0 &&
411	    ((flags & CO_LOCK) || (flags & CO_UNLOCK))) {
412		warnx("no revisions, so nothing can be %s",
413		    (flags & CO_LOCK) ? "locked" : "unlocked");
414	} else if (file->rf_ndelta != 0) {
415		if (!(flags & QUIET) && !(flags & NEWFILE))
416			printf("\n");
417	}
418
419	if (flags & CO_LOCK) {
420		if (rcs_errno != RCS_ERR_DUPENT)
421			lcount++;
422		if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT))
423			warnx("%s: warning: You now have %d locks.",
424			    file->rf_path, lcount);
425	}
426
427	if (!(flags & PIPEOUT) && stat(dst, &st) == 0 && !(flags & FORCE)) {
428		/*
429		 * XXX - Not sure what is "right".  If we go according
430		 * to GNU's behavior, an existing file with no writable
431		 * bits is overwritten without prompting the user.
432		 *
433		 * This is dangerous, so we always prompt.
434		 * Unfortunately this interferes with an unlocked
435		 * checkout followed by a locked checkout, which should
436		 * not prompt.  One (unimplemented) solution is to check
437		 * if the existing file is the same as the checked out
438		 * revision, and prompt if there are differences.
439		 */
440		if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
441			printf("writable ");
442		printf("%s exists%s; ", dst,
443		    (getuid() == st.st_uid) ? "" :
444		    ", and you do not own it");
445		printf("remove it? [ny](n): ");
446		/* default is n */
447		if (rcs_yesno() == -1) {
448			if (!(flags & QUIET) && isatty(STDIN_FILENO))
449				warnx("writable %s exists; "
450				    "checkout aborted", dst);
451			else
452				warnx("checkout aborted");
453			return (-1);
454		}
455	}
456
457	if (flags & PIPEOUT) {
458		rcs_buf_putc(bp, '\0');
459		content = rcs_buf_release(bp);
460		printf("%s", content);
461		xfree(content);
462	} else {
463		if (rcs_buf_write(bp, dst, mode) < 0) {
464			warnx("failed to write revision to file");
465			rcs_buf_free(bp);
466			return (-1);
467		}
468		rcs_buf_free(bp);
469		if (flags & CO_REVDATE) {
470			struct timeval tv[2];
471			memset(&tv, 0, sizeof(tv));
472			tv[0].tv_sec = (long)rcs_rev_getdate(file, rev);
473			tv[1].tv_sec = tv[0].tv_sec;
474			if (utimes(dst, (const struct timeval *)&tv) < 0)
475				warn("utimes");
476		}
477	}
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