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