cvs.c revision 1.12
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
45extern char *__progname;
46
47
48/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
49int verbosity = 2;
50
51
52
53/* compression level used with zlib, 0 meaning no compression taking place */
54int   cvs_compress = 0;
55int   cvs_trace = 0;
56int   cvs_nolog = 0;
57int   cvs_readonly = 0;
58int   cvs_nocase = 0;   /* set to 1 to disable case sensitivity on filenames */
59
60/* name of the command we are running */
61char *cvs_command;
62int   cvs_cmdop;
63char *cvs_rootstr;
64char *cvs_rsh = CVS_RSH_DEFAULT;
65char *cvs_editor = CVS_EDITOR_DEFAULT;
66
67
68/* hierarchy of all the files affected by the command */
69CVSFILE *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
86static 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
247void             usage        (void);
248void             sigchld_hdlr (int);
249void             cvs_readrc   (void);
250struct 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
261void
262sigchld_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
278void
279usage(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
288int
289main(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
440struct cvs_cmd*
441cvs_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
473void
474cvs_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