cvs.c revision 1.2
1#define DEBUG
2/*	$OpenBSD: cvs.c,v 1.2 2004/07/14 03:33:09 jfb Exp $	*/
3/*
4 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. The name of the author may not be used to endorse or promote products
14 *    derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <sys/types.h>
29#include <sys/wait.h>
30
31#include <err.h>
32#include <pwd.h>
33#include <errno.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <unistd.h>
37#include <signal.h>
38#include <string.h>
39#include <sysexits.h>
40
41#include "cvs.h"
42#include "log.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;
58
59/* name of the command we are running */
60char *cvs_command;
61char *cvs_rootstr;
62char *cvs_rsh = CVS_RSH_DEFAULT;
63char *cvs_editor = CVS_EDITOR_DEFAULT;
64
65struct cvsroot *cvs_root = NULL;
66
67
68/*
69 * Command dispatch table
70 * ----------------------
71 *
72 * The synopsis field should only contain the list of arguments that the
73 * command supports, without the actual command's name.
74 *
75 * Command handlers are expected to return 0 if no error occured, or one of
76 * the values known in sysexits.h in case of an error.  In case the error
77 * returned is EX_USAGE, the command's usage string is printed to standard
78 * error before returning.
79 */
80
81static struct cvs_cmd {
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		"add",      { "ad",  "new" }, cvs_add,
90		"[-m msg] file ...",
91		"Add a new file/directory to the repository",
92	},
93	{
94		"admin",    { "adm", "rcs" }, NULL,
95		"",
96		"Administration front end for rcs",
97	},
98	{
99		"annotate", { "ann"        }, NULL,
100		"",
101		"Show last revision where each line was modified",
102	},
103	{
104		"checkout", { "co",  "get" }, NULL,
105		"",
106		"Checkout sources for editing",
107	},
108	{
109		"commit",   { "ci",  "com" }, cvs_commit,
110		"",
111		"Check files into the repository",
112	},
113	{
114		"diff",     { "di",  "dif" }, cvs_diff,
115		"",
116		"Show differences between revisions",
117	},
118	{
119		"edit",     {              }, NULL,
120		"",
121		"Get ready to edit a watched file",
122	},
123	{
124		"editors",  {              }, NULL,
125		"",
126		"See who is editing a watched file",
127	},
128	{
129		"export",   { "ex",  "exp" }, NULL,
130		"",
131		"Export sources from CVS, similar to checkout",
132	},
133	{
134		"history",  { "hi",  "his" }, cvs_history,
135		"",
136		"Show repository access history",
137	},
138	{
139		"import",   { "im",  "imp" }, NULL,
140		"",
141		"Import sources into CVS, using vendor branches",
142	},
143	{
144		"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		"log",      { "lo"         }, cvs_getlog,
157		"",
158		"Print out history information for files",
159	},
160	{
161		"login",    {}, NULL,
162		"",
163		"Prompt for password for authenticating server",
164	},
165	{
166		"logout",   {}, NULL,
167		"",
168		"Removes entry in .cvspass for remote repository",
169	},
170	{
171		"rdiff",    {}, NULL,
172		"",
173		"Create 'patch' format diffs between releases",
174	},
175	{
176		"release",  {}, NULL,
177		"",
178		"Indicate that a Module is no longer in use",
179	},
180	{
181		"remove",   {}, NULL,
182		"",
183		"Remove an entry from the repository",
184	},
185	{
186		"rlog",     {}, NULL,
187		"",
188		"Print out history information for a module",
189	},
190	{
191		"rtag",     {}, NULL,
192		"",
193		"Add a symbolic tag to a module",
194	},
195	{
196		"server",   {}, cvs_server,
197		"",
198		"Server mode",
199	},
200	{
201		"status",   {}, NULL,
202		"",
203		"Display status information on checked out files",
204	},
205	{
206		"tag",      { "ta", }, NULL,
207		"",
208		"Add a symbolic tag to checked out version of files",
209	},
210	{
211		"unedit",   {}, NULL,
212		"",
213		"Undo an edit command",
214	},
215	{
216		"update",   {}, cvs_update,
217		"",
218		"Bring work tree in sync with repository",
219	},
220	{
221		"version",  {}, cvs_version,
222		"",
223		"Show current CVS version(s)",
224	},
225	{
226		"watch",    {}, NULL,
227		"",
228		"Set watches",
229	},
230	{
231		"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
241void             usage        (void);
242void             sigchld_hdlr (int);
243void             cvs_readrc   (void);
244struct 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
255void
256sigchld_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
272void
273usage(void)
274{
275	fprintf(stderr,
276	    "Usage: %s [-lQqtv] [-d root] [-e editor] [-z level] "
277	    "command [options] ...\n",
278	    __progname);
279}
280
281
282int
283main(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	ret = (*cmdp->cmd_hdlr)(argc, argv);
399	if (ret == EX_USAGE) {
400		fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
401		    cmdp->cmd_synopsis);
402	}
403
404	return (ret);
405}
406
407
408/*
409 * cvs_findcmd()
410 *
411 * Find the entry in the command dispatch table whose name or one of its
412 * aliases matches <cmd>.
413 * Returns a pointer to the command entry on success, NULL on failure.
414 */
415
416struct cvs_cmd*
417cvs_findcmd(const char *cmd)
418{
419	u_int i, j;
420	struct cvs_cmd *cmdp;
421
422	cmdp = NULL;
423
424	for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) {
425		if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0)
426			cmdp = &cvs_cdt[i];
427		else {
428			for (j = 0; j < CVS_CMD_MAXALIAS; j++) {
429				if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) {
430					cmdp = &cvs_cdt[i];
431					break;
432				}
433			}
434		}
435	}
436
437	return (cmdp);
438}
439
440
441/*
442 * cvs_readrc()
443 *
444 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
445 * exists, it should contain a list of arguments that should always be given
446 * implicitly to the specified commands.
447 */
448
449void
450cvs_readrc(void)
451{
452	char rcpath[MAXPATHLEN], linebuf[128], *lp;
453	struct cvs_cmd *cmdp;
454	struct passwd *pw;
455	FILE *fp;
456
457	pw = getpwuid(getuid());
458	if (pw == NULL) {
459		cvs_log(LP_NOTICE, "failed to get user's password entry");
460		return;
461	}
462
463	snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
464
465	fp = fopen(rcpath, "r");
466	if (fp == NULL) {
467		if (errno != ENOENT)
468			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
469			    strerror(errno));
470		return;
471	}
472
473	while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
474		lp = strchr(linebuf, ' ');
475
476		/* ignore lines with no arguments */
477		if (lp == NULL)
478			continue;
479
480		*(lp++) = '\0';
481		if (strcmp(linebuf, "cvs") == 0) {
482			/* global options */
483		}
484		else {
485			cmdp = cvs_findcmd(linebuf);
486			if (cmdp == NULL) {
487				cvs_log(LP_NOTICE,
488				    "unknown command `%s' in cvsrc",
489				    linebuf);
490				continue;
491			}
492		}
493	}
494	if (ferror(fp)) {
495		cvs_log(LP_NOTICE, "failed to read line from cvsrc");
496	}
497
498	(void)fclose(fp);
499}
500