ftp.nice.ch/pub/next/unix/developer/cvs.950905.s.tar.gz#/cvs-1.5.1/src/main.c

This is main.c in view mode; [Download] [Up]

/*
 *    Copyright (c) 1992, Brian Berliner and Jeff Polk
 *    Copyright (c) 1989-1992, Brian Berliner
 *
 *    You may distribute under the terms of the GNU General Public License
 *    as specified in the README file that comes with the CVS 1.4 kit.
 *
 * This is the main C driver for the CVS system.
 *
 * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing
 * the shell-script CVS system that this is based on.
 *
 * Usage:
 *	cvs [options] command [options] [files/modules...]
 *
 * Where "command" is composed of:
 *		admin		RCS command
 *		checkout	Check out a module/dir/file
 *		export		Like checkout, but used for exporting sources
 *		update		Brings work tree in sync with repository
 *		commit		Checks files into the repository
 *		diff		Runs diffs between revisions
 *		log		Prints "rlog" information for files
 *		add		Adds an entry to the repository
 *		remove		Removes an entry from the repository
 *		status		Status info on the revisions
 *		rdiff		"patch" format diff listing between releases
 *		tag		Add/delete a symbolic tag to the RCS file
 *		rtag		Add/delete a symbolic tag to the RCS file
 *		import		Import sources into CVS, using vendor branches
 *		release		Indicate that Module is no longer in use.
 *		history		Display history of Users and Modules.
 */

#include "cvs.h"
#include "patchlevel.h"

#if HAVE_KERBEROS
#include <sys/socket.h>
#include <netinet/in.h>
#include <krb.h>
#include <pwd.h>
#ifndef HAVE_KRB_GET_ERR_TEXT
#define krb_get_err_text(status) krb_err_txt[status]
#endif
#endif

#ifndef lint
static const char rcsid[] = "$CVSid: @(#)main.c 1.78 94/10/07 $\n";
USE(rcsid)
#endif

char *program_name;
char *command_name = "";

/*
 * Since some systems don't define this...
 */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN  256
#endif

char hostname[MAXHOSTNAMELEN];

int use_editor = TRUE;
int use_cvsrc = TRUE;
int cvswrite = !CVSREAD_DFLT;
int really_quiet = FALSE;
int quiet = FALSE;
int trace = FALSE;
int noexec = FALSE;
int logoff = FALSE;

char *CurDir;

/*
 * Defaults, for the environment variables that are not set
 */
char *Rcsbin = RCSBIN_DFLT;
char *Editor = EDITOR_DFLT;
char *CVSroot = CVSROOT_DFLT;
#ifdef CVSADM_ROOT
/*
 * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root'
 */
char *CVSADM_Root = CVSROOT_DFLT;
#endif /* CVSADM_ROOT */

int add PROTO((int argc, char **argv));
int admin PROTO((int argc, char **argv));
int checkout PROTO((int argc, char **argv));
int commit PROTO((int argc, char **argv));
int diff PROTO((int argc, char **argv));
int history PROTO((int argc, char **argv));
int import PROTO((int argc, char **argv));
int cvslog PROTO((int argc, char **argv));
int patch PROTO((int argc, char **argv));
int release PROTO((int argc, char **argv));
int cvsremove PROTO((int argc, char **argv));
int rtag PROTO((int argc, char **argv));
int status PROTO((int argc, char **argv));
int tag PROTO((int argc, char **argv));
int update PROTO((int argc, char **argv));

const struct cmd
{
    char *fullname;		/* Full name of the function (e.g. "commit") */
    char *nick1;		/* alternate name (e.g. "ci") */
    char *nick2;		/* another alternate names (e.g. "ci") */
    int (*func) ();		/* Function takes (argc, argv) arguments. */
#ifdef CLIENT_SUPPORT
    int (*client_func) ();	/* Function to do it via the protocol.  */
#endif
} cmds[] =

{
#ifdef CLIENT_SUPPORT
#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1, f2 }
#else
#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1 }
#endif

    CMD_ENTRY("add",      "ad",    "new",     add,       client_add),
#ifndef CVS_NOADMIN
    CMD_ENTRY("admin",    "adm",   "rcs",     admin,     client_admin),
#endif
    CMD_ENTRY("checkout", "co",    "get",     checkout,  client_checkout),
    CMD_ENTRY("commit",   "ci",    "com",     commit,    client_commit),
    CMD_ENTRY("diff",     "di",    "dif",     diff,      client_diff),
    CMD_ENTRY("export",   "exp",   "ex",      checkout,  client_export),
    CMD_ENTRY("history",  "hi",    "his",     history,   client_history),
    CMD_ENTRY("import",   "im",    "imp",     import,    client_import),
    CMD_ENTRY("log",      "lo",    "rlog",    cvslog,    client_log),
    CMD_ENTRY("rdiff",    "patch", "pa",      patch,     client_rdiff),
    CMD_ENTRY("release",  "re",    "rel",     release,   client_release),
    CMD_ENTRY("remove",   "rm",    "delete",  cvsremove, client_remove),
    CMD_ENTRY("status",   "st",    "stat",    status,    client_status),
    CMD_ENTRY("rtag",     "rt",    "rfreeze", rtag,      client_rtag),
    CMD_ENTRY("tag",      "ta",    "freeze",  tag,       client_tag),
    CMD_ENTRY("update",   "up",    "upd",     update,    client_update),

#ifdef SERVER_SUPPORT
    /*
     * The client_func is also server because we might have picked up a
     * CVSROOT environment variable containing a colon.  The client will send
     * the real root later.
     */
    CMD_ENTRY("server",   "server", "server", server,    server),
#endif
    CMD_ENTRY(NULL, NULL, NULL, NULL, NULL),

#undef CMD_ENTRY
};

static const char *const usg[] =
{
    "Usage: %s [cvs-options] command [command-options] [files...]\n",
    "    Where 'cvs-options' are:\n",
    "        -H           Displays Usage information for command\n",
    "        -Q           Cause CVS to be really quiet.\n",
    "        -q           Cause CVS to be somewhat quiet.\n",
    "        -r           Make checked-out files read-only\n",
    "        -w           Make checked-out files read-write (default)\n",
    "        -l           Turn History logging off\n",
    "        -n           Do not execute anything that will change the disk\n",
    "        -t           Show trace of program execution -- Try with -n\n",
    "        -v           CVS version and copyright\n",
    "        -b bindir    Find RCS programs in 'bindir'\n",
    "        -e editor    Use 'editor' for editing log information\n",
    "        -d CVS_root  Overrides $CVSROOT as the root of the CVS tree\n",
    "        -f           Do not use the ~/.cvsrc file\n",
#ifdef CLIENT_SUPPORT
    "        -z #         Use 'gzip -#' for net traffic if possible.\n",
#endif
    "\n",
    "    and where 'command' is:\n",
    "        add          Adds a new file/directory to the repository\n",
    "        admin        Administration front end for rcs\n",
    "        checkout     Checkout sources for editing\n",
    "        commit       Checks files into the repository\n",
    "        diff         Runs diffs between revisions\n",
    "        history      Shows status of files and users\n",
    "        import       Import sources into CVS, using vendor branches\n",
    "        export       Export sources from CVS, similar to checkout\n",
    "        log          Prints out 'rlog' information for files\n",
    "        rdiff        'patch' format diffs between releases\n",
    "        release      Indicate that a Module is no longer in use\n",
    "        remove       Removes an entry from the repository\n",
    "        status       Status info on the revisions\n",
    "        tag          Add a symbolic tag to checked out version of RCS file\n",
    "        rtag         Add a symbolic tag to the RCS file\n",
    "        update       Brings work tree in sync with repository\n",
    NULL,
};

static RETSIGTYPE
main_cleanup ()
{
    exit (1);
}

static void
error_cleanup ()
{
    Lock_Cleanup();
#ifdef SERVER_SUPPORT
    if (server_active)
	server_cleanup (0);
#endif
}

int
main (argc, argv)
    int argc;
    char **argv;
{
    extern char *version_string;
    extern char *config_string;
    char *cp;
    const struct cmd *cm;
    int c, help = FALSE, err = 0;
    int rcsbin_update_env, cvs_update_env = 0;
    char tmp[PATH_MAX];

    error_set_cleanup (error_cleanup);

    /*
     * Just save the last component of the path for error messages
     */
    program_name = last_component (argv[0]);

    CurDir = xmalloc (PATH_MAX);
#ifndef SERVER_SUPPORT
    if (!getwd (CurDir))
	error (1, 0, "cannot get working directory: %s", CurDir);
#endif

    /*
     * Query the environment variables up-front, so that
     * they can be overridden by command line arguments
     */
    rcsbin_update_env = *Rcsbin;	/* RCSBIN_DFLT must be set */
    cvs_update_env = 0;
    if ((cp = getenv (RCSBIN_ENV)) != NULL)
    {
	Rcsbin = cp;
	rcsbin_update_env = 0;		/* it's already there */
    }
    if ((cp = getenv (EDITOR1_ENV)) != NULL)
 	Editor = cp;
    else if ((cp = getenv (EDITOR2_ENV)) != NULL)
	Editor = cp;
    else if ((cp = getenv (EDITOR3_ENV)) != NULL)
	Editor = cp;
    if ((cp = getenv (CVSROOT_ENV)) != NULL)
    {
	CVSroot = cp;
	cvs_update_env = 0;		/* it's already there */
    }
    if (getenv (CVSREAD_ENV) != NULL)
	cvswrite = FALSE;

    optind = 1;
    while ((c = getopt (argc, argv, "Qqrwtnlvb:e:d:Hfz:")) != -1)
    {
	switch (c)
	{
	    case 'Q':
		really_quiet = TRUE;
		/* FALL THROUGH */
	    case 'q':
		quiet = TRUE;
		break;
	    case 'r':
		cvswrite = FALSE;
		break;
	    case 'w':
		cvswrite = TRUE;
		break;
	    case 't':
		trace = TRUE;
		break;
	    case 'n':
		noexec = TRUE;
	    case 'l':			/* Fall through */
		logoff = TRUE;
		break;
	    case 'v':
		(void) fputs (version_string, stdout);
		(void) fputs (config_string, stdout);
		(void) sprintf (tmp, "Patch Level: %d\n", PATCHLEVEL);
		(void) fputs (tmp, stdout);
		(void) fputs ("\n", stdout);
		(void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout);
		(void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout);
		(void) fputs ("Copyright (c) 1992, Brian Berliner and Jeff Polk\n", stdout);
		(void) fputs ("Copyright (c) 1989-1992, Brian Berliner\n", stdout);
		(void) fputs ("\n", stdout);
		(void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout);
		(void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout);
		exit (0);
		break;
	    case 'b':
		Rcsbin = optarg;
		rcsbin_update_env = 1;	/* need to update environment */
		break;
	    case 'e':
		Editor = optarg;
		break;
	    case 'd':
		CVSroot = optarg;
		cvs_update_env = 1;	/* need to update environment */
		break;
	    case 'H':
		use_cvsrc = FALSE;      /* this ensure that cvs -H works */
		help = TRUE;
		break;
            case 'f':
		use_cvsrc = FALSE;
		break;
#ifdef CLIENT_SUPPORT
	    case 'z':
		gzip_level = atoi (optarg);
		if (gzip_level <= 0 || gzip_level > 9)
		  error (1, 0,
			 "gzip compression level must be between 1 and 9");
		break;
#endif
	    case '?':
	    default:
		usage (usg);
	}
    }
    argc -= optind;
    argv += optind;
    if (argc < 1)
	usage (usg);

#ifdef HAVE_KERBEROS
    /* If we are invoked with a single argument "kserver", then we are
       running as Kerberos server as root.  Do the authentication as
       the very first thing, to minimize the amount of time we are
       running as root.  */
    if (strcmp (argv[0], "kserver") == 0)
    {
	int status;
	char instance[INST_SZ];
	struct sockaddr_in peer;
	struct sockaddr_in laddr;
	int len;
	KTEXT_ST ticket;
	AUTH_DAT auth;
	char version[KRB_SENDAUTH_VLEN];
	Key_schedule sched;
	char user[ANAME_SZ];
	struct passwd *pw;

	strcpy (instance, "*");
	len = sizeof peer;
	if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0
	    || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr,
			    &len) < 0)
	{
	    printf ("E Fatal error, aborting.\n\
error %s getpeername or getsockname failed\n", strerror (errno));
	    exit (1);
	}

	status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd",
			       instance, &peer, &laddr, &auth, "", sched,
			       version);
	if (status != KSUCCESS)
	{
	    printf ("E Fatal error, aborting.\n\
error 0 kerberos: %s\n", krb_get_err_text(status));
	    exit (1);
	}

	/* Get the local name.  */
	status = krb_kntoln (&auth, user);
	if (status != KSUCCESS)
	{
	    printf ("E Fatal error, aborting.\n\
error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status));
	    exit (1);
	}

	pw = getpwnam (user);
	if (pw == NULL)
	{
	    printf ("E Fatal error, aborting.\n\
error 0 %s: no such user\n", user);
	    exit (1);
	}

	initgroups (pw->pw_name, pw->pw_gid);
	setgid (pw->pw_gid);
	setuid (pw->pw_uid);
	/* Inhibit access by randoms.  Don't want people randomly
	   changing our temporary tree before we check things in.  */
	umask (077);

#if HAVE_PUTENV
	/* Set LOGNAME and USER in the environment, in case they are
           already set to something else.  */
	{
	    char *env;

	    env = xmalloc (sizeof "LOGNAME=" + strlen (user));
	    (void) sprintf (env, "LOGNAME=%s", user);
	    (void) putenv (env);

	    env = xmalloc (sizeof "USER=" + strlen (user));
	    (void) sprintf (env, "USER=%s", user);
	    (void) putenv (env);
	}
#endif

	/* Pretend we were invoked as a plain server.  */
	argv[0] = "server";
    }
#endif /* HAVE_KERBEROS */

#ifdef CVSADM_ROOT
    /*
     * See if we are able to find a 'better' value for CVSroot in the
     * CVSADM_ROOT directory.
     */
#ifdef SERVER_SUPPORT
    if (strcmp (argv[0], "server") == 0 && CVSroot == NULL)
        CVSADM_Root = NULL;
    else
        CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
#else /* No SERVER_SUPPORT */
    CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
#endif /* No SERVER_SUPPORT */
    if (CVSADM_Root != NULL)
    {
        if (CVSroot == NULL || !cvs_update_env)
        {
	    CVSroot = CVSADM_Root;
	    cvs_update_env = 1;	/* need to update environment */
        }
#ifdef CLIENT_SUPPORT
        else if (!getenv ("CVS_IGNORE_REMOTE_ROOT"))
#else
        else
#endif
        {
            /*
	     * Now for the hard part, compare the two directories. If they
	     * are not identical, then abort this command.
	     */
            if ((fncmp (CVSroot, CVSADM_Root) != 0) &&
		!same_directories(CVSroot, CVSADM_Root))
	    {
              error (0, 0, "%s value for CVS Root found in %s",
                     CVSADM_Root, CVSADM_ROOT);
              error (0, 0, "does not match command line -d %s setting",
                     CVSroot);
              error (1, 0,
                      "you may wish to try the cvs command again without the -d option ");
	    }
        }
    }
#endif /* CVSADM_ROOT */

    /*
     * Specifying just the '-H' flag to the sub-command causes a Usage
     * message to be displayed.
     */
    command_name = cp = argv[0];
    if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0))
	argc = -1;
    else
    {
	/*
	 * Check to see if we can write into the history file.  If not,
	 * we assume that we can't work in the repository.
	 * BUT, only if the history file exists.
	 */
#ifdef SERVER_SUPPORT
        if (strcmp (command_name, "server") != 0 || CVSroot != NULL)
#endif
	{
	    char path[PATH_MAX];
	    int save_errno;

	    if (!CVSroot || !*CVSroot)
		error (1, 0, "You don't have a %s environment variable",
		       CVSROOT_ENV);
	    (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM);
	    if (access (path, R_OK | X_OK))
	    {
		save_errno = errno;
#ifdef CLIENT_SUPPORT
		if (strchr (CVSroot, ':') == NULL)
		{
#endif
		error (0, 0,
		    "Sorry, you don't have sufficient access to %s", CVSroot);
		error (1, save_errno, "%s", path);
#ifdef CLIENT_SUPPORT
		}
#endif
	    }
	    (void) strcat (path, "/");
	    (void) strcat (path, CVSROOTADM_HISTORY);
	    if (isfile (path) && access (path, R_OK | W_OK))
	    {
		save_errno = errno;
		error (0, 0,
		 "Sorry, you don't have read/write access to the history file");
		error (1, save_errno, "%s", path);
	    }
	}
    }

#ifdef SERVER_SUPPORT
    if (strcmp (command_name, "server") == 0)
	/* This is only used for writing into the history file.  Might
	   be nice to have hostname and/or remote path, on the other hand
	   I'm not sure whether it is worth the trouble.  */
	strcpy (CurDir, "<remote>");
    else if (!getwd (CurDir))
	error (1, 0, "cannot get working directory: %s", CurDir);
#endif

#ifdef HAVE_PUTENV
    /* Now, see if we should update the environment with the Rcsbin value */
    if (cvs_update_env)
    {
	char *env;

	env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
	(void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot);
	(void) putenv (env);
	/* do not free env, as putenv has control of it */
    }
    if (rcsbin_update_env)
    {
	char *env;

	env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1);
	(void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin);
	(void) putenv (env);
	/* do not free env, as putenv has control of it */
    }
#endif

    /*
     * If Rcsbin is set to something, make sure it is terminated with
     * a slash character.  If not, add one.
     */
    if (*Rcsbin)
    {
	int len = strlen (Rcsbin);
	char *rcsbin;

	if (Rcsbin[len - 1] != '/')
	{
	    rcsbin = Rcsbin;
	    Rcsbin = xmalloc (len + 2);	/* one for '/', one for NULL */
	    (void) strcpy (Rcsbin, rcsbin);
	    (void) strcat (Rcsbin, "/");
	}
    }

    for (cm = cmds; cm->fullname; cm++)
    {
	if (cm->nick1 && !strcmp (cp, cm->nick1))
	    break;
	if (cm->nick2 && !strcmp (cp, cm->nick2))
	    break;
	if (!strcmp (cp, cm->fullname))
	    break;
    }

    if (!cm->fullname)
	usage (usg);			/* no match */
    else
    {
	command_name = cm->fullname;	/* Global pointer for later use */

	/* make sure we clean up on error */
#ifdef SIGHUP
	(void) SIG_register (SIGHUP, main_cleanup);
	(void) SIG_register (SIGHUP, Lock_Cleanup);
#endif
#ifdef SIGINT
	(void) SIG_register (SIGINT, main_cleanup);
	(void) SIG_register (SIGINT, Lock_Cleanup);
#endif
#ifdef SIGQUIT
	(void) SIG_register (SIGQUIT, main_cleanup);
	(void) SIG_register (SIGQUIT, Lock_Cleanup);
#endif
#ifdef SIGPIPE
	(void) SIG_register (SIGPIPE, main_cleanup);
	(void) SIG_register (SIGPIPE, Lock_Cleanup);
#endif
#ifdef SIGTERM
	(void) SIG_register (SIGTERM, main_cleanup);
	(void) SIG_register (SIGTERM, Lock_Cleanup);
#endif

	gethostname(hostname, sizeof (hostname));

#ifdef HAVE_SETVBUF
	/*
	 * Make stdout line buffered, so 'tail -f' can monitor progress.
	 * Patch creates too much output to monitor and it runs slowly.
	 */
	if (strcmp (cm->fullname, "patch"))
	    (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0);
#endif

	if (use_cvsrc)
	  read_cvsrc(&argc, &argv);

#ifdef CLIENT_SUPPORT
	/* If cvsroot contains a colon, try to do it via the protocol.  */
        {
	    char *p = CVSroot == NULL ? NULL : strchr (CVSroot, ':');
	    if (p)
		err = (*(cm->client_func)) (argc, argv);
	    else
		err = (*(cm->func)) (argc, argv);
	}
#else /* No CLIENT_SUPPORT */
	err = (*(cm->func)) (argc, argv);

#endif /* No CLIENT_SUPPORT */
    }
    /*
     * If the command's error count is modulo 256, we need to change it
     * so that we don't overflow the 8-bits we get to report exit status
     */
    if (err && (err % 256) == 0)
	err = 1;
    Lock_Cleanup ();
    return (err);
}

char *
Make_Date (rawdate)
    char *rawdate;
{
    struct tm *ftm;
    time_t unixtime;
    char date[256];			/* XXX bigger than we'll ever need? */
    char *ret;

    unixtime = get_date (rawdate, (struct timeb *) NULL);
    if (unixtime == (time_t) - 1)
	error (1, 0, "Can't parse date/time: %s", rawdate);
#ifdef HAVE_RCS5
    ftm = gmtime (&unixtime);
#else
    ftm = localtime (&unixtime);
#endif
    (void) sprintf (date, DATEFORM,
		    ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
		    ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
		    ftm->tm_min, ftm->tm_sec);
    ret = xstrdup (date);
    return (ret);
}

void
usage (cpp)
    register const char *const *cpp;
{
    (void) fprintf (stderr, *cpp++, program_name, command_name);
    for (; *cpp; cpp++)
	(void) fprintf (stderr, *cpp);
    exit (1);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.