/* * 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 * login Record user, host, repos, password * 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" #ifdef HAVE_WINSOCK_H #include #else extern int gethostname (); #endif #if HAVE_KERBEROS #include #include #include #ifndef HAVE_KRB_GET_ERR_TEXT #define krb_get_err_text(status) krb_err_txt[status] #endif #endif char *program_name; char *program_path; /* * Initialize comamnd_name to "cvs" so that the first call to * read_cvsrc tries to find global cvs options. */ char *command_name = ""; /* * Since some systems don't define this... */ #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 256 #endif char hostname[MAXHOSTNAMELEN]; #ifdef AUTH_CLIENT_SUPPORT int use_authenticating_server = FALSE; #endif /* AUTH_CLIENT_SUPPORT */ 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; mode_t cvsumask = UMASK_DFLT; char *CurDir; /* * Defaults, for the environment variables that are not set */ char *Rcsbin = RCSBIN_DFLT; char *Editor = EDITOR_DFLT; char *CVSroot = CVSROOT_DFLT; /* * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root' */ char *CVSADM_Root = CVSROOT_DFLT; 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)); #ifdef AUTH_CLIENT_SUPPORT int login PROTO((int argc, char **argv)); #endif /* AUTH_CLIENT_SUPPORT */ 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), CMD_ENTRY("admin", "adm", "rcs", admin, client_admin), CMD_ENTRY("annotate", NULL, NULL, annotate, client_annotate), 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("edit", "edit", "edit", edit, client_edit), CMD_ENTRY("editors", "editors","editors",editors, client_editors), 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("init", NULL, NULL, init, client_init), CMD_ENTRY("log", "lo", "rlog", cvslog, client_log), #ifdef AUTH_CLIENT_SUPPORT CMD_ENTRY("login", "logon", "lgn", login, login), #endif /* AUTH_CLIENT_SUPPORT */ 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("unedit", "unedit","unedit", unedit, client_unedit), CMD_ENTRY("update", "up", "upd", update, client_update), CMD_ENTRY("watch", "watch", "watch", watch, client_watch), CMD_ENTRY("watchers", "watchers","watchers",watchers,client_watchers), #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 " -s VAR=VAL Set CVS user variable.\n", "\n", " and where 'command' is: add, admin, etc. (use the --help-commands\n", " option for a list of commands)\n", NULL, }; static const char *const cmd_usage[] = { "CVS commands are:\n", " add Adds a new file/directory to the repository\n", " admin Administration front end for rcs\n", " annotate Show revision where each line was modified\n", " checkout Checkout sources for editing\n", " commit Checks files into the repository\n", " diff Runs diffs between revisions\n", " edit Get ready to edit a watched file\n", " editors See who is editing a watched file\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", " init Initialize a new CVS repository\n", " log Prints out 'rlog' information for files\n", #ifdef AUTH_CLIENT_SUPPORT " login Prompt for password for authenticating server.\n", #endif /* AUTH_CLIENT_SUPPORT */ " 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", " unedit Undo an edit command\n", " rtag Add a symbolic tag to the RCS file\n", " update Brings work tree in sync with repository\n", " watch Set watches\n", " watchers See who is watching a file\n", NULL, }; static RETSIGTYPE main_cleanup () { exit (EXIT_FAILURE); } static void error_cleanup PROTO((void)) { 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, *end; const struct cmd *cm; int c, err = 0; static int help = FALSE; static int version_flag = FALSE; static int help_commands = FALSE; int rcsbin_update_env, cvs_update_env = 0; static struct option long_options[] = { {"help", 0, &help, TRUE}, {"version", 0, &version_flag, TRUE}, {"help-commands", 0, &help_commands, TRUE}, {0, 0, 0, 0} }; /* `getopt_long' stores the option index here, but right now we don't use it. */ int option_index = 0; error_set_cleanup (error_cleanup); /* The socket subsystems on NT and OS2 must be initialized before use */ #ifdef INITIALIZE_SOCKET_SUBSYSTEM INITIALIZE_SOCKET_SUBSYSTEM(); #endif /* INITIALIZE_SOCKET_SUBSYSTEM */ /* * Just save the last component of the path for error messages */ program_path = xstrdup (argv[0]); 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; if ((cp = getenv (CVSUMASK_ENV)) != NULL) { /* FIXME: Should be accepting symbolic as well as numeric mask. */ cvsumask = strtol (cp, &end, 8) & 0777; if (*end != '\0') error (1, errno, "invalid umask value in %s (%s)", CVSUMASK_ENV, cp); } /* This has the effect of setting getopt's ordering to REQUIRE_ORDER, which is what we need to distinguish between global options and command options. FIXME: It would appear to be possible to do this much less kludgily by passing "+" as the first character to the option string we pass to getopt_long. */ optind = 1; /* We have to parse the options twice because else there is no chance to avoid reading the global options from ".cvsrc". Set opterr to 0 for avoiding error messages about invalid options. */ opterr = 0; while ((c = getopt_long (argc, argv, "f", NULL, NULL)) != EOF) { if (c == 'f') use_cvsrc = FALSE; } /* * Scan cvsrc file for global options. */ if (use_cvsrc) read_cvsrc (&argc, &argv, "cvs"); optind = 1; opterr = 1; while ((c = getopt_long (argc, argv, "Qqrwtnlvb:e:d:Hfz:s:", long_options, &option_index)) != EOF) { switch (c) { case 0: /* getopt_long took care of setting the flag. */ break; 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': version_flag = TRUE; 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; case 'z': #ifdef CLIENT_SUPPORT gzip_level = atoi (optarg); if (gzip_level <= 0 || gzip_level > 9) error (1, 0, "gzip compression level must be between 1 and 9"); #endif /* If no CLIENT_SUPPORT, we just silently ignore the gzip level, so that users can have it in their .cvsrc and not cause any trouble. */ break; case 's': variable_set (optarg); break; case '?': default: usage (usg); } } if (version_flag == TRUE) { (void) fputs (version_string, stdout); (void) fputs (config_string, 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); } else if (help_commands) usage (cmd_usage); 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 (EXIT_FAILURE); } 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 (EXIT_FAILURE); } /* 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 (EXIT_FAILURE); } pw = getpwnam (user); if (pw == NULL) { printf ("E Fatal error, aborting.\n\ error 0 %s: no such user\n", user); exit (EXIT_FAILURE); } 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 */ #if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT) if (strcmp (argv[0], "pserver") == 0) { /* Gets username and password from client, authenticates, then switches to run as that user and sends an ACK back to the client. */ authenticate_connection (); /* Pretend we were invoked as a plain server. */ argv[0] = "server"; } #endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */ /* * 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 /* ! CLIENT_SUPPORT */ else #endif /* CLIENT_SUPPORT */ { /* * 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 "); } } } /* CVSroot may need fixing up, if an access-method was specified, * but not a user. Later code assumes that if CVSroot contains an * access-method, then it also has a user. We print a warning and * die if we can't guarantee that. */ if (CVSroot && *CVSroot && (CVSroot[0] == ':') && (strchr (CVSroot, '@') == NULL)) { error (1, 0, "must also give a username if specifying access method"); } /* * 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 (!isaccessible (path, R_OK | X_OK)) { save_errno = errno; /* If this is "cvs init", the root need not exist yet. */ if (strcmp (command_name, "init") != 0 #ifdef CLIENT_SUPPORT /* If we are a remote client, the root need not exist on the client machine (FIXME: we should also skip the check for CVSROOTADM_HISTORY being writable; it shouldn't matter if there is a read-only file which happens to have the same name on the client machine). */ && strchr (CVSroot, ':') == NULL) #endif { error (0, 0, "Sorry, you don't have sufficient access to %s", CVSroot); error (1, save_errno, "%s", path); } } (void) strcat (path, "/"); (void) strcat (path, CVSROOTADM_HISTORY); if (isfile (path) && !isaccessible (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, ""); 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, command_name); #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 */ } Lock_Cleanup (); if (err) return (EXIT_FAILURE); return 0; } 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 (EXIT_FAILURE); }