co.c revision 1.79
1/*	$OpenBSD: co.c,v 1.79 2006/04/19 06:53:41 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 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				cvs_log(LP_ERR,
72				    "invalid RCS keyword expansion mode");
73				(usage)();
74				exit(1);
75			}
76			break;
77		case 'l':
78			if (flags & CO_UNLOCK) {
79				cvs_log(LP_ERR, "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			pipeout = 1;
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 = xstrdup(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				cvs_log(LP_ERR, "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			/* NOTREACHED */
119		case 'w':
120			/* if no argument, assume current user */
121			if (rcs_optarg == NULL) {
122				if ((author = getlogin()) == NULL)
123					fatal("getlogin failed");
124			} else {
125				author = xstrdup(rcs_optarg);
126				warg = 1;
127			}
128			flags |= CO_AUTHOR;
129			break;
130		case 'x':
131			/* Use blank extension if none given. */
132			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
133			break;
134		case 'z':
135			timezone_flag = rcs_optarg;
136			break;
137		default:
138			(usage)();
139			exit(1);
140		}
141	}
142
143	argc -= rcs_optind;
144	argv += rcs_optind;
145
146	if (argc == 0) {
147		cvs_log(LP_ERR, "no input file");
148		(usage)();
149		exit (1);
150	}
151
152	if ((username = getlogin()) == NULL) {
153		cvs_log(LP_ERRNO, "failed to get username");
154		exit (1);
155	}
156
157	for (i = 0; i < argc; i++) {
158		if (rcs_statfile(argv[i], fpath, sizeof(fpath)) < 0)
159			continue;
160
161		if (!(flags & QUIET))
162			printf("%s  -->  %s\n", fpath,
163			    (pipeout == 1) ? "standard output" : argv[i]);
164
165		if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) {
166			cvs_log(LP_ERR, "%s: cannot combine -kv and -l", fpath);
167			continue;
168		}
169
170		if ((file = rcs_open(fpath, RCS_RDWR|RCS_PARSE_FULLY)) == NULL)
171			continue;
172
173		if (flags & PRESERVETIME)
174			rcs_mtime = rcs_get_mtime(file->rf_path);
175
176		rcs_kwexp_set(file, kflag);
177
178		if (rev_str != NULL) {
179			if ((rev = rcs_getrevnum(rev_str, file)) == NULL)
180				fatal("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					fatal("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			printf("done\n");
202
203		rcs_close(file);
204		rcsnum_free(rev);
205
206		if (flags & PRESERVETIME)
207			rcs_set_mtime(fpath, rcs_mtime);
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 * Currenly 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 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 = cvs_date_parse(date);
259
260	if (file->rf_ndelta == 0)
261		printf("no revisions present; generating empty revision 0.0\n");
262
263	/* XXX rcsnum_cmp()
264	 * Check out the latest revision if <frev> is greater than HEAD
265	 */
266	if (file->rf_ndelta != 0) {
267		for (i = 0; i < file->rf_head->rn_len; i++) {
268			if (file->rf_head->rn_id[i] < frev->rn_id[i]) {
269				frev = file->rf_head;
270				break;
271			}
272		}
273	}
274
275	lcount = 0;
276	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
277		if (!strcmp(lkp->rl_name, lockname))
278			lcount++;
279	}
280
281	/*
282	 * If the user didn't specify any revision, we cycle through
283	 * revisions to lookup the first one that matches what he specified.
284	 *
285	 * If we cannot find one, we return an error.
286	 */
287	rdp = NULL;
288	if (file->rf_ndelta != 0 && frev == file->rf_head) {
289		if (lcount > 1) {
290			cvs_log(LP_WARN,
291			    "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 = cvs_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			strlcpy(msg, "Revision %s is already locked by %s; ",
334			    sizeof(msg));
335
336			if (flags & CO_UNLOCK) {
337				strlcat(msg, "use co -r or rcs -u",
338				    sizeof(msg));
339			}
340
341			cvs_log(LP_ERR, msg, buf, rdp->rd_locker);
342			return (-1);
343		}
344	}
345
346	if (!(flags & QUIET) && !(flags & NEWFILE) &&
347	    !(flags & CO_REVERT) && file->rf_ndelta != 0)
348		printf("revision %s", buf);
349
350	if (!(flags & QUIET) && (flags & CO_REVERT))
351		printf("done");
352
353	if (file->rf_ndelta != 0) {
354		if ((bp = rcs_getrev(file, rev)) == NULL) {
355			cvs_log(LP_ERR, "cannot find revision `%s'", buf);
356			return (-1);
357		}
358	} else {
359		bp = cvs_buf_alloc(1, 0);
360	}
361
362	/*
363	 * Do keyword expansion if required.
364	 */
365	if (file->rf_ndelta != 0)
366		bp = rcs_kwexp_buf(bp, file, rev);
367
368	/*
369	 * File inherits permissions from its ,v file
370	 */
371	if (stat(file->rf_path, &st) == -1)
372		fatal("could not stat rcsfile");
373
374	mode = st.st_mode;
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		mode = st.st_mode &
387		    (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
388		mode |= S_IWUSR;
389
390		if (file->rf_ndelta != 0) {
391			if (!(flags & QUIET) && !(flags & NEWFILE) &&
392			    !(flags & CO_REVERT))
393				printf(" (locked)");
394		}
395	} else if (flags & CO_UNLOCK) {
396		if (file->rf_ndelta != 0) {
397			if (rcs_lock_remove(file, lockname, rev) < 0) {
398				if (rcs_errno != RCS_ERR_NOENT)
399					return (-1);
400			}
401		}
402
403		/* Strip all write bits from mode */
404		mode = st.st_mode &
405		    (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
406
407		if (file->rf_ndelta != 0) {
408			if (!(flags & QUIET) && !(flags & NEWFILE) &&
409			    !(flags & CO_REVERT))
410				printf(" (unlocked)");
411		}
412	}
413
414	if (file->rf_ndelta == 0 &&
415	    ((flags & CO_LOCK) || (flags & CO_UNLOCK))) {
416		cvs_log(LP_WARN, "no revisions, so nothing can be %s",
417		    (flags & CO_LOCK) ? "locked" : "unlocked");
418	} else if (file->rf_ndelta != 0) {
419		if (!(flags & QUIET) && !(flags & NEWFILE))
420			printf("\n");
421	}
422
423	if (flags & CO_LOCK) {
424		if (rcs_errno != RCS_ERR_DUPENT)
425			lcount++;
426		if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT))
427			cvs_log(LP_WARN, "%s: warning: You now have %d locks.",
428			    file->rf_path, lcount);
429	}
430
431	if (pipeout == 0 && stat(dst, &st) == 0 && !(flags & FORCE)) {
432		/*
433		 * XXX - Not sure what is "right".  If we go according
434		 * to GNU's behavior, an existing file with no writable
435		 * bits is overwritten without prompting the user.
436		 *
437		 * This is dangerous, so we always prompt.
438		 * Unfortunately this interferes with an unlocked
439		 * checkout followed by a locked checkout, which should
440		 * not prompt.  One (unimplemented) solution is to check
441		 * if the existing file is the same as the checked out
442		 * revision, and prompt if there are differences.
443		 */
444		if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
445			printf("writable ");
446		printf("%s exists%s; ", dst,
447		    (getuid() == st.st_uid) ? "" :
448		    ", and you do not own it");
449		printf("remove it? [ny](n): ");
450		/* default is n */
451		if (cvs_yesno() == -1) {
452			if (!(flags & QUIET) && isatty(STDIN_FILENO))
453				cvs_log(LP_ERR,
454				    "writable %s exists; checkout aborted",
455				    dst);
456			else
457				cvs_log(LP_ERR, "checkout aborted");
458			return (-1);
459		}
460	}
461
462	if (pipeout == 1) {
463		cvs_buf_putc(bp, '\0');
464		content = cvs_buf_release(bp);
465		printf("%s", content);
466		xfree(content);
467	} else {
468		if (cvs_buf_write(bp, dst, mode) < 0) {
469			cvs_log(LP_ERR, "failed to write revision to file");
470			cvs_buf_free(bp);
471			return (-1);
472		}
473		cvs_buf_free(bp);
474		if (flags & CO_REVDATE) {
475			struct timeval tv[2];
476			memset(&tv, 0, sizeof(tv));
477			tv[0].tv_sec = (long)rcs_rev_getdate(file, rev);
478			tv[1].tv_sec = tv[0].tv_sec;
479			if (utimes(dst, (const struct timeval *)&tv) < 0)
480				cvs_log(LP_ERRNO, "error setting utimes");
481		}
482	}
483
484	return (0);
485}
486
487/*
488 * checkout_err_nobranch()
489 *
490 * XXX - should handle the dates too.
491 */
492static void
493checkout_err_nobranch(RCSFILE *file, const char *author, const char *date,
494    const char *state, int flags)
495{
496	if (!(flags & CO_AUTHOR))
497		author = NULL;
498	if (!(flags & CO_STATE))
499		state = NULL;
500
501	cvs_log(LP_ERR, "%s: No revision on branch has%s%s%s%s%s%s.",
502	    file->rf_path,
503	    date ? " a date before " : "",
504	    date ? date : "",
505	    author ? " and author " + (date ? 0:4 ) : "",
506	    author ? author : "",
507	    state  ? " and state " + (date || author ? 0:4) : "",
508	    state  ? state : "");
509}
510