co.c revision 1.91
1/*	$OpenBSD: co.c,v 1.91 2006/05/17 19:37:40 xsa 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 fd, 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		case 'w':
118			/* if no argument, assume current user */
119			if (rcs_optarg == NULL) {
120				if ((author = getlogin()) == NULL)
121					err(1, "getlogin");
122			} else {
123				author = xstrdup(rcs_optarg);
124				warg = 1;
125			}
126			flags |= CO_AUTHOR;
127			break;
128		case 'x':
129			/* Use blank extension if none given. */
130			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
131			break;
132		case 'z':
133			timezone_flag = rcs_optarg;
134			break;
135		default:
136			(usage)();
137			exit(1);
138		}
139	}
140
141	argc -= rcs_optind;
142	argv += rcs_optind;
143
144	if (argc == 0) {
145		warnx("no input file");
146		(usage)();
147		exit (1);
148	}
149
150	if ((username = getlogin()) == NULL)
151		err(1, "getlogin");
152
153	for (i = 0; i < argc; i++) {
154		fd = rcs_statfile(argv[i], fpath, sizeof(fpath), flags);
155		if (fd < 0)
156			continue;
157
158		if (!(flags & QUIET))
159			(void)fprintf(stderr, "%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			(void)close(fd);
165			continue;
166		}
167
168		if ((file = rcs_open(fpath, fd,
169		    RCS_RDWR|RCS_PARSE_FULLY)) == NULL)
170			continue;
171
172		if (flags & PRESERVETIME)
173			rcs_mtime = rcs_get_mtime(file);
174
175		rcs_kwexp_set(file, kflag);
176
177		if (rev_str != NULL) {
178			if ((rev = rcs_getrevnum(rev_str, file)) == NULL)
179				errx(1, "invalid revision: %s", rev_str);
180		} else {
181			/* no revisions in RCS file, generate empty 0.0 */
182			if (file->rf_ndelta == 0) {
183				rev = rcsnum_parse("0.0");
184				if (rev == NULL)
185					errx(1, "failed to generate rev 0.0");
186			} else {
187				rev = rcsnum_alloc();
188				rcsnum_cpy(file->rf_head, rev, 0);
189			}
190		}
191
192		if ((status = checkout_rev(file, rev, argv[i], flags,
193		    username, author, state, date)) < 0) {
194			rcs_close(file);
195			rcsnum_free(rev);
196			continue;
197		}
198
199		if (!(flags & QUIET))
200			(void)fprintf(stderr, "done\n");
201
202		rcsnum_free(rev);
203
204		rcs_write(file);
205		if (flags & PRESERVETIME)
206			rcs_set_mtime(file, rcs_mtime);
207		rcs_close(file);
208	}
209
210	if (author != NULL && warg)
211		xfree(author);
212
213	if (date != NULL)
214		xfree(date);
215
216	if (state != NULL)
217		xfree(state);
218
219	return (status);
220}
221
222void
223checkout_usage(void)
224{
225	fprintf(stderr,
226	    "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n"
227	    "          [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n"
228	    "          [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n");
229}
230
231/*
232 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst>
233 * Currently recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE.
234 *
235 * Looks up revision based upon <lockname>, <author>, <state> and <date>
236 *
237 * Returns 0 on success, -1 on failure.
238 */
239int
240checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
241    const char *lockname, const char *author, const char *state,
242    const char *date)
243{
244	BUF *bp;
245	u_int i;
246	int fd, lcount;
247	char buf[16];
248	mode_t mode = 0444;
249	struct stat st;
250	struct rcs_delta *rdp;
251	struct rcs_lock *lkp;
252	char *content, msg[128], *fdate;
253	time_t rcsdate, givendate;
254	RCSNUM *rev;
255
256	rcsdate = givendate = -1;
257	if (date != NULL)
258		givendate = rcs_date_parse(date);
259
260	if (file->rf_ndelta == 0 && !(flags & QUIET))
261		(void)fprintf(stderr,
262		    "no revisions present; generating empty revision 0.0\n");
263
264	/* XXX rcsnum_cmp()
265	 * Check out the latest revision if <frev> is greater than HEAD
266	 */
267	if (file->rf_ndelta != 0) {
268		for (i = 0; i < file->rf_head->rn_len; i++) {
269			if (file->rf_head->rn_id[i] < frev->rn_id[i]) {
270				frev = file->rf_head;
271				break;
272			}
273		}
274	}
275
276	lcount = 0;
277	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
278		if (!strcmp(lkp->rl_name, lockname))
279			lcount++;
280	}
281
282	/*
283	 * If the user didn't specify any revision, we cycle through
284	 * revisions to lookup the first one that matches what he specified.
285	 *
286	 * If we cannot find one, we return an error.
287	 */
288	rdp = NULL;
289	if (file->rf_ndelta != 0 && frev == file->rf_head) {
290		if (lcount > 1) {
291			warnx("multiple revisions locked by %s; "
292			    "please specify one", lockname);
293			return (-1);
294		}
295
296		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) {
297			if (date != NULL) {
298				fdate = asctime(&rdp->rd_date);
299				rcsdate = rcs_date_parse(fdate);
300				if (givendate <= rcsdate)
301					continue;
302			}
303
304			if (author != NULL &&
305			    strcmp(rdp->rd_author, author))
306				continue;
307
308			if (state != NULL &&
309			    strcmp(rdp->rd_state, state))
310				continue;
311
312			frev = rdp->rd_num;
313			break;
314		}
315	} else if (file->rf_ndelta != 0) {
316		rdp = rcs_findrev(file, frev);
317	}
318
319	if (file->rf_ndelta != 0 && rdp == NULL) {
320		checkout_err_nobranch(file, author, date, state, flags);
321		return (-1);
322	}
323
324	if (file->rf_ndelta == 0)
325		rev = frev;
326	else
327		rev = rdp->rd_num;
328
329	rcsnum_tostr(rev, buf, sizeof(buf));
330
331	if (file->rf_ndelta != 0 && rdp->rd_locker != NULL) {
332		if (strcmp(lockname, rdp->rd_locker)) {
333			if (strlcpy(msg, "Revision %s is already locked by %s; ",
334			    sizeof(msg)) >= sizeof(msg))
335				errx(1, "msg too long");
336
337			if (flags & CO_UNLOCK) {
338				if (strlcat(msg, "use co -r or rcs -u",
339				    sizeof(msg)) >= sizeof(msg))
340					errx(1, "msg too long");
341			}
342
343			warnx(msg, buf, rdp->rd_locker);
344			return (-1);
345		}
346	}
347
348	if (!(flags & QUIET) && !(flags & NEWFILE) &&
349	    !(flags & CO_REVERT) && file->rf_ndelta != 0)
350		(void)fprintf(stderr, "revision %s", buf);
351
352	if (file->rf_ndelta != 0) {
353		if ((bp = rcs_getrev(file, rev)) == NULL) {
354			warnx("cannot find revision `%s'", buf);
355			return (-1);
356		}
357	} else {
358		bp = rcs_buf_alloc(1, 0);
359	}
360
361	/*
362	 * Do keyword expansion if required.
363	 */
364	if (file->rf_ndelta != 0)
365		bp = rcs_kwexp_buf(bp, file, rev);
366
367	/*
368	 * File inherits permissions from its ,v file
369	 */
370	if (file->fd != -1) {
371		if (fstat(file->fd, &st) == -1)
372			err(1, "%s", file->rf_path);
373		mode = st.st_mode;
374	}
375
376	if (flags & CO_LOCK) {
377		if (file->rf_ndelta != 0) {
378			if (lockname != NULL &&
379			    rcs_lock_add(file, lockname, rev) < 0) {
380				if (rcs_errno != RCS_ERR_DUPENT)
381					return (-1);
382			}
383		}
384
385		/* Strip all write bits from mode */
386		if (file->fd != -1) {
387			mode = st.st_mode &
388			    (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
389		}
390
391		mode |= S_IWUSR;
392
393		if (file->rf_ndelta != 0) {
394			if (!(flags & QUIET) && !(flags & NEWFILE) &&
395			    !(flags & CO_REVERT))
396				(void)fprintf(stderr, " (locked)");
397		}
398	} else if (flags & CO_UNLOCK) {
399		if (file->rf_ndelta != 0) {
400			if (rcs_lock_remove(file, lockname, rev) < 0) {
401				if (rcs_errno != RCS_ERR_NOENT)
402					return (-1);
403			}
404		}
405
406		/* Strip all write bits from mode */
407		if (file->fd != -1) {
408			mode = st.st_mode &
409			    (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
410		}
411
412		if (file->rf_ndelta != 0) {
413			if (!(flags & QUIET) && !(flags & NEWFILE) &&
414			    !(flags & CO_REVERT))
415				(void)fprintf(stderr, " (unlocked)");
416		}
417	}
418
419	if (file->rf_ndelta == 0 && !(flags & QUIET) &&
420	    ((flags & CO_LOCK) || (flags & CO_UNLOCK))) {
421		(void)fprintf(stderr, "no revisions, so nothing can be %s\n",
422		    (flags & CO_LOCK) ? "locked" : "unlocked");
423	} else if (file->rf_ndelta != 0) {
424		/* XXX - Not a good way to detect if a newline is needed. */
425		if (!(flags & QUIET) && !(flags & NEWFILE) &&
426		    !(flags & CO_REVERT))
427			(void)fprintf(stderr, "\n");
428	}
429
430	if (flags & CO_LOCK) {
431		if (rcs_errno != RCS_ERR_DUPENT)
432			lcount++;
433		if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT))
434			warnx("%s: warning: You now have %d locks.",
435			    file->rf_path, lcount);
436	}
437
438	if (!(flags & PIPEOUT) && stat(dst, &st) != -1 && !(flags & FORCE)) {
439		/*
440		 * XXX - Not sure what is "right".  If we go according
441		 * to GNU's behavior, an existing file with no writable
442		 * bits is overwritten without prompting the user.
443		 *
444		 * This is dangerous, so we always prompt.
445		 * Unfortunately this interferes with an unlocked
446		 * checkout followed by a locked checkout, which should
447		 * not prompt.  One (unimplemented) solution is to check
448		 * if the existing file is the same as the checked out
449		 * revision, and prompt if there are differences.
450		 */
451		if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
452			(void)fprintf(stderr, "writable ");
453		(void)fprintf(stderr, "%s exists%s; ", dst,
454		    (getuid() == st.st_uid) ? "" :
455		    ", and you do not own it");
456		(void)fprintf(stderr, "remove it? [ny](n): ");
457		/* default is n */
458		if (rcs_yesno() == -1) {
459			if (!(flags & QUIET) && isatty(STDIN_FILENO))
460				warnx("writable %s exists; "
461				    "checkout aborted", dst);
462			else
463				warnx("checkout aborted");
464			return (-1);
465		}
466	}
467
468	if (flags & PIPEOUT) {
469		rcs_buf_putc(bp, '\0');
470		content = rcs_buf_release(bp);
471		printf("%s", content);
472		xfree(content);
473	} else {
474		(void)unlink(dst);
475
476		if ((fd = open(dst, O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0)
477			err(1, "%s", dst);
478
479		if (rcs_buf_write_fd(bp, fd) < 0) {
480			warnx("failed to write revision to file");
481			rcs_buf_free(bp);
482			(void)close(fd);
483			return (-1);
484		}
485
486		if (fchmod(fd, mode) == -1)
487			warn("%s", dst);
488
489		rcs_buf_free(bp);
490
491		if (flags & CO_REVDATE) {
492			struct timeval tv[2];
493			memset(&tv, 0, sizeof(tv));
494			tv[0].tv_sec = (long)rcs_rev_getdate(file, rev);
495			tv[1].tv_sec = tv[0].tv_sec;
496			if (futimes(fd, (const struct timeval *)&tv) < 0)
497				warn("utimes");
498		}
499
500		(void)close(fd);
501	}
502
503	return (0);
504}
505
506/*
507 * checkout_err_nobranch()
508 *
509 * XXX - should handle the dates too.
510 */
511static void
512checkout_err_nobranch(RCSFILE *file, const char *author, const char *date,
513    const char *state, int flags)
514{
515	if (!(flags & CO_AUTHOR))
516		author = NULL;
517	if (!(flags & CO_STATE))
518		state = NULL;
519
520	warnx("%s: No revision on branch has%s%s%s%s%s%s.",
521	    file->rf_path,
522	    date ? " a date before " : "",
523	    date ? date : "",
524	    author ? " and author " + (date ? 0:4 ) : "",
525	    author ? author : "",
526	    state  ? " and state " + (date || author ? 0:4) : "",
527	    state  ? state : "");
528}
529