xref: /openbsd-src/usr.bin/cvs/checkout.c (revision d874cce4b1d9fe6b41c9e4f2117a77d8a4a37b92)
1 /*	$OpenBSD: checkout.c,v 1.155 2008/06/14 04:34:08 tobias Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/dirent.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <libgen.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 #include "cvs.h"
32 #include "diff.h"
33 #include "remote.h"
34 
35 static void checkout_check_repository(int, char **);
36 static int checkout_classify(const char *, const char *);
37 static void checkout_repository(const char *, const char *);
38 
39 extern int print_stdout;
40 extern int prune_dirs;
41 extern int build_dirs;
42 
43 static int flags = CR_REPO | CR_RECURSE_DIRS;
44 static int Aflag = 0;
45 static char *dflag = NULL;
46 static char *koptstr = NULL;
47 static char *dateflag = NULL;
48 
49 static int nflag = 0;
50 
51 char *checkout_target_dir = NULL;
52 
53 time_t cvs_specified_date = -1;
54 time_t cvs_directory_date = -1;
55 int disable_fast_checkout = 0;
56 
57 struct cvs_cmd cvs_cmd_checkout = {
58 	CVS_OP_CHECKOUT, CVS_USE_WDIR, "checkout",
59 	{ "co", "get" },
60 	"Checkout a working copy of a repository",
61 	"[-AcflNnPpRs] [-D date | -r tag] [-d dir] [-j rev] [-k mode] "
62 	"[-t id] module ...",
63 	"AcD:d:fj:k:lNnPpRr:st:",
64 	NULL,
65 	cvs_checkout
66 };
67 
68 struct cvs_cmd cvs_cmd_export = {
69 	CVS_OP_EXPORT, CVS_USE_WDIR, "export",
70 	{ "exp", "ex" },
71 	"Export sources from CVS, similar to checkout",
72 	"[-flNnR] [-d dir] [-k mode] -D date | -r rev module ...",
73 	"D:d:k:flNnRr:",
74 	NULL,
75 	cvs_export
76 };
77 
78 int
79 cvs_checkout(int argc, char **argv)
80 {
81 	int ch;
82 
83 	while ((ch = getopt(argc, argv, cvs_cmd_checkout.cmd_opts)) != -1) {
84 		switch (ch) {
85 		case 'A':
86 			Aflag = 1;
87 			if (koptstr == NULL)
88 				reset_option = 1;
89 			if (cvs_specified_tag == NULL)
90 				reset_tag = 1;
91 			break;
92 		case 'c':
93 			cvs_modules_list();
94 			exit(0);
95 		case 'D':
96 			dateflag = optarg;
97 			cvs_specified_date = cvs_date_parse(dateflag);
98 			reset_tag = 0;
99 			break;
100 		case 'd':
101 			if (dflag != NULL)
102 				fatal("-d specified two or more times");
103 			dflag = optarg;
104 			checkout_target_dir = dflag;
105 
106 			if (cvs_server_active == 1)
107 				disable_fast_checkout = 1;
108 			break;
109 		case 'j':
110 			if (cvs_join_rev1 == NULL)
111 				cvs_join_rev1 = optarg;
112 			else if (cvs_join_rev2 == NULL)
113 				cvs_join_rev2 = optarg;
114 			else
115 				fatal("too many -j options");
116 			break;
117 		case 'k':
118 			reset_option = 0;
119 			koptstr = optarg;
120 			kflag = rcs_kflag_get(koptstr);
121 			if (RCS_KWEXP_INVAL(kflag)) {
122 				cvs_log(LP_ERR,
123 				    "invalid RCS keyword expansion mode");
124 				fatal("%s", cvs_cmd_add.cmd_synopsis);
125 			}
126 			break;
127 		case 'l':
128 			flags &= ~CR_RECURSE_DIRS;
129 			break;
130 		case 'N':
131 			break;
132 		case 'n':
133 			nflag = 1;
134 			break;
135 		case 'P':
136 			prune_dirs = 1;
137 			break;
138 		case 'p':
139 			cmdp->cmd_flags &= ~CVS_USE_WDIR;
140 			print_stdout = 1;
141 			cvs_noexec = 1;
142 			nflag = 1;
143 			break;
144 		case 'R':
145 			flags |= CR_RECURSE_DIRS;
146 			break;
147 		case 'r':
148 			reset_tag = 0;
149 			cvs_specified_tag = optarg;
150 			break;
151 		default:
152 			fatal("%s", cvs_cmd_checkout.cmd_synopsis);
153 		}
154 	}
155 
156 	argc -= optind;
157 	argv += optind;
158 
159 	if (argc == 0)
160 		fatal("%s", cvs_cmd_checkout.cmd_synopsis);
161 
162 	if (cvs_server_active == 1 && disable_fast_checkout != 1) {
163 		cmdp->cmd_flags &= ~CVS_USE_WDIR;
164 		cvs_noexec = 1;
165 	}
166 
167 	checkout_check_repository(argc, argv);
168 
169 	if (cvs_server_active == 1 && disable_fast_checkout != 1)
170 		cvs_noexec = 0;
171 
172 	return (0);
173 }
174 
175 int
176 cvs_export(int argc, char **argv)
177 {
178 	int ch;
179 
180 	prune_dirs = 1;
181 
182 	while ((ch = getopt(argc, argv, cvs_cmd_export.cmd_opts)) != -1) {
183 		switch (ch) {
184 		case 'k':
185 			koptstr = optarg;
186 			kflag = rcs_kflag_get(koptstr);
187 			if (RCS_KWEXP_INVAL(kflag)) {
188 				cvs_log(LP_ERR,
189 				    "invalid RCS keyword expansion mode");
190 				fatal("%s", cvs_cmd_add.cmd_synopsis);
191 			}
192 			break;
193 		case 'l':
194 			flags &= ~CR_RECURSE_DIRS;
195 			break;
196 		case 'R':
197 			flags |= CR_RECURSE_DIRS;
198 			break;
199 		case 'r':
200 			cvs_specified_tag = optarg;
201 			break;
202 		default:
203 			fatal("%s", cvs_cmd_export.cmd_synopsis);
204 		}
205 	}
206 
207 	argc -= optind;
208 	argv += optind;
209 
210 	if (cvs_specified_tag == NULL)
211 		fatal("must specify a tag or date");
212 
213 	if (argc == 0)
214 		fatal("%s", cvs_cmd_export.cmd_synopsis);
215 
216 	checkout_check_repository(argc, argv);
217 
218 	return (0);
219 }
220 
221 static void
222 checkout_check_repository(int argc, char **argv)
223 {
224 	int i;
225 	char *wdir, *d;
226 	struct cvs_recursion cr;
227 	struct module_checkout *mc;
228 	struct cvs_ignpat *ip;
229 	struct cvs_filelist *fl, *nxt;
230 	char repo[MAXPATHLEN], fpath[MAXPATHLEN], *f[1];
231 
232 	build_dirs = print_stdout ? 0 : 1;
233 
234 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
235 		cvs_client_connect_to_server();
236 
237 		if (cvs_specified_tag != NULL)
238 			cvs_client_send_request("Argument -r%s",
239 			    cvs_specified_tag);
240 		if (Aflag)
241 			cvs_client_send_request("Argument -A");
242 
243 		if (dateflag != NULL)
244 			cvs_client_send_request("Argument -D%s", dateflag);
245 
246 		if (kflag)
247 			cvs_client_send_request("Argument -k%s", koptstr);
248 
249 		if (dflag != NULL)
250 			cvs_client_send_request("Argument -d%s", dflag);
251 
252 		if (!(flags & CR_RECURSE_DIRS))
253 			cvs_client_send_request("Argument -l");
254 
255 		if (cvs_cmdop == CVS_OP_CHECKOUT && prune_dirs == 1)
256 			cvs_client_send_request("Argument -P");
257 
258 		if (print_stdout == 1)
259 			cvs_client_send_request("Argument -p");
260 
261 		if (nflag == 1)
262 			cvs_client_send_request("Argument -n");
263 
264 		cr.enterdir = NULL;
265 		cr.leavedir = NULL;
266 		if (print_stdout)
267 			cr.fileproc = NULL;
268 		else
269 			cr.fileproc = cvs_client_sendfile;
270 
271 		flags &= ~CR_REPO;
272 		cr.flags = flags;
273 
274 		if (cvs_cmdop != CVS_OP_EXPORT)
275 			cvs_file_run(argc, argv, &cr);
276 
277 		cvs_client_send_files(argv, argc);
278 		cvs_client_senddir(".");
279 
280 		cvs_client_send_request("%s",
281 		    (cvs_cmdop == CVS_OP_CHECKOUT) ? "co" : "export");
282 
283 		cvs_client_get_responses();
284 
285 		return;
286 	}
287 
288 	for (i = 0; i < argc; i++) {
289 		mc = cvs_module_lookup(argv[i]);
290 		current_module = mc;
291 
292 		TAILQ_FOREACH(fl, &(mc->mc_ignores), flist)
293 			cvs_file_ignore(fl->file_path, &checkout_ign_pats);
294 
295 		TAILQ_FOREACH(fl, &(mc->mc_modules), flist) {
296 			module_repo_root = NULL;
297 
298 			(void)xsnprintf(repo, sizeof(repo), "%s/%s",
299 			    current_cvsroot->cr_dir, fl->file_path);
300 
301 			if (!(mc->mc_flags & MODULE_ALIAS) || dflag != NULL)
302 				module_repo_root = xstrdup(fl->file_path);
303 
304 			if (mc->mc_flags & MODULE_NORECURSE)
305 				flags &= ~CR_RECURSE_DIRS;
306 
307 			if (dflag != NULL)
308 				wdir = dflag;
309 			else if (mc->mc_flags & MODULE_ALIAS)
310 				wdir = fl->file_path;
311 			else
312 				wdir = mc->mc_name;
313 
314 			switch (checkout_classify(repo, fl->file_path)) {
315 			case CVS_FILE:
316 				cr.fileproc = cvs_update_local;
317 				cr.flags = flags;
318 
319 				if (!(mc->mc_flags & MODULE_ALIAS)) {
320 					module_repo_root =
321 					    xstrdup(dirname(fl->file_path));
322 					d = wdir;
323 					(void)xsnprintf(fpath, sizeof(fpath),
324 					    "%s/%s", d,
325 					    basename(fl->file_path));
326 				} else {
327 					d = dirname(wdir);
328 					strlcpy(fpath, fl->file_path,
329 					    sizeof(fpath));
330 				}
331 
332 				if (build_dirs == 1)
333 					cvs_mkpath(d, cvs_specified_tag);
334 
335 				f[0] = fpath;
336 				cvs_file_run(1, f, &cr);
337 				break;
338 			case CVS_DIR:
339 				if (build_dirs == 1)
340 					cvs_mkpath(wdir, cvs_specified_tag);
341 				checkout_repository(repo, wdir);
342 				break;
343 			default:
344 				break;
345 			}
346 
347 			if (nflag != 1 && mc->mc_prog != NULL &&
348 			    mc->mc_flags & MODULE_RUN_ON_CHECKOUT)
349 				cvs_exec(mc->mc_prog, NULL, 0);
350 
351 			if (module_repo_root != NULL)
352 				xfree(module_repo_root);
353 		}
354 
355 		if (mc->mc_canfree == 1) {
356 			for (fl = TAILQ_FIRST(&(mc->mc_modules));
357 			    fl != TAILQ_END(&(mc->mc_modules)); fl = nxt) {
358 				nxt = TAILQ_NEXT(fl, flist);
359 				TAILQ_REMOVE(&(mc->mc_modules), fl, flist);
360 				xfree(fl->file_path);
361 				xfree(fl);
362 			}
363 		}
364 
365 		while ((ip = TAILQ_FIRST(&checkout_ign_pats)) != NULL) {
366 			TAILQ_REMOVE(&checkout_ign_pats, ip, ip_list);
367 			xfree(ip);
368 		}
369 
370 		xfree(mc);
371 	}
372 }
373 
374 static int
375 checkout_classify(const char *repo, const char *arg)
376 {
377 	char *d, *f, fpath[MAXPATHLEN];
378 	struct stat sb;
379 
380 	if (stat(repo, &sb) == 0) {
381 		if (S_ISDIR(sb.st_mode))
382 			return CVS_DIR;
383 	}
384 
385 	d = dirname(repo);
386 	f = basename(repo);
387 
388 	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s%s", d, f, RCS_FILE_EXT);
389 	if (stat(fpath, &sb) == 0) {
390 		if (!S_ISREG(sb.st_mode)) {
391 			cvs_log(LP_ERR, "ignoring %s: not a regular file", arg);
392 			return 0;
393 		}
394 		return CVS_FILE;
395 	}
396 
397 	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s/%s%s",
398 	    d, CVS_PATH_ATTIC, f, RCS_FILE_EXT);
399 	if (stat(fpath, &sb) == 0) {
400 		if (!S_ISREG(sb.st_mode)) {
401 			cvs_log(LP_ERR, "ignoring %s: not a regular file", arg);
402 			return 0;
403 		}
404 		return CVS_FILE;
405 	}
406 
407 	cvs_log(LP_ERR, "cannot find module `%s' - ignored", arg);
408 	return 0;
409 }
410 
411 static void
412 checkout_repository(const char *repobase, const char *wdbase)
413 {
414 	struct cvs_flisthead fl, dl;
415 	struct cvs_recursion cr;
416 
417 	TAILQ_INIT(&fl);
418 	TAILQ_INIT(&dl);
419 
420 	cvs_history_add((cvs_cmdop == CVS_OP_CHECKOUT) ?
421 	    CVS_HISTORY_CHECKOUT : CVS_HISTORY_EXPORT, NULL, wdbase);
422 
423 	if (print_stdout) {
424 		cr.enterdir = NULL;
425 		cr.leavedir = NULL;
426 	} else {
427 		cr.enterdir = cvs_update_enterdir;
428 		if (cvs_server_active == 1) {
429 			if (disable_fast_checkout != 1)
430 				cr.leavedir = NULL;
431 			else
432 				cr.leavedir = cvs_update_leavedir;
433 		} else {
434 			cr.leavedir = prune_dirs ? cvs_update_leavedir : NULL;
435 		}
436 	}
437 	cr.fileproc = cvs_update_local;
438 	cr.flags = flags;
439 
440 	cvs_repository_lock(repobase, 0);
441 	cvs_repository_getdir(repobase, wdbase, &fl, &dl,
442 	    flags & CR_RECURSE_DIRS ? 1 : 0);
443 
444 	cvs_file_walklist(&fl, &cr);
445 	cvs_file_freelist(&fl);
446 
447 	cvs_repository_unlock(repobase);
448 
449 	cvs_file_walklist(&dl, &cr);
450 	cvs_file_freelist(&dl);
451 }
452 
453 void
454 cvs_checkout_file(struct cvs_file *cf, RCSNUM *rnum, char *tag, int co_flags)
455 {
456 	BUF *bp;
457 	mode_t mode;
458 	int cf_kflag, exists, fd;
459 	time_t rcstime;
460 	CVSENTRIES *ent;
461 	struct timeval tv[2];
462 	struct tm datetm;
463 	char *entry, *tosend;
464 	char kbuf[8], sticky[CVS_REV_BUFSZ], rev[CVS_REV_BUFSZ];
465 	char timebuf[CVS_TIME_BUFSZ], tbuf[CVS_TIME_BUFSZ];
466 
467 	exists = 0;
468 	tosend = NULL;
469 
470 	if (!(co_flags & CO_REMOVE))
471 		rcsnum_tostr(rnum, rev, sizeof(rev));
472 	else
473 		rev[0] = '\0';
474 
475 	cvs_log(LP_TRACE, "cvs_checkout_file(%s, %s, %d) -> %s",
476 	    cf->file_path, rev, co_flags,
477 	    (cvs_server_active) ? "to client" : "to disk");
478 
479 	if (co_flags & CO_DUMP) {
480 		rcs_rev_write_fd(cf->file_rcs, rnum, STDOUT_FILENO, 0);
481 		return;
482 	}
483 
484 	if (cvs_server_active == 0) {
485 		(void)unlink(cf->file_path);
486 
487 		if (!(co_flags & CO_MERGE)) {
488 			if (cf->fd != -1) {
489 				exists = 1;
490 				(void)close(cf->fd);
491 			}
492 
493 			cf->fd = open(cf->file_path,
494 			    O_CREAT | O_RDWR | O_TRUNC);
495 			if (cf->fd == -1)
496 				fatal("cvs_checkout_file: open: %s",
497 				    strerror(errno));
498 
499 			rcs_rev_write_fd(cf->file_rcs, rnum, cf->fd, 0);
500 		} else {
501 			cvs_merge_file(cf, (cvs_join_rev1 == NULL));
502 		}
503 
504 		mode = cf->file_rcs->rf_mode;
505 		mode |= S_IWUSR;
506 
507 		if (fchmod(cf->fd, mode) == -1)
508 			fatal("cvs_checkout_file: fchmod: %s", strerror(errno));
509 
510 		if ((exists == 0) && (cf->file_ent == NULL) &&
511 		    !(co_flags & CO_MERGE))
512 			rcstime = rcs_rev_getdate(cf->file_rcs, rnum);
513 		else
514 			time(&rcstime);
515 
516 		tv[0].tv_sec = rcstime;
517 		tv[0].tv_usec = 0;
518 		tv[1] = tv[0];
519 		if (futimes(cf->fd, tv) == -1)
520 			fatal("cvs_checkout_file: futimes: %s",
521 			    strerror(errno));
522 	} else {
523 		time(&rcstime);
524 	}
525 
526 	gmtime_r(&rcstime, &datetm);
527 	asctime_r(&datetm, tbuf);
528 	tbuf[strcspn(tbuf, "\n")] = '\0';
529 
530 	if (co_flags & CO_MERGE) {
531 		(void)xsnprintf(timebuf, sizeof(timebuf), "Result of merge+%s",
532 		    tbuf);
533 	} else {
534 		strlcpy(timebuf, tbuf, sizeof(timebuf));
535 	}
536 
537 	if (reset_tag) {
538 		sticky[0] = '\0';
539 	} else if (co_flags & CO_SETSTICKY)
540 		if (tag != NULL)
541 			(void)xsnprintf(sticky, sizeof(sticky), "T%s", tag);
542 		else if (cvs_specified_date != -1) {
543 			gmtime_r(&cvs_specified_date, &datetm);
544 			(void)strftime(sticky, sizeof(sticky),
545 			    "D"CVS_DATE_FMT, &datetm);
546 		} else if (cvs_directory_date != -1) {
547 			gmtime_r(&cvs_directory_date, &datetm);
548 			(void)strftime(sticky, sizeof(sticky),
549 			    "D"CVS_DATE_FMT, &datetm);
550 		} else
551 			(void)xsnprintf(sticky, sizeof(sticky), "T%s", rev);
552 	else if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL)
553 		(void)xsnprintf(sticky, sizeof(sticky), "T%s",
554 		    cf->file_ent->ce_tag);
555 	else
556 		sticky[0] = '\0';
557 
558 	kbuf[0] = '\0';
559 	if (cf->file_rcs->rf_expand != NULL) {
560 		cf_kflag = rcs_kflag_get(cf->file_rcs->rf_expand);
561 		if (kflag || cf_kflag != RCS_KWEXP_DEFAULT)
562 			(void)xsnprintf(kbuf, sizeof(kbuf),
563 			    "-k%s", cf->file_rcs->rf_expand);
564 	} else if (!reset_option && cf->file_ent != NULL) {
565 		if (cf->file_ent->ce_opts != NULL)
566 			strlcpy(kbuf, cf->file_ent->ce_opts, sizeof(kbuf));
567 	}
568 
569 	entry = xmalloc(CVS_ENT_MAXLINELEN);
570 	cvs_ent_line_str(cf->file_name, rev, timebuf, kbuf, sticky, 0, 0,
571 	    entry, CVS_ENT_MAXLINELEN);
572 
573 	if (cvs_server_active == 0) {
574 		if (!(co_flags & CO_REMOVE) && cvs_cmdop != CVS_OP_EXPORT) {
575 			ent = cvs_ent_open(cf->file_wd);
576 			cvs_ent_add(ent, entry);
577 			cf->file_ent = cvs_ent_parse(entry);
578 		}
579 	} else {
580 		if (co_flags & CO_MERGE) {
581 			(void)unlink(cf->file_path);
582 			cvs_merge_file(cf, (cvs_join_rev1 == NULL));
583 			tosend = cf->file_path;
584 			fd = cf->fd;
585 		}
586 
587 		if (co_flags & CO_COMMIT)
588 			cvs_server_update_entry("Updated", cf);
589 		else if (co_flags & CO_MERGE)
590 			cvs_server_update_entry("Merged", cf);
591 		else if (co_flags & CO_REMOVE)
592 			cvs_server_update_entry("Removed", cf);
593 		else
594 			cvs_server_update_entry("Updated", cf);
595 
596 		if (!(co_flags & CO_REMOVE)) {
597 			cvs_remote_output(entry);
598 
599 			if (!(co_flags & CO_MERGE)) {
600 				mode = cf->file_rcs->rf_mode;
601 				mode |= S_IWUSR;
602 				bp = rcs_rev_getbuf(cf->file_rcs, rnum, 0);
603 				cvs_remote_send_file_buf(cf->file_path,
604 				    bp, mode);
605 			} else {
606 				cvs_remote_send_file(tosend, fd);
607 			}
608 		}
609 	}
610 
611 	xfree(entry);
612 }
613