co.c revision 1.53
1/*	$OpenBSD: co.c,v 1.53 2006/02/14 13:28:38 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::k:l::M::p::q::r::s:Tu::Vw::x:"
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;
40	RCSNUM *frev, *rev;
41	RCSFILE *file;
42	char fpath[MAXPATHLEN];
43	char *author, *username, *date;
44	const char *state;
45	time_t rcs_mtime = -1;
46
47	flags = 0;
48	kflag = RCS_KWEXP_ERR;
49	rev = RCS_HEAD_REV;
50	frev = NULL;
51	state = NULL;
52	author = NULL;
53	date = NULL;
54
55	while ((ch = rcs_getopt(argc, argv, CO_OPTSTRING)) != -1) {
56		switch (ch) {
57		case 'd':
58			date = xstrdup(rcs_optarg);
59			break;
60		case 'f':
61			rcs_set_rev(rcs_optarg, &rev);
62			flags |= FORCE;
63			break;
64		case 'k':
65			kflag = rcs_kflag_get(rcs_optarg);
66			if (RCS_KWEXP_INVAL(kflag)) {
67				cvs_log(LP_ERR,
68				    "invalid RCS keyword expansion mode");
69				(usage)();
70				exit(1);
71			}
72			break;
73		case 'l':
74			rcs_set_rev(rcs_optarg, &rev);
75			if (flags & CO_UNLOCK) {
76				cvs_log(LP_ERR, "warning: -u overridden by -l");
77				flags &= ~CO_UNLOCK;
78			}
79			flags |= CO_LOCK;
80			break;
81		case 'M':
82			rcs_set_rev(rcs_optarg, &rev);
83			flags |= CO_REVDATE;
84			break;
85		case 'p':
86			rcs_set_rev(rcs_optarg, &rev);
87			pipeout = 1;
88			break;
89		case 'q':
90			rcs_set_rev(rcs_optarg, &rev);
91			verbose = 0;
92			break;
93		case 'r':
94			rcs_set_rev(rcs_optarg, &rev);
95			break;
96		case 's':
97			state = xstrdup(rcs_optarg);
98			flags |= CO_STATE;
99			break;
100		case 'T':
101			flags |= PRESERVETIME;
102			break;
103		case 'u':
104			rcs_set_rev(rcs_optarg, &rev);
105			if (flags & CO_LOCK) {
106				cvs_log(LP_ERR, "warning: -l overridden by -u");
107				flags &= ~CO_LOCK;
108			}
109			flags |= CO_UNLOCK;
110			break;
111		case 'V':
112			printf("%s\n", rcs_version);
113			exit(0);
114		case 'w':
115			/* if no argument, assume current user */
116			if (rcs_optarg == NULL) {
117				if ((author = getlogin()) == NULL)
118					fatal("getlogin failed");
119			} else
120				author = xstrdup(rcs_optarg);
121			flags |= CO_AUTHOR;
122			break;
123		case 'x':
124			rcs_suffixes = rcs_optarg;
125			break;
126		default:
127			(usage)();
128			exit(1);
129		}
130	}
131
132	argc -= rcs_optind;
133	argv += rcs_optind;
134
135	if (argc == 0) {
136		cvs_log(LP_ERR, "no input file");
137		(usage)();
138		exit (1);
139	}
140
141	if ((username = getlogin()) == NULL) {
142		cvs_log(LP_ERRNO, "failed to get username");
143		exit (1);
144	}
145
146	for (i = 0; i < argc; i++) {
147		if (rcs_statfile(argv[i], fpath, sizeof(fpath)) < 0)
148			continue;
149
150		if (verbose == 1)
151			printf("%s  -->  %s\n", fpath,
152			    (pipeout == 1) ? "standard output" : argv[i]);
153
154		if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) {
155			cvs_log(LP_ERR, "%s: cannot combine -kv and -l", fpath);
156			continue;
157		}
158
159		if ((file = rcs_open(fpath, RCS_RDWR|RCS_PARSE_FULLY)) == NULL)
160			continue;
161
162		if (flags & PRESERVETIME)
163			rcs_mtime = rcs_get_mtime(file->rf_path);
164
165		if (kflag != RCS_KWEXP_ERR)
166			rcs_kwexp_set(file, kflag);
167
168		if (rev == RCS_HEAD_REV)
169			frev = file->rf_head;
170		else
171			frev = rev;
172
173		if (checkout_rev(file, frev, argv[i], flags,
174		    username, author, state, date) < 0) {
175				rcs_close(file);
176				continue;
177		}
178
179		rcs_close(file);
180
181		if (flags & PRESERVETIME)
182			rcs_set_mtime(fpath, rcs_mtime);
183	}
184
185	if (rev != RCS_HEAD_REV)
186		rcsnum_free(frev);
187
188	return (0);
189}
190
191void
192checkout_usage(void)
193{
194	fprintf(stderr,
195	    "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n"
196	    "          [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n"
197	    "          [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n");
198}
199
200/*
201 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst>
202 * Currenly recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE.
203 *
204 * Looks up revision based upon <lockname>, <author>, <state> and <date>
205 *
206 * Returns 0 on success, -1 on failure.
207 */
208int
209checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
210    const char *lockname, const char *author, const char *state,
211    const char *date)
212{
213	BUF *bp;
214	int lcount;
215	char buf[16], yn;
216	mode_t mode = 0444;
217	struct stat st;
218	struct rcs_delta *rdp;
219	struct rcs_lock *lkp;
220	char *content, msg[128], *fdate;
221	time_t rcsdate, givendate;
222
223	rcsdate = givendate = -1;
224	if (date != NULL)
225		givendate = cvs_date_parse(date);
226
227	/* Check out the latest revision if <frev> is greater than HEAD */
228	if (rcsnum_cmp(frev, file->rf_head, 0) == -1)
229		frev = file->rf_head;
230
231	lcount = 0;
232	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
233		if (!strcmp(lkp->rl_name, lockname))
234			lcount++;
235	}
236
237	/*
238	 * If the user didn't specify any revision, we cycle through
239	 * revisions to lookup the first one that matches what he specified.
240	 *
241	 * If we cannot find one, we return an error.
242	 */
243	rdp = NULL;
244	if (frev == file->rf_head) {
245		if (lcount > 1) {
246			cvs_log(LP_WARN,
247			    "multiple revisions locked by %s; "
248			    "please specify one", lockname);
249			return (-1);
250		}
251
252		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) {
253			if (date != NULL) {
254				fdate = asctime(&rdp->rd_date);
255				rcsdate = cvs_date_parse(fdate);
256				if (givendate <= rcsdate)
257					continue;
258			}
259
260			if ((author != NULL) &&
261			    (strcmp(rdp->rd_author, author)))
262				continue;
263
264			if ((state != NULL) &&
265			    (strcmp(rdp->rd_state, state)))
266				continue;
267
268			frev = rdp->rd_num;
269			break;
270		}
271	} else {
272		rdp = rcs_findrev(file, frev);
273	}
274
275	if (rdp == NULL) {
276		checkout_err_nobranch(file, author, date, state, flags);
277		return (-1);
278	}
279
280	rcsnum_tostr(frev, buf, sizeof(buf));
281
282	if (rdp->rd_locker != NULL) {
283		if (strcmp(lockname, rdp->rd_locker)) {
284			strlcpy(msg, "Revision %s is already locked by %s; ",
285			    sizeof(msg));
286			if (flags & CO_UNLOCK)
287				strlcat(msg, "use co -r or rcs -u", sizeof(msg));
288			cvs_log(LP_ERR, msg, buf, rdp->rd_locker);
289			return (-1);
290		}
291	}
292
293	if ((verbose == 1) && !(flags & NEWFILE))
294		printf("revision %s", buf);
295
296
297	if ((bp = rcs_getrev(file, frev)) == NULL) {
298		cvs_log(LP_ERR, "cannot find revision `%s'", buf);
299		return (-1);
300	}
301
302	if (flags & CO_LOCK) {
303		if ((lockname != NULL)
304		    && (rcs_lock_add(file, lockname, frev) < 0)) {
305			if (rcs_errno != RCS_ERR_DUPENT)
306				return (-1);
307		}
308
309		mode = 0644;
310		if ((verbose == 1) && !(flags & NEWFILE))
311			printf(" (locked)\n");
312	} else if (flags & CO_UNLOCK) {
313		if (rcs_lock_remove(file, lockname, frev) < 0) {
314			if (rcs_errno != RCS_ERR_NOENT)
315				return (-1);
316		}
317
318		mode = 0444;
319		if ((verbose == 1) && !(flags & NEWFILE))
320			printf(" (unlocked)\n");
321	}
322
323	if (flags & CO_LOCK) {
324		lcount++;
325		if (lcount > 1)
326			cvs_log(LP_WARN, "You now have %d locks.", lcount);
327	}
328
329	if ((pipeout == 0) && (stat(dst, &st) == 0) && !(flags & FORCE)) {
330		if (st.st_mode & S_IWUSR) {
331			yn = 0;
332			if (verbose == 0) {
333				cvs_log(LP_ERR,
334				    "writable %s exists; checkout aborted",
335				    dst);
336				return (-1);
337			}
338
339			while ((yn != 'y') && (yn != 'n')) {
340				printf("writable %s exists%s; ", dst,
341				    ((uid_t)getuid() == st.st_uid) ? "" :
342				    ", and you do not own it");
343				printf("remove it? [ny](n): ");
344				fflush(stdout);
345				yn = getchar();
346			}
347
348			if (yn == 'n') {
349				cvs_log(LP_ERR, "checkout aborted");
350				return (-1);
351			}
352		}
353	}
354
355	if (pipeout == 1) {
356		cvs_buf_putc(bp, '\0');
357		content = cvs_buf_release(bp);
358		printf("%s", content);
359		xfree(content);
360	} else {
361		if (cvs_buf_write(bp, dst, mode) < 0) {
362			cvs_log(LP_ERR, "failed to write revision to file");
363			cvs_buf_free(bp);
364			return (-1);
365		}
366		cvs_buf_free(bp);
367		if (flags & CO_REVDATE) {
368			struct timeval tv[2];
369			memset(&tv, 0, sizeof(tv));
370			tv[0].tv_sec = (long)rcs_rev_getdate(file, frev);
371			tv[1].tv_sec = tv[0].tv_sec;
372			if (utimes(dst, (const struct timeval *)&tv) < 0)
373				cvs_log(LP_ERRNO, "error setting utimes");
374		}
375
376		if ((verbose == 1) && !(flags & NEWFILE))
377			printf("done\n");
378	}
379
380	return (0);
381}
382
383/*
384 * checkout_err_nobranch()
385 *
386 * XXX - should handle the dates too.
387 */
388static void
389checkout_err_nobranch(RCSFILE *file, const char *author, const char *date,
390    const char *state, int flags)
391{
392	if (!(flags & CO_AUTHOR))
393		author = NULL;
394	if (!(flags & CO_STATE))
395		state = NULL;
396
397	cvs_log(LP_ERR, "%s: No revision on branch has%s%s%s%s%s%s.",
398	    file->rf_path,
399	    date ? " a date before " : "",
400	    date ? date : "",
401	    author ? " and author " + (date ? 0:4 ) : "",
402	    author ? author : "",
403	    state  ? " and state " + (date || author ? 0:4) : "",
404	    state  ? state : "");
405}
406