1 /* $OpenBSD: checkout.c,v 1.172 2020/10/19 19:51:20 naddy 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/types.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
cvs_checkout(int argc,char ** argv)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 if ((cvs_specified_date = date_parse(dateflag)) == -1)
98 fatal("invalid date: %s", dateflag);
99 reset_tag = 0;
100 break;
101 case 'd':
102 if (dflag != NULL)
103 fatal("-d specified two or more times");
104 dflag = optarg;
105 checkout_target_dir = dflag;
106
107 if (cvs_server_active == 1)
108 disable_fast_checkout = 1;
109 break;
110 case 'j':
111 if (cvs_join_rev1 == NULL)
112 cvs_join_rev1 = optarg;
113 else if (cvs_join_rev2 == NULL)
114 cvs_join_rev2 = optarg;
115 else
116 fatal("too many -j options");
117 break;
118 case 'k':
119 reset_option = 0;
120 koptstr = optarg;
121 kflag = rcs_kflag_get(koptstr);
122 if (RCS_KWEXP_INVAL(kflag)) {
123 cvs_log(LP_ERR,
124 "invalid RCS keyword expansion mode");
125 fatal("%s", cvs_cmd_checkout.cmd_synopsis);
126 }
127 break;
128 case 'l':
129 flags &= ~CR_RECURSE_DIRS;
130 break;
131 case 'N':
132 break;
133 case 'n':
134 nflag = 1;
135 break;
136 case 'P':
137 prune_dirs = 1;
138 break;
139 case 'p':
140 cmdp->cmd_flags &= ~CVS_USE_WDIR;
141 print_stdout = 1;
142 cvs_noexec = 1;
143 nflag = 1;
144 break;
145 case 'R':
146 flags |= CR_RECURSE_DIRS;
147 break;
148 case 'r':
149 reset_tag = 0;
150 cvs_specified_tag = optarg;
151 break;
152 default:
153 fatal("%s", cvs_cmd_checkout.cmd_synopsis);
154 }
155 }
156
157 argc -= optind;
158 argv += optind;
159
160 if (argc == 0)
161 fatal("%s", cvs_cmd_checkout.cmd_synopsis);
162
163 if (cvs_server_active == 1 && disable_fast_checkout != 1) {
164 cmdp->cmd_flags &= ~CVS_USE_WDIR;
165 cvs_noexec = 1;
166 }
167
168 checkout_check_repository(argc, argv);
169
170 if (cvs_server_active == 1 && disable_fast_checkout != 1)
171 cvs_noexec = 0;
172
173 return (0);
174 }
175
176 int
cvs_export(int argc,char ** argv)177 cvs_export(int argc, char **argv)
178 {
179 int ch;
180
181 prune_dirs = 1;
182
183 while ((ch = getopt(argc, argv, cvs_cmd_export.cmd_opts)) != -1) {
184 switch (ch) {
185 case 'd':
186 if (dflag != NULL)
187 fatal("-d specified two or more times");
188 dflag = optarg;
189 checkout_target_dir = dflag;
190
191 if (cvs_server_active == 1)
192 disable_fast_checkout = 1;
193 break;
194 case 'k':
195 koptstr = optarg;
196 kflag = rcs_kflag_get(koptstr);
197 if (RCS_KWEXP_INVAL(kflag)) {
198 cvs_log(LP_ERR,
199 "invalid RCS keyword expansion mode");
200 fatal("%s", cvs_cmd_export.cmd_synopsis);
201 }
202 break;
203 case 'l':
204 flags &= ~CR_RECURSE_DIRS;
205 break;
206 case 'N':
207 break;
208 case 'R':
209 flags |= CR_RECURSE_DIRS;
210 break;
211 case 'r':
212 cvs_specified_tag = optarg;
213 break;
214 default:
215 fatal("%s", cvs_cmd_export.cmd_synopsis);
216 }
217 }
218
219 argc -= optind;
220 argv += optind;
221
222 if (cvs_specified_tag == NULL)
223 fatal("must specify a tag or date");
224
225 if (argc == 0)
226 fatal("%s", cvs_cmd_export.cmd_synopsis);
227
228 checkout_check_repository(argc, argv);
229
230 return (0);
231 }
232
233 static void
checkout_check_repository(int argc,char ** argv)234 checkout_check_repository(int argc, char **argv)
235 {
236 int i;
237 char *wdir, *d;
238 struct cvs_recursion cr;
239 struct module_checkout *mc;
240 struct cvs_ignpat *ip;
241 struct cvs_filelist *fl, *nxt;
242 char repo[PATH_MAX], fpath[PATH_MAX], path[PATH_MAX], *f[1];
243
244 build_dirs = print_stdout ? 0 : 1;
245
246 if (cvsroot_is_remote()) {
247 cvs_client_connect_to_server();
248
249 if (cvs_specified_tag != NULL)
250 cvs_client_send_request("Argument -r%s",
251 cvs_specified_tag);
252 if (Aflag)
253 cvs_client_send_request("Argument -A");
254
255 if (dateflag != NULL)
256 cvs_client_send_request("Argument -D%s", dateflag);
257
258 if (kflag)
259 cvs_client_send_request("Argument -k%s", koptstr);
260
261 if (dflag != NULL)
262 cvs_client_send_request("Argument -d%s", dflag);
263
264 if (!(flags & CR_RECURSE_DIRS))
265 cvs_client_send_request("Argument -l");
266
267 if (cvs_cmdop == CVS_OP_CHECKOUT && prune_dirs == 1)
268 cvs_client_send_request("Argument -P");
269
270 if (print_stdout == 1)
271 cvs_client_send_request("Argument -p");
272
273 if (nflag == 1)
274 cvs_client_send_request("Argument -n");
275
276 cr.enterdir = NULL;
277 cr.leavedir = NULL;
278 if (print_stdout)
279 cr.fileproc = NULL;
280 else
281 cr.fileproc = cvs_client_sendfile;
282
283 flags &= ~CR_REPO;
284 cr.flags = flags;
285
286 if (cvs_cmdop != CVS_OP_EXPORT)
287 cvs_file_run(argc, argv, &cr);
288
289 cvs_client_send_files(argv, argc);
290 cvs_client_senddir(".");
291
292 cvs_client_send_request("%s",
293 (cvs_cmdop == CVS_OP_CHECKOUT) ? "co" : "export");
294
295 cvs_client_get_responses();
296
297 return;
298 }
299
300 for (i = 0; i < argc; i++) {
301 mc = cvs_module_lookup(argv[i]);
302 current_module = mc;
303
304 RB_FOREACH(fl, cvs_flisthead, &(mc->mc_ignores))
305 cvs_file_ignore(fl->file_path, &checkout_ign_pats);
306
307 RB_FOREACH(fl, cvs_flisthead, &(mc->mc_modules)) {
308 module_repo_root = NULL;
309
310 (void)xsnprintf(repo, sizeof(repo), "%s/%s",
311 current_cvsroot->cr_dir, fl->file_path);
312
313 if (!(mc->mc_flags & MODULE_ALIAS) || dflag != NULL)
314 module_repo_root = xstrdup(fl->file_path);
315
316 if (mc->mc_flags & MODULE_NORECURSE)
317 flags &= ~CR_RECURSE_DIRS;
318
319 if (dflag != NULL)
320 wdir = dflag;
321 else if (mc->mc_flags & MODULE_ALIAS)
322 wdir = fl->file_path;
323 else
324 wdir = mc->mc_name;
325
326 switch (checkout_classify(repo, fl->file_path)) {
327 case CVS_FILE:
328 cr.fileproc = cvs_update_local;
329 cr.flags = flags;
330
331 if (!(mc->mc_flags & MODULE_ALIAS)) {
332 if (strlcpy(path, fl->file_path,
333 sizeof(path)) >= sizeof(path))
334 fatal("%s: truncation",
335 __func__);
336 module_repo_root =
337 xstrdup(dirname(path));
338 d = wdir;
339 if (strlcpy(path, fl->file_path,
340 sizeof(path)) >= sizeof(path))
341 fatal("%s: truncation",
342 __func__);
343 (void)xsnprintf(fpath, sizeof(fpath),
344 "%s/%s", d, basename(path));
345 } else {
346 if (strlcpy(path, wdir,
347 sizeof(path)) >= sizeof(path))
348 fatal("%s: truncation",
349 __func__);
350 d = dirname(path);
351 strlcpy(fpath, fl->file_path,
352 sizeof(fpath));
353 }
354
355 if (build_dirs == 1)
356 cvs_mkpath(d, cvs_specified_tag);
357
358 f[0] = fpath;
359 cvs_file_run(1, f, &cr);
360 break;
361 case CVS_DIR:
362 if (build_dirs == 1)
363 cvs_mkpath(wdir, cvs_specified_tag);
364 checkout_repository(repo, wdir);
365 break;
366 default:
367 break;
368 }
369
370 if (nflag != 1 && mc->mc_prog != NULL &&
371 mc->mc_flags & MODULE_RUN_ON_CHECKOUT)
372 cvs_exec(mc->mc_prog, NULL, 0);
373
374 free(module_repo_root);
375 }
376
377 if (mc->mc_canfree == 1) {
378 for (fl = RB_MIN(cvs_flisthead, &(mc->mc_modules));
379 fl != NULL; fl = nxt) {
380 nxt = RB_NEXT(cvs_flisthead,
381 &(mc->mc_modules), fl);
382 RB_REMOVE(cvs_flisthead,
383 &(mc->mc_modules), fl);
384 free(fl->file_path);
385 free(fl);
386 }
387 }
388
389 while ((ip = TAILQ_FIRST(&checkout_ign_pats)) != NULL) {
390 TAILQ_REMOVE(&checkout_ign_pats, ip, ip_list);
391 free(ip);
392 }
393
394 free(mc);
395 }
396 }
397
398 static int
checkout_classify(const char * repo,const char * arg)399 checkout_classify(const char *repo, const char *arg)
400 {
401 char *d, dbuf[PATH_MAX], *f, fbuf[PATH_MAX], fpath[PATH_MAX];
402 struct stat sb;
403
404 if (stat(repo, &sb) == 0) {
405 if (S_ISDIR(sb.st_mode))
406 return CVS_DIR;
407 }
408
409 if (strlcpy(dbuf, repo, sizeof(dbuf)) >= sizeof(dbuf))
410 fatal("checkout_classify: truncation");
411 d = dirname(dbuf);
412
413 if (strlcpy(fbuf, repo, sizeof(fbuf)) >= sizeof(fbuf))
414 fatal("checkout_classify: truncation");
415 f = basename(fbuf);
416
417 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s%s", d, f, RCS_FILE_EXT);
418 if (stat(fpath, &sb) == 0) {
419 if (!S_ISREG(sb.st_mode)) {
420 cvs_log(LP_ERR, "ignoring %s: not a regular file", arg);
421 return 0;
422 }
423 return CVS_FILE;
424 }
425
426 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s/%s%s",
427 d, CVS_PATH_ATTIC, f, RCS_FILE_EXT);
428 if (stat(fpath, &sb) == 0) {
429 if (!S_ISREG(sb.st_mode)) {
430 cvs_log(LP_ERR, "ignoring %s: not a regular file", arg);
431 return 0;
432 }
433 return CVS_FILE;
434 }
435
436 cvs_log(LP_ERR, "cannot find module `%s' - ignored", arg);
437 return 0;
438 }
439
440 static void
checkout_repository(const char * repobase,const char * wdbase)441 checkout_repository(const char *repobase, const char *wdbase)
442 {
443 struct cvs_flisthead fl, dl;
444 struct cvs_recursion cr;
445
446 RB_INIT(&fl);
447 RB_INIT(&dl);
448
449 cvs_history_add((cvs_cmdop == CVS_OP_CHECKOUT) ?
450 CVS_HISTORY_CHECKOUT : CVS_HISTORY_EXPORT, NULL, wdbase);
451
452 if (print_stdout) {
453 cr.enterdir = NULL;
454 cr.leavedir = NULL;
455 } else {
456 cr.enterdir = cvs_update_enterdir;
457 if (cvs_server_active == 1) {
458 if (disable_fast_checkout != 1)
459 cr.leavedir = NULL;
460 else
461 cr.leavedir = cvs_update_leavedir;
462 } else {
463 cr.leavedir = prune_dirs ? cvs_update_leavedir : NULL;
464 }
465 }
466 cr.fileproc = cvs_update_local;
467 cr.flags = flags;
468
469 cvs_repository_lock(repobase, 0);
470 cvs_repository_getdir(repobase, wdbase, &fl, &dl,
471 flags & CR_RECURSE_DIRS ? REPOSITORY_DODIRS : 0);
472
473 cvs_file_walklist(&fl, &cr);
474 cvs_file_freelist(&fl);
475
476 cvs_repository_unlock(repobase);
477
478 cvs_file_walklist(&dl, &cr);
479 cvs_file_freelist(&dl);
480 }
481
482 void
cvs_checkout_file(struct cvs_file * cf,RCSNUM * rnum,char * tag,int co_flags)483 cvs_checkout_file(struct cvs_file *cf, RCSNUM *rnum, char *tag, int co_flags)
484 {
485 BUF *bp;
486 mode_t mode;
487 int cf_kflag, exists;
488 time_t rcstime;
489 CVSENTRIES *ent;
490 struct timeval tv[2];
491 struct tm datetm;
492 char *entry, *tosend;
493 char kbuf[8], sticky[CVS_REV_BUFSZ], rev[CVS_REV_BUFSZ];
494 char timebuf[CVS_TIME_BUFSZ], tbuf[CVS_TIME_BUFSZ];
495 static char lastwd[PATH_MAX];
496
497 exists = 0;
498 tosend = NULL;
499
500 if (!(co_flags & CO_REMOVE))
501 rcsnum_tostr(rnum, rev, sizeof(rev));
502 else
503 rev[0] = '\0';
504
505 cvs_log(LP_TRACE, "cvs_checkout_file(%s, %s, %d) -> %s",
506 cf->file_path, rev, co_flags,
507 (cvs_server_active) ? "to client" : "to disk");
508
509 if (co_flags & CO_DUMP) {
510 rcs_rev_write_fd(cf->file_rcs, rnum, STDOUT_FILENO, 0);
511 return;
512 }
513
514 if (cvs_server_active == 0) {
515 (void)unlink(cf->file_path);
516
517 if (!(co_flags & CO_MERGE)) {
518 if (cf->file_flags & FILE_ON_DISK) {
519 exists = 1;
520 (void)close(cf->fd);
521 }
522
523 cf->fd = open(cf->file_path,
524 O_CREAT | O_RDWR | O_TRUNC);
525 if (cf->fd == -1)
526 fatal("cvs_checkout_file: open: %s",
527 strerror(errno));
528
529 rcs_rev_write_fd(cf->file_rcs, rnum, cf->fd, 0);
530 cf->file_flags |= FILE_ON_DISK;
531 } else {
532 cvs_merge_file(cf, (cvs_join_rev1 == NULL));
533 }
534
535 mode = cf->file_rcs->rf_mode;
536 mode |= S_IWUSR;
537
538 if (fchmod(cf->fd, mode) == -1)
539 fatal("cvs_checkout_file: fchmod: %s", strerror(errno));
540
541 if ((exists == 0) && (cf->file_ent == NULL) &&
542 !(co_flags & CO_MERGE))
543 rcstime = rcs_rev_getdate(cf->file_rcs, rnum);
544 else
545 time(&rcstime);
546
547 tv[0].tv_sec = rcstime;
548 tv[0].tv_usec = 0;
549 tv[1] = tv[0];
550 if (futimes(cf->fd, tv) == -1)
551 fatal("cvs_checkout_file: futimes: %s",
552 strerror(errno));
553 } else {
554 time(&rcstime);
555 }
556
557 gmtime_r(&rcstime, &datetm);
558 asctime_r(&datetm, tbuf);
559 tbuf[strcspn(tbuf, "\n")] = '\0';
560
561 if (co_flags & CO_MERGE) {
562 (void)xsnprintf(timebuf, sizeof(timebuf), "Result of merge+%s",
563 tbuf);
564 } else {
565 strlcpy(timebuf, tbuf, sizeof(timebuf));
566 }
567
568 if (reset_tag) {
569 sticky[0] = '\0';
570 } else if (co_flags & CO_SETSTICKY)
571 if (tag != NULL)
572 (void)xsnprintf(sticky, sizeof(sticky), "T%s", tag);
573 else if (cvs_specified_date != -1) {
574 gmtime_r(&cvs_specified_date, &datetm);
575 (void)strftime(sticky, sizeof(sticky),
576 "D"CVS_DATE_FMT, &datetm);
577 } else if (cvs_directory_date != -1) {
578 gmtime_r(&cvs_directory_date, &datetm);
579 (void)strftime(sticky, sizeof(sticky),
580 "D"CVS_DATE_FMT, &datetm);
581 } else
582 (void)xsnprintf(sticky, sizeof(sticky), "T%s", rev);
583 else if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL)
584 (void)xsnprintf(sticky, sizeof(sticky), "T%s",
585 cf->file_ent->ce_tag);
586 else
587 sticky[0] = '\0';
588
589 kbuf[0] = '\0';
590 if (cf->file_rcs != NULL && cf->file_rcs->rf_expand != NULL) {
591 cf_kflag = rcs_kflag_get(cf->file_rcs->rf_expand);
592 if (kflag || cf_kflag != RCS_KWEXP_DEFAULT)
593 (void)xsnprintf(kbuf, sizeof(kbuf),
594 "-k%s", cf->file_rcs->rf_expand);
595 } else if (!reset_option && cf->file_ent != NULL) {
596 if (cf->file_ent->ce_opts != NULL)
597 strlcpy(kbuf, cf->file_ent->ce_opts, sizeof(kbuf));
598 }
599
600 entry = xmalloc(CVS_ENT_MAXLINELEN);
601 cvs_ent_line_str(cf->file_name, rev, timebuf, kbuf, sticky, 0, 0,
602 entry, CVS_ENT_MAXLINELEN);
603
604 if (cvs_server_active == 0) {
605 if (!(co_flags & CO_REMOVE) && cvs_cmdop != CVS_OP_EXPORT) {
606 ent = cvs_ent_open(cf->file_wd);
607 cvs_ent_add(ent, entry);
608 cf->file_ent = cvs_ent_parse(entry);
609 }
610 } else {
611 if (co_flags & CO_MERGE) {
612 (void)unlink(cf->file_path);
613 cvs_merge_file(cf, (cvs_join_rev1 == NULL));
614 tosend = cf->file_path;
615 }
616
617 /*
618 * If this file has a tag, push out the Directory with the
619 * tag to the client. Except when this file was explicitly
620 * specified on the command line.
621 */
622 if (tag != NULL && strcmp(cf->file_wd, lastwd) &&
623 !(cf->file_flags & FILE_USER_SUPPLIED)) {
624 strlcpy(lastwd, cf->file_wd, PATH_MAX);
625 cvs_server_set_sticky(cf->file_wd, sticky);
626 }
627
628 if (co_flags & CO_COMMIT)
629 cvs_server_update_entry("Updated", cf);
630 else if (co_flags & CO_MERGE)
631 cvs_server_update_entry("Merged", cf);
632 else if (co_flags & CO_REMOVE)
633 cvs_server_update_entry("Removed", cf);
634 else
635 cvs_server_update_entry("Updated", cf);
636
637 if (!(co_flags & CO_REMOVE)) {
638 cvs_remote_output(entry);
639
640 if (!(co_flags & CO_MERGE)) {
641 mode = cf->file_rcs->rf_mode;
642 mode |= S_IWUSR;
643 bp = rcs_rev_getbuf(cf->file_rcs, rnum, 0);
644 cvs_remote_send_file_buf(cf->file_path,
645 bp, mode);
646 } else {
647 cvs_remote_send_file(tosend, cf->fd);
648 }
649 }
650 }
651
652 free(entry);
653 }
654