1 /* $OpenBSD: rcsprog.c,v 1.165 2023/08/11 05:02:21 guenther Exp $ */
2 /*
3 * Copyright (c) 2005 Jean-Francois Brousseau <jfb@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 <sys/stat.h>
28
29 #include <err.h>
30 #include <signal.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include "rcsprog.h"
37
38 #define RCSPROG_OPTSTRING "A:a:b::c:e::Iik:Ll::m:Mn:N:o:qt::TUu::Vx::z::"
39
40 const char rcs_version[] = "OpenRCS 4.5";
41
42 int rcsflags;
43 int rcs_optind;
44 char *rcs_optarg;
45 char *rcs_suffixes = RCS_DEFAULT_SUFFIX;
46 char *rcs_tmpdir = RCS_TMPDIR_DEFAULT;
47
48 struct rcs_prog {
49 char *prog_name;
50 int (*prog_hdlr)(int, char **);
51 void (*prog_usage)(void);
52 } programs[] = {
53 { "rcs", rcs_main, rcs_usage },
54 { "ci", checkin_main, checkin_usage },
55 { "co", checkout_main, checkout_usage },
56 { "rcsclean", rcsclean_main, rcsclean_usage },
57 { "rcsdiff", rcsdiff_main, rcsdiff_usage },
58 { "rcsmerge", rcsmerge_main, rcsmerge_usage },
59 { "rlog", rlog_main, rlog_usage },
60 { "ident", ident_main, ident_usage },
61 { "merge", merge_main, merge_usage },
62 };
63 void (*usage)(void);
64
65 struct wklhead temp_files;
66
67 void sighdlr(int);
68 static void rcs_attach_symbol(RCSFILE *, const char *);
69
70 void
sighdlr(int sig)71 sighdlr(int sig)
72 {
73 worklist_clean(&temp_files, worklist_unlink);
74 _exit(1);
75 }
76
77 int
build_cmd(char *** cmd_argv,char ** argv,int argc)78 build_cmd(char ***cmd_argv, char **argv, int argc)
79 {
80 int cmd_argc, i, cur;
81 char *cp, *rcsinit, *linebuf, *lp;
82
83 if ((rcsinit = getenv("RCSINIT")) == NULL) {
84 *cmd_argv = argv;
85 return argc;
86 }
87
88 cur = argc + 2;
89 cmd_argc = 0;
90 *cmd_argv = xcalloc(cur, sizeof(char *));
91 (*cmd_argv)[cmd_argc++] = argv[0];
92
93 linebuf = xstrdup(rcsinit);
94 for (lp = linebuf; lp != NULL;) {
95 cp = strsep(&lp, " \t\b\f\n\r\t\v");
96 if (cp == NULL)
97 break;
98 if (*cp == '\0')
99 continue;
100
101 if (cmd_argc == cur) {
102 cur += 8;
103 *cmd_argv = xreallocarray(*cmd_argv, cur,
104 sizeof(char *));
105 }
106
107 (*cmd_argv)[cmd_argc++] = cp;
108 }
109
110 if (cmd_argc + argc > cur) {
111 cur = cmd_argc + argc + 1;
112 *cmd_argv = xreallocarray(*cmd_argv, cur,
113 sizeof(char *));
114 }
115
116 for (i = 1; i < argc; i++)
117 (*cmd_argv)[cmd_argc++] = argv[i];
118
119 (*cmd_argv)[cmd_argc] = NULL;
120
121 return cmd_argc;
122 }
123
124 int
main(int argc,char ** argv)125 main(int argc, char **argv)
126 {
127 u_int i;
128 char **cmd_argv;
129 int ret, cmd_argc;
130
131 if (pledge("stdio rpath wpath cpath fattr flock getpw", NULL) == -1)
132 err(2, "pledge");
133
134 ret = -1;
135 rcs_optind = 1;
136 SLIST_INIT(&temp_files);
137
138 cmd_argc = build_cmd(&cmd_argv, argv, argc);
139
140 if ((rcs_tmpdir = getenv("TMPDIR")) == NULL)
141 rcs_tmpdir = RCS_TMPDIR_DEFAULT;
142
143 signal(SIGHUP, sighdlr);
144 signal(SIGINT, sighdlr);
145 signal(SIGQUIT, sighdlr);
146 signal(SIGABRT, sighdlr);
147 signal(SIGALRM, sighdlr);
148 signal(SIGTERM, sighdlr);
149
150 for (i = 0; i < (sizeof(programs)/sizeof(programs[0])); i++)
151 if (strcmp(__progname, programs[i].prog_name) == 0) {
152 usage = programs[i].prog_usage;
153 ret = programs[i].prog_hdlr(cmd_argc, cmd_argv);
154 break;
155 }
156
157 /* clean up temporary files */
158 worklist_run(&temp_files, worklist_unlink);
159
160 exit(ret);
161 }
162
163
164 __dead void
rcs_usage(void)165 rcs_usage(void)
166 {
167 fprintf(stderr,
168 "usage: rcs [-IiLqTUV] [-Aoldfile] [-ausers] [-b[rev]]\n"
169 " [-cstring] [-e[users]] [-kmode] [-l[rev]] [-mrev:msg]\n"
170 " [-orev] [-t[str]] [-u[rev]] [-xsuffixes] file ...\n");
171
172 exit(1);
173 }
174
175 /*
176 * rcs_main()
177 *
178 * Handler for the `rcs' program.
179 * Returns 0 on success, or >0 on error.
180 */
181 int
rcs_main(int argc,char ** argv)182 rcs_main(int argc, char **argv)
183 {
184 int fd;
185 int i, j, ch, flags, kflag, lkmode;
186 const char *nflag, *oldfilename, *orange;
187 char fpath[PATH_MAX];
188 char *logstr, *logmsg, *descfile;
189 char *alist, *comment, *elist, *lrev, *urev;
190 mode_t fmode;
191 RCSFILE *file;
192 RCSNUM *logrev;
193 struct rcs_access *acp;
194 struct timespec rcs_mtime = { .tv_sec = 0, .tv_nsec = UTIME_OMIT };
195
196 kflag = RCS_KWEXP_ERR;
197 lkmode = RCS_LOCK_INVAL;
198 fmode = S_IRUSR|S_IRGRP|S_IROTH;
199 flags = RCS_RDWR|RCS_PARSE_FULLY;
200 lrev = urev = descfile = NULL;
201 logstr = alist = comment = elist = NULL;
202 nflag = oldfilename = orange = NULL;
203
204 /* match GNU */
205 if (1 < argc && argv[1][0] != '-')
206 warnx("warning: No options were given; "
207 "this usage is obsolescent.");
208
209 while ((ch = rcs_getopt(argc, argv, RCSPROG_OPTSTRING)) != -1) {
210 switch (ch) {
211 case 'A':
212 oldfilename = rcs_optarg;
213 rcsflags |= CO_ACLAPPEND;
214 break;
215 case 'a':
216 alist = rcs_optarg;
217 break;
218 case 'c':
219 comment = rcs_optarg;
220 break;
221 case 'e':
222 elist = rcs_optarg;
223 rcsflags |= RCSPROG_EFLAG;
224 break;
225 case 'I':
226 rcsflags |= INTERACTIVE;
227 break;
228 case 'i':
229 flags |= RCS_CREATE;
230 break;
231 case 'k':
232 kflag = rcs_kflag_get(rcs_optarg);
233 if (RCS_KWEXP_INVAL(kflag)) {
234 warnx("invalid RCS keyword substitution mode");
235 (usage)();
236 }
237 break;
238 case 'L':
239 if (lkmode == RCS_LOCK_LOOSE)
240 warnx("-U overridden by -L");
241 lkmode = RCS_LOCK_STRICT;
242 break;
243 case 'l':
244 if (rcsflags & RCSPROG_UFLAG)
245 warnx("-u overridden by -l");
246 lrev = rcs_optarg;
247 rcsflags &= ~RCSPROG_UFLAG;
248 rcsflags |= RCSPROG_LFLAG;
249 break;
250 case 'm':
251 free(logstr);
252 logstr = xstrdup(rcs_optarg);
253 break;
254 case 'M':
255 /* ignore for the moment */
256 break;
257 case 'n':
258 nflag = rcs_optarg;
259 break;
260 case 'N':
261 nflag = rcs_optarg;
262 rcsflags |= RCSPROG_NFLAG;
263 break;
264 case 'o':
265 orange = rcs_optarg;
266 break;
267 case 'q':
268 rcsflags |= QUIET;
269 break;
270 case 't':
271 descfile = rcs_optarg;
272 rcsflags |= DESCRIPTION;
273 break;
274 case 'T':
275 rcsflags |= PRESERVETIME;
276 break;
277 case 'U':
278 if (lkmode == RCS_LOCK_STRICT)
279 warnx("-L overridden by -U");
280 lkmode = RCS_LOCK_LOOSE;
281 break;
282 case 'u':
283 if (rcsflags & RCSPROG_LFLAG)
284 warnx("-l overridden by -u");
285 urev = rcs_optarg;
286 rcsflags &= ~RCSPROG_LFLAG;
287 rcsflags |= RCSPROG_UFLAG;
288 break;
289 case 'V':
290 printf("%s\n", rcs_version);
291 exit(0);
292 case 'x':
293 /* Use blank extension if none given. */
294 rcs_suffixes = rcs_optarg ? rcs_optarg : "";
295 break;
296 case 'z':
297 /*
298 * kept for compatibility
299 */
300 break;
301 default:
302 (usage)();
303 }
304 }
305
306 argc -= rcs_optind;
307 argv += rcs_optind;
308
309 if (argc == 0) {
310 warnx("no input file");
311 (usage)();
312 }
313
314 for (i = 0; i < argc; i++) {
315 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath));
316 if (fd < 0 && !(flags & RCS_CREATE)) {
317 warn("%s", fpath);
318 continue;
319 }
320
321 if (!(rcsflags & QUIET))
322 (void)fprintf(stderr, "RCS file: %s\n", fpath);
323
324 if ((file = rcs_open(fpath, fd, flags, fmode)) == NULL) {
325 close(fd);
326 continue;
327 }
328
329 if (rcsflags & DESCRIPTION) {
330 if (rcs_set_description(file, descfile, rcsflags) == -1) {
331 warn("%s", descfile);
332 rcs_close(file);
333 continue;
334 }
335 }
336 else if (flags & RCS_CREATE) {
337 if (rcs_set_description(file, NULL, rcsflags) == -1) {
338 warn("stdin");
339 rcs_close(file);
340 continue;
341 }
342 }
343
344 if (rcsflags & PRESERVETIME)
345 rcs_mtime = rcs_get_mtime(file);
346
347 if (nflag != NULL)
348 rcs_attach_symbol(file, nflag);
349
350 if (logstr != NULL) {
351 if ((logmsg = strchr(logstr, ':')) == NULL) {
352 warnx("missing log message");
353 rcs_close(file);
354 continue;
355 }
356
357 *logmsg++ = '\0';
358 if ((logrev = rcsnum_parse(logstr)) == NULL) {
359 warnx("`%s' bad revision number", logstr);
360 rcs_close(file);
361 continue;
362 }
363
364 if (rcs_rev_setlog(file, logrev, logmsg) < 0) {
365 warnx("failed to set logmsg for `%s' to `%s'",
366 logstr, logmsg);
367 rcs_close(file);
368 rcsnum_free(logrev);
369 continue;
370 }
371
372 rcsnum_free(logrev);
373 }
374
375 /* entries to add from <oldfile> */
376 if (rcsflags & CO_ACLAPPEND) {
377 RCSFILE *oldfile;
378 int ofd;
379 char ofpath[PATH_MAX];
380
381 ofd = rcs_choosefile(oldfilename, ofpath, sizeof(ofpath));
382 if (ofd < 0) {
383 if (!(flags & RCS_CREATE))
384 warn("%s", ofpath);
385 exit(1);
386 }
387 if ((oldfile = rcs_open(ofpath, ofd, RCS_READ)) == NULL)
388 exit(1);
389
390 TAILQ_FOREACH(acp, &(oldfile->rf_access), ra_list)
391 rcs_access_add(file, acp->ra_name);
392
393 rcs_close(oldfile);
394 (void)close(ofd);
395 }
396
397 /* entries to add to the access list */
398 if (alist != NULL) {
399 struct rcs_argvector *aargv;
400
401 aargv = rcs_strsplit(alist, ",");
402 for (j = 0; aargv->argv[j] != NULL; j++)
403 rcs_access_add(file, aargv->argv[j]);
404
405 rcs_argv_destroy(aargv);
406 }
407
408 if (comment != NULL)
409 rcs_comment_set(file, comment);
410
411 if (elist != NULL) {
412 struct rcs_argvector *eargv;
413
414 eargv = rcs_strsplit(elist, ",");
415 for (j = 0; eargv->argv[j] != NULL; j++)
416 rcs_access_remove(file, eargv->argv[j]);
417
418 rcs_argv_destroy(eargv);
419 } else if (rcsflags & RCSPROG_EFLAG) {
420 struct rcs_access *rap;
421
422 /* XXX rcs_access_remove(file, NULL); ?? */
423 while (!TAILQ_EMPTY(&(file->rf_access))) {
424 rap = TAILQ_FIRST(&(file->rf_access));
425 TAILQ_REMOVE(&(file->rf_access), rap, ra_list);
426 free(rap->ra_name);
427 free(rap);
428 }
429 /* not synced anymore */
430 file->rf_flags &= ~RCS_SYNCED;
431 }
432
433 rcs_kwexp_set(file, kflag);
434
435 if (lkmode != RCS_LOCK_INVAL)
436 (void)rcs_lock_setmode(file, lkmode);
437
438 if (rcsflags & RCSPROG_LFLAG) {
439 RCSNUM *rev;
440 const char *username;
441 char rev_str[RCS_REV_BUFSZ];
442
443 if (file->rf_head == NULL) {
444 warnx("%s contains no revisions", fpath);
445 rcs_close(file);
446 continue;
447 }
448
449 if ((username = getlogin()) == NULL)
450 err(1, "getlogin");
451 if (lrev == NULL) {
452 rev = rcsnum_alloc();
453 rcsnum_cpy(file->rf_head, rev, 0);
454 } else if ((rev = rcsnum_parse(lrev)) == NULL) {
455 warnx("unable to unlock file");
456 rcs_close(file);
457 continue;
458 }
459 rcsnum_tostr(rev, rev_str, sizeof(rev_str));
460 /* Make sure revision exists. */
461 if (rcs_findrev(file, rev) == NULL)
462 errx(1, "%s: cannot lock nonexisting "
463 "revision %s", fpath, rev_str);
464 if (rcs_lock_add(file, username, rev) != -1 &&
465 !(rcsflags & QUIET))
466 (void)fprintf(stderr, "%s locked\n", rev_str);
467 rcsnum_free(rev);
468 }
469
470 if (rcsflags & RCSPROG_UFLAG) {
471 RCSNUM *rev;
472 const char *username;
473 char rev_str[RCS_REV_BUFSZ];
474
475 if (file->rf_head == NULL) {
476 warnx("%s contains no revisions", fpath);
477 rcs_close(file);
478 continue;
479 }
480
481 if ((username = getlogin()) == NULL)
482 err(1, "getlogin");
483 if (urev == NULL) {
484 rev = rcsnum_alloc();
485 rcsnum_cpy(file->rf_head, rev, 0);
486 } else if ((rev = rcsnum_parse(urev)) == NULL) {
487 warnx("unable to unlock file");
488 rcs_close(file);
489 continue;
490 }
491 rcsnum_tostr(rev, rev_str, sizeof(rev_str));
492 /* Make sure revision exists. */
493 if (rcs_findrev(file, rev) == NULL)
494 errx(1, "%s: cannot unlock nonexisting "
495 "revision %s", fpath, rev_str);
496 if (rcs_lock_remove(file, username, rev) == -1 &&
497 !(rcsflags & QUIET))
498 warnx("%s: warning: No locks are set.", fpath);
499 else {
500 if (!(rcsflags & QUIET))
501 (void)fprintf(stderr,
502 "%s unlocked\n", rev_str);
503 }
504 rcsnum_free(rev);
505 }
506
507 if (orange != NULL) {
508 struct rcs_delta *rdp, *nrdp;
509 char b[RCS_REV_BUFSZ];
510
511 rcs_rev_select(file, orange);
512 for (rdp = TAILQ_FIRST(&(file->rf_delta));
513 rdp != NULL; rdp = nrdp) {
514 nrdp = TAILQ_NEXT(rdp, rd_list);
515
516 /*
517 * Delete selected revisions.
518 */
519 if (rdp->rd_flags & RCS_RD_SELECT) {
520 rcsnum_tostr(rdp->rd_num, b, sizeof(b));
521
522 if (rdp->rd_locker != NULL) {
523 errx(1, "%s: can't remove "
524 "locked revision %s",
525 fpath, b);
526 continue;
527 }
528
529 if (!(rcsflags & QUIET)) {
530 (void)fprintf(stderr, "deleting"
531 " revision %s\n", b);
532 }
533 (void)rcs_rev_remove(file, rdp->rd_num);
534 }
535 }
536 }
537
538 rcs_write(file);
539
540 if (rcsflags & PRESERVETIME)
541 rcs_set_mtime(file, rcs_mtime);
542
543 rcs_close(file);
544
545 if (!(rcsflags & QUIET))
546 (void)fprintf(stderr, "done\n");
547 }
548
549 return (0);
550 }
551
552 static void
rcs_attach_symbol(RCSFILE * file,const char * symname)553 rcs_attach_symbol(RCSFILE *file, const char *symname)
554 {
555 char *rnum;
556 RCSNUM *rev;
557 char rbuf[RCS_REV_BUFSZ];
558 int rm;
559
560 rm = 0;
561 rev = NULL;
562 if ((rnum = strrchr(symname, ':')) != NULL) {
563 if (rnum[1] == '\0')
564 rev = file->rf_head;
565 *(rnum++) = '\0';
566 } else {
567 rm = 1;
568 }
569
570 if (rev == NULL && rm != 1) {
571 if ((rev = rcsnum_parse(rnum)) == NULL)
572 errx(1, "bad revision %s", rnum);
573 }
574
575 if (rcsflags & RCSPROG_NFLAG)
576 rm = 1;
577
578 if (rm == 1) {
579 if (rcs_sym_remove(file, symname) < 0) {
580 if (rcs_errno == RCS_ERR_NOENT &&
581 !(rcsflags & RCSPROG_NFLAG))
582 warnx("cannot delete nonexisting symbol %s",
583 symname);
584 } else {
585 if (rcsflags & RCSPROG_NFLAG)
586 rm = 0;
587 }
588 }
589
590 if (rm == 0) {
591 if (rcs_sym_add(file, symname, rev) < 0 &&
592 rcs_errno == RCS_ERR_DUPENT) {
593 rcsnum_tostr(rcs_sym_getrev(file, symname),
594 rbuf, sizeof(rbuf));
595 errx(1, "symbolic name %s already bound to %s",
596 symname, rbuf);
597 }
598 }
599 }
600