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