xref: /openbsd-src/usr.bin/cvs/cvs.c (revision 4deeb87832e5d00dbfea5b08ed9c463296b435ef)
1 /*	$OpenBSD: cvs.c,v 1.12 2004/08/12 17:51:05 jfb Exp $	*/
2 /*
3  * Copyright (c) 2004 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/types.h>
28 #include <sys/wait.h>
29 
30 #include <err.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <signal.h>
37 #include <string.h>
38 #include <sysexits.h>
39 
40 #include "cvs.h"
41 #include "log.h"
42 #include "file.h"
43 
44 
45 extern char *__progname;
46 
47 
48 /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
49 int verbosity = 2;
50 
51 
52 
53 /* compression level used with zlib, 0 meaning no compression taking place */
54 int   cvs_compress = 0;
55 int   cvs_trace = 0;
56 int   cvs_nolog = 0;
57 int   cvs_readonly = 0;
58 int   cvs_nocase = 0;   /* set to 1 to disable case sensitivity on filenames */
59 
60 /* name of the command we are running */
61 char *cvs_command;
62 int   cvs_cmdop;
63 char *cvs_rootstr;
64 char *cvs_rsh = CVS_RSH_DEFAULT;
65 char *cvs_editor = CVS_EDITOR_DEFAULT;
66 
67 
68 /* hierarchy of all the files affected by the command */
69 CVSFILE *cvs_files;
70 
71 
72 
73 /*
74  * Command dispatch table
75  * ----------------------
76  *
77  * The synopsis field should only contain the list of arguments that the
78  * command supports, without the actual command's name.
79  *
80  * Command handlers are expected to return 0 if no error occured, or one of
81  * the values known in sysexits.h in case of an error.  In case the error
82  * returned is EX_USAGE, the command's usage string is printed to standard
83  * error before returning.
84  */
85 
86 static struct cvs_cmd {
87 	int     cmd_op;
88 	char    cmd_name[CVS_CMD_MAXNAMELEN];
89 	char    cmd_alias[CVS_CMD_MAXALIAS][CVS_CMD_MAXNAMELEN];
90 	int   (*cmd_hdlr)(int, char **);
91 	char   *cmd_synopsis;
92 	char    cmd_descr[CVS_CMD_MAXDESCRLEN];
93 } cvs_cdt[] = {
94 	{
95 		CVS_OP_ADD, "add",      { "ad",  "new" }, cvs_add,
96 		"[-m msg] file ...",
97 		"Add a new file/directory to the repository",
98 	},
99 	{
100 		-1, "admin",    { "adm", "rcs" }, NULL,
101 		"",
102 		"Administration front end for rcs",
103 	},
104 	{
105 		CVS_OP_ANNOTATE, "annotate", { "ann"        }, NULL,
106 		"",
107 		"Show last revision where each line was modified",
108 	},
109 	{
110 		CVS_OP_CHECKOUT, "checkout", { "co",  "get" }, cvs_checkout,
111 		"",
112 		"Checkout sources for editing",
113 	},
114 	{
115 		CVS_OP_COMMIT, "commit",   { "ci",  "com" }, cvs_commit,
116 		"[-flR] [-F logfile | -m msg] [-r rev] ...",
117 		"Check files into the repository",
118 	},
119 	{
120 		CVS_OP_DIFF, "diff",     { "di",  "dif" }, cvs_diff,
121 		"[-cilu] [-D date] [-r rev] ...",
122 		"Show differences between revisions",
123 	},
124 	{
125 		-1, "edit",     {              }, NULL,
126 		"",
127 		"Get ready to edit a watched file",
128 	},
129 	{
130 		-1, "editors",  {              }, NULL,
131 		"",
132 		"See who is editing a watched file",
133 	},
134 	{
135 		-1, "export",   { "ex",  "exp" }, NULL,
136 		"",
137 		"Export sources from CVS, similar to checkout",
138 	},
139 	{
140 		CVS_OP_HISTORY, "history",  { "hi",  "his" }, cvs_history,
141 		"",
142 		"Show repository access history",
143 	},
144 	{
145 		CVS_OP_IMPORT, "import",   { "im",  "imp" }, NULL,
146 		"",
147 		"Import sources into CVS, using vendor branches",
148 	},
149 	{
150 		CVS_OP_INIT, "init",     {              }, cvs_init,
151 		"",
152 		"Create a CVS repository if it doesn't exist",
153 	},
154 #if defined(HAVE_KERBEROS)
155 	{
156 		"kserver",  {}, NULL
157 		"",
158 		"Start a Kerberos authentication CVS server",
159 	},
160 #endif
161 	{
162 		CVS_OP_LOG, "log",      { "lo"         }, cvs_getlog,
163 		"",
164 		"Print out history information for files",
165 	},
166 	{
167 		-1, "login",    {}, NULL,
168 		"",
169 		"Prompt for password for authenticating server",
170 	},
171 	{
172 		-1, "logout",   {}, NULL,
173 		"",
174 		"Removes entry in .cvspass for remote repository",
175 	},
176 	{
177 		-1, "rdiff",    {}, NULL,
178 		"",
179 		"Create 'patch' format diffs between releases",
180 	},
181 	{
182 		-1, "release",  {}, NULL,
183 		"",
184 		"Indicate that a Module is no longer in use",
185 	},
186 	{
187 		CVS_OP_REMOVE, "remove",   {}, NULL,
188 		"",
189 		"Remove an entry from the repository",
190 	},
191 	{
192 		-1, "rlog",     {}, NULL,
193 		"",
194 		"Print out history information for a module",
195 	},
196 	{
197 		-1, "rtag",     {}, NULL,
198 		"",
199 		"Add a symbolic tag to a module",
200 	},
201 	{
202 		CVS_OP_SERVER, "server",   {}, cvs_server,
203 		"",
204 		"Server mode",
205 	},
206 	{
207 		CVS_OP_STATUS, "status",   {}, cvs_status,
208 		"",
209 		"Display status information on checked out files",
210 	},
211 	{
212 		CVS_OP_TAG, "tag",      { "ta", }, NULL,
213 		"",
214 		"Add a symbolic tag to checked out version of files",
215 	},
216 	{
217 		-1, "unedit",   {}, NULL,
218 		"",
219 		"Undo an edit command",
220 	},
221 	{
222 		CVS_OP_UPDATE, "update",   {}, cvs_update,
223 		"",
224 		"Bring work tree in sync with repository",
225 	},
226 	{
227 		CVS_OP_VERSION, "version",  {}, cvs_version,
228 		"",
229 		"Show current CVS version(s)",
230 	},
231 	{
232 		-1, "watch",    {}, NULL,
233 		"",
234 		"Set watches",
235 	},
236 	{
237 		-1, "watchers", {}, NULL,
238 		"",
239 		"See who is watching a file",
240 	},
241 };
242 
243 #define CVS_NBCMD  (sizeof(cvs_cdt)/sizeof(cvs_cdt[0]))
244 
245 
246 
247 void             usage        (void);
248 void             sigchld_hdlr (int);
249 void             cvs_readrc   (void);
250 struct cvs_cmd*  cvs_findcmd  (const char *);
251 
252 
253 
254 /*
255  * sigchld_hdlr()
256  *
257  * Handler for the SIGCHLD signal, which can be received in case we are
258  * running a remote server and it dies.
259  */
260 
261 void
262 sigchld_hdlr(int signo)
263 {
264 	int status;
265 	pid_t pid;
266 
267 	if ((pid = wait(&status)) == -1) {
268 	}
269 }
270 
271 
272 /*
273  * usage()
274  *
275  * Display usage information.
276  */
277 
278 void
279 usage(void)
280 {
281 	fprintf(stderr,
282 	    "Usage: %s [-lQqtvx] [-b bindir] [-d root] [-e editor] [-z level] "
283 	    "command [options] ...\n",
284 	    __progname);
285 }
286 
287 
288 int
289 main(int argc, char **argv)
290 {
291 	char *envstr, *ep;
292 	int ret;
293 	u_int i, readrc;
294 	struct cvs_cmd *cmdp;
295 
296 	readrc = 1;
297 
298 	if (cvs_log_init(LD_STD, 0) < 0)
299 		err(1, "failed to initialize logging");
300 
301 	/* by default, be very verbose */
302 	(void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
303 
304 #ifdef DEBUG
305 	(void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
306 #endif
307 
308 	/* check environment so command-line options override it */
309 	if ((envstr = getenv("CVS_RSH")) != NULL)
310 		cvs_rsh = envstr;
311 
312 	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
313 	    ((envstr = getenv("VISUAL")) != NULL) ||
314 	    ((envstr = getenv("EDITOR")) != NULL))
315 		cvs_editor = envstr;
316 
317 	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrtvz:")) != -1) {
318 		switch (ret) {
319 		case 'b':
320 			/*
321 			 * We do not care about the bin directory for RCS files
322 			 * as this program has no dependencies on RCS programs,
323 			 * so it is only here for backwards compatibility.
324 			 */
325 			cvs_log(LP_NOTICE, "the -b argument is obsolete");
326 			break;
327 		case 'd':
328 			cvs_rootstr = optarg;
329 			break;
330 		case 'e':
331 			cvs_editor = optarg;
332 			break;
333 		case 'f':
334 			readrc = 0;
335 			break;
336 		case 'l':
337 			cvs_nolog = 1;
338 			break;
339 		case 'n':
340 			break;
341 		case 'Q':
342 			verbosity = 0;
343 			break;
344 		case 'q':
345 			/* don't override -Q */
346 			if (verbosity > 1)
347 				verbosity = 1;
348 			break;
349 		case 'r':
350 			cvs_readonly = 1;
351 			break;
352 		case 't':
353 			cvs_trace = 1;
354 			break;
355 		case 'v':
356 			printf("%s\n", CVS_VERSION);
357 			exit(0);
358 			/* NOTREACHED */
359 			break;
360 		case 'x':
361 			/*
362 			 * Kerberos encryption support, kept for compatibility
363 			 */
364 			break;
365 		case 'z':
366 			cvs_compress = (int)strtol(optarg, &ep, 10);
367 			if (*ep != '\0')
368 				errx(1, "error parsing compression level");
369 			if (cvs_compress < 0 || cvs_compress > 9)
370 				errx(1, "gzip compression level must be "
371 				    "between 0 and 9");
372 			break;
373 		default:
374 			usage();
375 			exit(EX_USAGE);
376 		}
377 	}
378 
379 	argc -= optind;
380 	argv += optind;
381 
382 	/* reset getopt() for use by commands */
383 	optind = 1;
384 	optreset = 1;
385 
386 	if (argc == 0) {
387 		usage();
388 		exit(EX_USAGE);
389 	}
390 
391 	/* setup signal handlers */
392 	signal(SIGCHLD, sigchld_hdlr);
393 
394 	cvs_file_init();
395 
396 	if (readrc)
397 		cvs_readrc();
398 
399 	cvs_command = argv[0];
400 	ret = -1;
401 
402 	cmdp = cvs_findcmd(cvs_command);
403 	if (cmdp == NULL) {
404 		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
405 		fprintf(stderr, "CVS commands are:\n");
406 		for (i = 0; i < CVS_NBCMD; i++)
407 			fprintf(stderr, "\t%-16s%s\n",
408 			    cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr);
409 		exit(EX_USAGE);
410 	}
411 
412 	if (cmdp->cmd_hdlr == NULL) {
413 		cvs_log(LP_ERR, "command `%s' not implemented", cvs_command);
414 		exit(1);
415 	}
416 
417 	cvs_cmdop = cmdp->cmd_op;
418 
419 	ret = (*cmdp->cmd_hdlr)(argc, argv);
420 	if (ret == EX_USAGE) {
421 		fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
422 		    cmdp->cmd_synopsis);
423 	}
424 
425 	if (cvs_files != NULL)
426 		cvs_file_free(cvs_files);
427 
428 	return (ret);
429 }
430 
431 
432 /*
433  * cvs_findcmd()
434  *
435  * Find the entry in the command dispatch table whose name or one of its
436  * aliases matches <cmd>.
437  * Returns a pointer to the command entry on success, NULL on failure.
438  */
439 
440 struct cvs_cmd*
441 cvs_findcmd(const char *cmd)
442 {
443 	u_int i, j;
444 	struct cvs_cmd *cmdp;
445 
446 	cmdp = NULL;
447 
448 	for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) {
449 		if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0)
450 			cmdp = &cvs_cdt[i];
451 		else {
452 			for (j = 0; j < CVS_CMD_MAXALIAS; j++) {
453 				if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) {
454 					cmdp = &cvs_cdt[i];
455 					break;
456 				}
457 			}
458 		}
459 	}
460 
461 	return (cmdp);
462 }
463 
464 
465 /*
466  * cvs_readrc()
467  *
468  * Read the CVS `.cvsrc' file in the user's home directory.  If the file
469  * exists, it should contain a list of arguments that should always be given
470  * implicitly to the specified commands.
471  */
472 
473 void
474 cvs_readrc(void)
475 {
476 	char rcpath[MAXPATHLEN], linebuf[128], *lp;
477 	struct cvs_cmd *cmdp;
478 	struct passwd *pw;
479 	FILE *fp;
480 
481 	pw = getpwuid(getuid());
482 	if (pw == NULL) {
483 		cvs_log(LP_NOTICE, "failed to get user's password entry");
484 		return;
485 	}
486 
487 	snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
488 
489 	fp = fopen(rcpath, "r");
490 	if (fp == NULL) {
491 		if (errno != ENOENT)
492 			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
493 			    strerror(errno));
494 		return;
495 	}
496 
497 	while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
498 		lp = strchr(linebuf, ' ');
499 
500 		/* ignore lines with no arguments */
501 		if (lp == NULL)
502 			continue;
503 
504 		*(lp++) = '\0';
505 		if (strcmp(linebuf, "cvs") == 0) {
506 			/* global options */
507 		}
508 		else {
509 			cmdp = cvs_findcmd(linebuf);
510 			if (cmdp == NULL) {
511 				cvs_log(LP_NOTICE,
512 				    "unknown command `%s' in cvsrc",
513 				    linebuf);
514 				continue;
515 			}
516 		}
517 	}
518 	if (ferror(fp)) {
519 		cvs_log(LP_NOTICE, "failed to read line from cvsrc");
520 	}
521 
522 	(void)fclose(fp);
523 }
524