diff options
Diffstat (limited to 'contrib/cvs/src/main.c')
-rw-r--r-- | contrib/cvs/src/main.c | 1240 |
1 files changed, 1240 insertions, 0 deletions
diff --git a/contrib/cvs/src/main.c b/contrib/cvs/src/main.c new file mode 100644 index 0000000..332946a --- /dev/null +++ b/contrib/cvs/src/main.c @@ -0,0 +1,1240 @@ +/* + * Copyright (C) 1986-2008 The Free Software Foundation, Inc. + * + * Portions Copyright (C) 1998-2006 Derek Price, Ximbiot <http://ximbiot.com>, + * and others. + * + * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk + * Portions 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 source distribution. + * + * 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. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include "cvs.h" +#include "prepend_args.h" + +#ifdef HAVE_WINSOCK_H +#include <winsock.h> +#else +extern int gethostname (); +#endif + +const char *program_name; +const char *program_path; +const char *cvs_cmd_name; + +/* I'd dynamically allocate this, but it seems like gethostname + requires a fixed size array. If I'm remembering the RFCs right, + 256 should be enough. */ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +char hostname[MAXHOSTNAMELEN]; + +int use_editor = 1; +int use_cvsrc = 1; +int cvswrite = !CVSREAD_DFLT; +int really_quiet = 0; +int quiet = 0; +int trace = 0; +int noexec = 0; +int readonlyfs = 0; +int require_real_user = 0; +int logoff = 0; + +/* + * Zero if compression isn't supported or requested; non-zero to indicate + * a compression level to request from gzip. + */ +int gzip_level; + +/* Set if we should be writing CVSADM directories at top level. At + least for now we'll make the default be off (the CVS 1.9, not CVS + 1.9.2, behavior). */ +int top_level_admin = 0; + +mode_t cvsumask = UMASK_DFLT; + +char *CurDir; + +/* + * Defaults, for the environment variables that are not set + */ +char *Tmpdir = TMPDIR_DFLT; +char *Editor = EDITOR_DFLT; + + +/* When our working directory contains subdirectories with different + values in CVS/Root files, we maintain a list of them. */ +List *root_directories = NULL; + +static const struct cmd +{ + char *fullname; /* Full name of the function (e.g. "commit") */ + + /* Synonyms for the command, nick1 and nick2. We supply them + mostly for two reasons: (1) CVS has always supported them, and + we need to maintain compatibility, (2) if there is a need for a + version which is shorter than the fullname, for ease in typing. + Synonyms have the disadvantage that people will see "new" and + then have to think about it, or look it up, to realize that is + the operation they know as "add". Also, this means that one + cannot create a command "cvs new" with a different meaning. So + new synonyms are probably best used sparingly, and where used + should be abbreviations of the fullname (preferably consisting + of the first 2 or 3 or so letters). + + One thing that some systems do is to recognize any unique + abbreviation, for example "annotat" "annota", etc., for + "annotate". The problem with this is that scripts and user + habits will expect a certain abbreviation to be unique, and in + a future release of CVS it may not be. So it is better to + accept only an explicit list of abbreviations and plan on + supporting them in the future as well as now. */ + + char *nick1; + char *nick2; + + int (*func) (); /* Function takes (argc, argv) arguments. */ + unsigned long attr; /* Attributes. */ +} cmds[] = + +{ + { "add", "ad", "new", add, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "admin", "adm", "rcs", admin, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "annotate", "ann", "blame", annotate, CVS_CMD_USES_WORK_DIR }, + { "checkout", "co", "get", checkout, 0 }, + { "commit", "ci", "com", commit, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "diff", "di", "dif", diff, CVS_CMD_USES_WORK_DIR }, + { "edit", NULL, NULL, edit, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "editors", NULL, NULL, editors, CVS_CMD_USES_WORK_DIR }, + { "export", "exp", "ex", checkout, CVS_CMD_USES_WORK_DIR }, + { "history", "hi", "his", history, CVS_CMD_USES_WORK_DIR }, + { "import", "im", "imp", import, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR | CVS_CMD_IGNORE_ADMROOT}, + { "init", NULL, NULL, init, CVS_CMD_MODIFIES_REPOSITORY }, +#if defined (HAVE_KERBEROS) && defined (SERVER_SUPPORT) + { "kserver", NULL, NULL, server, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, /* placeholder */ +#endif + { "log", "lo", NULL, cvslog, CVS_CMD_USES_WORK_DIR }, +#ifdef AUTH_CLIENT_SUPPORT + { "login", "logon", "lgn", login, 0 }, + { "logout", NULL, NULL, logout, 0 }, +#endif /* AUTH_CLIENT_SUPPORT */ +#if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT) + { "pserver", NULL, NULL, server, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, /* placeholder */ +#endif + { "rannotate","rann", "ra", annotate, 0 }, + { "rdiff", "patch", "pa", patch, 0 }, + { "release", "re", "rel", release, 0 }, + { "remove", "rm", "delete", cvsremove, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "rlog", "rl", NULL, cvslog, 0 }, + { "rtag", "rt", "rfreeze", cvstag, CVS_CMD_MODIFIES_REPOSITORY }, +#ifdef SERVER_SUPPORT + { "server", NULL, NULL, server, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, +#endif + { "status", "st", "stat", cvsstatus, CVS_CMD_USES_WORK_DIR }, + { "tag", "ta", "freeze", cvstag, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "unedit", NULL, NULL, unedit, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "update", "up", "upd", update, CVS_CMD_USES_WORK_DIR }, + { "version", "ve", "ver", version, 0 }, + { "watch", NULL, NULL, watch, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, + { "watchers", NULL, NULL, watchers, CVS_CMD_USES_WORK_DIR }, + { NULL, NULL, NULL, NULL, 0 }, +}; + +static const char *const usg[] = +{ + /* CVS usage messages never have followed the GNU convention of + putting metavariables in uppercase. I don't know whether that + is a good convention or not, but if it changes it would have to + change in all the usage messages. For now, they consistently + use lowercase, as far as I know. Punctuation is pretty funky, + though. Sometimes they use none, as here. Sometimes they use + single quotes (not the TeX-ish `' stuff), as in --help-options. + Sometimes they use double quotes, as in cvs -H add. + + Most (not all) of the usage messages seem to have periods at + the end of each line. I haven't tried to duplicate this style + in --help as it is a rather different format from the rest. */ + + "Usage: %s [cvs-options] command [command-options-and-arguments]\n", + " where cvs-options are -q, -n, etc.\n", + " (specify --help-options for a list of options)\n", + " where command is add, admin, etc.\n", + " (specify --help-commands for a list of commands\n", + " or --help-synonyms for a list of command synonyms)\n", + " where command-options-and-arguments depend on the specific command\n", + " (specify -H followed by a command name for command-specific help)\n", + " Specify --help to receive this message\n", + "\n", + + /* Some people think that a bug-reporting address should go here. IMHO, + the web sites are better because anything else is very likely to go + obsolete in the years between a release and when someone might be + reading this help. Besides, we could never adequately discuss + bug reporting in a concise enough way to put in a help message. */ + + /* I was going to put this at the top, but usage() wants the %s to + be in the first line. */ + "The Concurrent Versions System (CVS) is a tool for version control.\n", + /* I really don't think I want to try to define "version control" + in one line. I'm not sure one can get more concise than the + paragraph in ../cvs.spec without assuming the reader knows what + version control means. */ + + "For CVS updates and additional information, see\n", + " the CVS home page at http://cvs.nongnu.org/\n", + NULL, +}; + +static const char *const cmd_usage[] = +{ + "CVS commands are:\n", + " add Add a new file/directory to the repository\n", + " admin Administration front end for rcs\n", + " annotate Show last revision where each line was modified\n", + " checkout Checkout sources for editing\n", + " commit Check files into the repository\n", + " diff Show differences between revisions\n", + " edit Get ready to edit a watched file\n", + " editors See who is editing a watched file\n", + " export Export sources from CVS, similar to checkout\n", + " history Show repository access history\n", + " import Import sources into CVS, using vendor branches\n", + " init Create a CVS repository if it doesn't exist\n", +#if defined (HAVE_KERBEROS) && defined (SERVER_SUPPORT) + " kserver Kerberos server mode\n", +#endif + " log Print out history information for files\n", +#ifdef AUTH_CLIENT_SUPPORT + " login Prompt for password for authenticating server\n", + " logout Removes entry in .cvspass for remote repository\n", +#endif /* AUTH_CLIENT_SUPPORT */ +#if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT) + " pserver Password server mode\n", +#endif + " rannotate Show last revision where each line of module was modified\n", + " rdiff Create 'patch' format diffs between releases\n", + " release Indicate that a Module is no longer in use\n", + " remove Remove an entry from the repository\n", + " rlog Print out history information for a module\n", + " rtag Add a symbolic tag to a module\n", +#ifdef SERVER_SUPPORT + " server Server mode\n", +#endif + " status Display status information on checked out files\n", + " tag Add a symbolic tag to checked out version of files\n", + " unedit Undo an edit command\n", + " update Bring work tree in sync with repository\n", + " version Show current CVS version(s)\n", + " watch Set watches\n", + " watchers See who is watching a file\n", + "(Specify the --help option for a list of other help options)\n", + NULL, +}; + +static const char *const opt_usage[] = +{ + /* Omit -b because it is just for compatibility. */ + "CVS global options (specified before the command name) 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", + " -g Force group-write perms on checked-out files.\n", + " -n Do not execute anything that will change the disk.\n", + " -t Show trace of program execution -- try with -n.\n", + " -R Assume repository is read-only, such as CDROM\n", + " -v CVS version and copyright.\n", + " -T tmpdir Use 'tmpdir' for temporary files.\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 compression level '#' for net traffic.\n", +#ifdef ENCRYPTION + " -x Encrypt all net traffic.\n", +#endif + " -a Authenticate all net traffic.\n", +#endif + " -s VAR=VAL Set CVS user variable.\n", + "(Specify the --help option for a list of other help options)\n", + NULL +}; + + +static int +set_root_directory (p, ignored) + Node *p; + void *ignored; +{ + if (current_parsed_root == NULL && p->data != NULL) + { + current_parsed_root = p->data; + return 1; + } + return 0; +} + + +static const char * const* +cmd_synonyms () +{ + char ** synonyms; + char ** line; + const struct cmd *c = &cmds[0]; + /* Three more for title, "specify --help" line, and NULL. */ + int numcmds = 3; + + while (c->fullname != NULL) + { + numcmds++; + c++; + } + + synonyms = (char **) xmalloc(numcmds * sizeof(char *)); + line = synonyms; + *line++ = "CVS command synonyms are:\n"; + for (c = &cmds[0]; c->fullname != NULL; c++) + { + if (c->nick1 || c->nick2) + { + *line = xmalloc (strlen (c->fullname) + + (c->nick1 != NULL ? strlen (c->nick1) : 0) + + (c->nick2 != NULL ? strlen (c->nick2) : 0) + + 40); + sprintf(*line, " %-12s %s %s\n", c->fullname, + c->nick1 ? c->nick1 : "", + c->nick2 ? c->nick2 : ""); + line++; + } + } + *line++ = "(Specify the --help option for a list of other help options)\n"; + *line = NULL; + + return (const char * const*) synonyms; /* will never be freed */ +} + + +unsigned long int +lookup_command_attribute (cmd_name) + char *cmd_name; +{ + const struct cmd *cm; + + for (cm = cmds; cm->fullname; cm++) + { + if (strcmp (cmd_name, cm->fullname) == 0) + break; + } + if (!cm->fullname) + error (1, 0, "unknown command: %s", cmd_name); + return cm->attr; +} + + +static RETSIGTYPE +main_cleanup (sig) + int sig; +{ +#ifndef DONT_USE_SIGNALS + const char *name; + char temp[10]; + + switch (sig) + { +#ifdef SIGABRT + case SIGABRT: + name = "abort"; + break; +#endif +#ifdef SIGHUP + case SIGHUP: + name = "hangup"; + break; +#endif +#ifdef SIGINT + case SIGINT: + name = "interrupt"; + break; +#endif +#ifdef SIGQUIT + case SIGQUIT: + name = "quit"; + break; +#endif +#ifdef SIGPIPE + case SIGPIPE: + name = "broken pipe"; + break; +#endif +#ifdef SIGTERM + case SIGTERM: + name = "termination"; + break; +#endif + default: + /* This case should never be reached, because we list above all + the signals for which we actually establish a signal handler. */ + sprintf (temp, "%d", sig); + name = temp; + break; + } + + error (1, 0, "received %s signal", name); +#endif /* !DONT_USE_SIGNALS */ +} + +int +main (argc, argv) + int argc; + char **argv; +{ + cvsroot_t *CVSroot_parsed = NULL; + int cvsroot_update_env = 1; + char *cp, *end; + const struct cmd *cm; + int c, err = 0; + int tmpdir_update_env; + int free_Editor = 0; + int free_Tmpdir = 0; + + int help = 0; /* Has the user asked for help? This + lets us support the `cvs -H cmd' + convention to give help for cmd. */ + static const char short_options[] = "+QqgrwtnRvb:T:e:d:Hfz:s:xaU"; + static struct option long_options[] = + { + {"help", 0, NULL, 'H'}, + {"version", 0, NULL, 'v'}, + {"help-commands", 0, NULL, 1}, + {"help-synonyms", 0, NULL, 2}, + {"help-options", 0, NULL, 4}, + {"allow-root", required_argument, NULL, 3}, + {0, 0, 0, 0} + }; + /* `getopt_long' stores the option index here, but right now we + don't use it. */ + int option_index = 0; + +#ifdef SYSTEM_INITIALIZE + /* Hook for OS-specific behavior, for example socket subsystems on + NT and OS2 or dealing with windows and arguments on Mac. */ + SYSTEM_INITIALIZE (&argc, &argv); +#endif + +#ifdef HAVE_TZSET + /* On systems that have tzset (which is almost all the ones I know + of), it's a good idea to call it. */ + tzset (); +#endif + + /* + * Just save the last component of the path for error messages + */ + program_path = xstrdup (argv[0]); +#ifdef ARGV0_NOT_PROGRAM_NAME + /* On some systems, e.g. VMS, argv[0] is not the name of the command + which the user types to invoke the program. */ + program_name = "cvs"; +#else + program_name = last_component (argv[0]); +#endif + + /* + * Query the environment variables up-front, so that + * they can be overridden by command line arguments + */ + tmpdir_update_env = *Tmpdir; /* TMPDIR_DFLT must be set */ + if ((cp = getenv (TMPDIR_ENV)) != NULL) + { + Tmpdir = cp; + tmpdir_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 (getenv (CVSREAD_ENV) != NULL) + cvswrite = 0; + if (getenv (CVSREADONLYFS_ENV) != NULL) { + readonlyfs = 1; + logoff = 1; + } + + prepend_default_options (getenv ("CVS_OPTIONS"), &argc, &argv); + + /* Set this to 0 to force getopt initialization. getopt() sets + this to 1 internally. */ + optind = 0; + + /* 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, short_options, long_options, &option_index)) + != EOF) + { + if (c == 'f') + use_cvsrc = 0; + } + + /* + * Scan cvsrc file for global options. + */ + if (use_cvsrc) + read_cvsrc (&argc, &argv, "cvs"); + + optind = 0; + opterr = 1; + + while ((c = getopt_long + (argc, argv, short_options, long_options, &option_index)) + != EOF) + { + switch (c) + { + case 1: + /* --help-commands */ + usage (cmd_usage); + break; + case 2: + /* --help-synonyms */ + usage (cmd_synonyms()); + break; + case 4: + /* --help-options */ + usage (opt_usage); + break; + case 3: + /* --allow-root */ + root_allow_add (optarg); + break; + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'r': + cvswrite = 0; + break; + case 'w': + cvswrite = 1; + break; + case 'g': + /* + * force full group write perms (used for shared checked-out + * source trees, see manual page) + */ + umask(umask(077) & 007); + break; + case 't': + trace = 1; + break; + case 'R': + readonlyfs = 1; + logoff = 1; + break; + case 'n': + noexec = 1; + logoff = 1; + break; + case 'v': + (void) fputs ("\n", stdout); + version (0, (char **) NULL); + (void) fputs ("\n", stdout); + (void) fputs ("\ +Copyright (C) 2006 Free Software Foundation, Inc.\n\ +\n\ +Senior active maintainers include Larry Jones, Derek R. Price,\n\ +and Mark D. Baushke. Please see the AUTHORS and README files from the CVS\n\ +distribution kit for a complete list of contributors and copyrights.\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); + (void) fputs ("\n", stdout); + + (void) fputs ("Specify the --help option for further information about CVS\n", stdout); + +#ifdef SYSTEM_CLEANUP + /* Hook for OS-specific behavior, for example socket subsystems + * on NT and OS2 or dealing with windows and arguments on Mac. + */ + SYSTEM_CLEANUP (); +#endif + exit (0); + break; + case 'b': + /* This option used to specify the directory for RCS + executables. But since we don't run them any more, + this is a noop. Silently ignore it so that .cvsrc + and scripts and inetd.conf and such can work with + either new or old CVS. */ + break; + case 'T': + if (free_Tmpdir) free (Tmpdir); + Tmpdir = xstrdup (optarg); + free_Tmpdir = 1; + tmpdir_update_env = 1; /* need to update environment */ + break; + case 'e': + if (free_Editor) free (Editor); + Editor = xstrdup (optarg); + free_Editor = 1; + break; + case 'd': + if (CVSroot_cmdline != NULL) + free (CVSroot_cmdline); + CVSroot_cmdline = xstrdup (optarg); + break; + case 'H': + help = 1; + break; + case 'f': + use_cvsrc = 0; /* unnecessary, since we've done it above */ + break; + case 'z': + gzip_level = strtol (optarg, &end, 10); + if (*end != '\0' || gzip_level < 0 || gzip_level > 9) + error (1, 0, + "gzip compression level must be between 0 and 9"); + /* 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. + * + * We still parse the argument to -z for correctness since + * one user complained of being bitten by a run of + * `cvs -z -n up' which read -n as the argument to -z without + * complaining. */ + break; + case 's': + variable_set (optarg); + break; + case 'x': +#ifdef CLIENT_SUPPORT + cvsencrypt = 1; +#endif /* CLIENT_SUPPORT */ + /* If no CLIENT_SUPPORT, ignore -x, so that users can + have it in their .cvsrc and not cause any trouble. + If no ENCRYPTION, we still accept -x, but issue an + error if we are being run as a client. */ + break; + case 'a': +#ifdef CLIENT_SUPPORT + cvsauthenticate = 1; +#endif + /* If no CLIENT_SUPPORT, ignore -a, so that users can + have it in their .cvsrc and not cause any trouble. + We will issue an error later if stream + authentication is not supported. */ + break; + case 'U': +#ifdef SERVER_SUPPORT + require_real_user = 1; +#endif + break; + case '?': + default: + usage (usg); + } + } + + argc -= optind; + argv += optind; + if (argc < 1) + usage (usg); + + + /* Look up the command name. */ + + cvs_cmd_name = argv[0]; + for (cm = cmds; cm->fullname; cm++) + { + if (cm->nick1 && !strcmp (cvs_cmd_name, cm->nick1)) + break; + if (cm->nick2 && !strcmp (cvs_cmd_name, cm->nick2)) + break; + if (!strcmp (cvs_cmd_name, cm->fullname)) + break; + } + + if (!cm->fullname) + { + fprintf (stderr, "Unknown command: `%s'\n\n", cvs_cmd_name); + usage (cmd_usage); + } + else + cvs_cmd_name = cm->fullname; /* Global pointer for later use */ + + if (help) + { + argc = -1; /* some functions only check for this */ + err = (*(cm->func)) (argc, argv); + } + else + { + /* The user didn't ask for help, so go ahead and authenticate, + set up CVSROOT, and the rest of it. */ + + /* The UMASK environment variable isn't handled with the + others above, since we don't want to signal errors if the + user has asked for help. This won't work if somebody adds + a command-line flag to set the umask, since we'll have to + parse it before we get here. */ + + 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); + } + +#ifdef SERVER_SUPPORT + +# 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 (cvs_cmd_name, "kserver") == 0) + { + kserver_authenticate_connection (); + + /* Pretend we were invoked as a plain server. */ + cvs_cmd_name = "server"; + } +# endif /* HAVE_KERBEROS */ + + +# if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI) + if (strcmp (cvs_cmd_name, "pserver") == 0) + { + /* The reason that --allow-root is not a command option + is mainly the comment in server() about how argc,argv + might be from .cvsrc. I'm not sure about that, and + I'm not sure it is only true of command options, but + it seems easier to make it a global option. */ + + /* Gets username and password from client, authenticates, then + switches to run as that user and sends an ACK back to the + client. */ + pserver_authenticate_connection (); + + /* Pretend we were invoked as a plain server. */ + cvs_cmd_name = "server"; + } +# endif /* AUTH_SERVER_SUPPORT || HAVE_GSSAPI */ +#endif /* SERVER_SUPPORT */ + + server_active = strcmp (cvs_cmd_name, "server") == 0; + + /* This is only used for writing into the history file. For + remote connections, it might be nice to have hostname + and/or remote path, on the other hand I'm not sure whether + it is worth the trouble. */ + + if (server_active) + CurDir = xstrdup ("<remote>"); + else + { + CurDir = xgetwd (); + if (CurDir == NULL) + error (1, errno, "cannot get working directory"); + } + + if (Tmpdir == NULL || Tmpdir[0] == '\0') + { + if (free_Tmpdir) free (Tmpdir); + Tmpdir = "/tmp"; + } + +#ifdef HAVE_PUTENV + if (tmpdir_update_env) + { + char *env; + env = xmalloc (strlen (TMPDIR_ENV) + strlen (Tmpdir) + 1 + 1); + (void) sprintf (env, "%s=%s", TMPDIR_ENV, Tmpdir); + (void) putenv (env); + /* do not free env, as putenv has control of it */ + } + { + char *env; + env = xmalloc (sizeof "CVS_PID=" + 32); /* XXX pid < 10^32 */ + (void) sprintf (env, "CVS_PID=%ld", (long) getpid ()); + (void) putenv (env); + } +#endif + +#ifndef DONT_USE_SIGNALS + /* make sure we clean up on error */ +#ifdef SIGABRT + (void) SIG_register (SIGABRT, main_cleanup); +#endif +#ifdef SIGHUP + (void) SIG_register (SIGHUP, main_cleanup); +#endif +#ifdef SIGINT + (void) SIG_register (SIGINT, main_cleanup); +#endif +#ifdef SIGQUIT + (void) SIG_register (SIGQUIT, main_cleanup); +#endif +#ifdef SIGPIPE + (void) SIG_register (SIGPIPE, main_cleanup); +#endif +#ifdef SIGTERM + (void) SIG_register (SIGTERM, main_cleanup); +#endif +#endif /* !DONT_USE_SIGNALS */ + + gethostname(hostname, sizeof (hostname)); + +#ifdef KLUDGE_FOR_WNT_TESTSUITE + /* Probably the need for this will go away at some point once + we call fflush enough places (e.g. fflush (stdout) in + cvs_outerr). */ + (void) setvbuf (stdout, (char *) NULL, _IONBF, 0); + (void) setvbuf (stderr, (char *) NULL, _IONBF, 0); +#endif /* KLUDGE_FOR_WNT_TESTSUITE */ + + if (use_cvsrc) + read_cvsrc (&argc, &argv, cvs_cmd_name); + + /* Fiddling with CVSROOT doesn't make sense if we're running + * in server mode, since the client will send the repository + * directory after the connection is made. + */ + if (!server_active) + { + /* First check if a root was set via the command line. */ + if (CVSroot_cmdline) + { + if (!(CVSroot_parsed = parse_cvsroot (CVSroot_cmdline))) + error (1, 0, "Bad CVSROOT: `%s'.", CVSroot_cmdline); + } + + /* See if we are able to find a 'better' value for CVSroot + * in the CVSADM_ROOT directory. + * + * "cvs import" shouldn't check CVS/Root; in general it + * ignores CVS directories and CVS/Root is likely to + * specify a different repository than the one we are + * importing to, but if this is not import and no root was + * specified on the command line, set the root from the + * CVS/Root file. + */ + if (!CVSroot_parsed + && !(cm->attr & CVS_CMD_IGNORE_ADMROOT) + ) + CVSroot_parsed = Name_Root (NULL, NULL); + + /* Now, if there is no root on the command line and we didn't find + * one in a file, set it via the $CVSROOT env var. + */ + if (!CVSroot_parsed) + { + char *tmp = getenv (CVSROOT_ENV); + if (tmp) + { + if (!(CVSroot_parsed = parse_cvsroot (tmp))) + error (1, 0, "Bad CVSROOT: `%s'.", tmp); + cvsroot_update_env = 0; + } + } + +#ifdef CVSROOT_DFLT + if (!CVSroot_parsed) + { + if (!(CVSroot_parsed = parse_cvsroot (CVSROOT_DFLT))) + error (1, 0, "Bad CVSROOT: `%s'.", CVSROOT_DFLT); + } +#endif /* CVSROOT_DFLT */ + + /* Now we've reconciled CVSROOT from the command line, the + CVS/Root file, and the environment variable. Do the + last sanity checks on the variable. */ + if (!CVSroot_parsed) + { + error (0, 0, + "No CVSROOT specified! Please use the `-d' option"); + error (1, 0, + "or set the %s environment variable.", CVSROOT_ENV); + } + } + + /* Here begins the big loop over unique cvsroot values. We + need to call do_recursion once for each unique value found + in CVS/Root. Prime the list with the current value. */ + + /* Create the list. */ + assert (root_directories == NULL); + root_directories = getlist (); + + /* Prime it. */ + if (CVSroot_parsed) + { + Node *n; + n = getnode (); + n->type = NT_UNKNOWN; + n->key = xstrdup (CVSroot_parsed->original); + n->data = CVSroot_parsed; + + if (addnode (root_directories, n)) + error (1, 0, "cannot add initial CVSROOT %s", n->key); + } + + assert (current_parsed_root == NULL); + + /* If we're running the server, we want to execute this main + loop once and only once (we won't be serving multiple roots + from this connection, so there's no need to do it more than + once). To get out of the loop, we perform a "break" at the + end of things. */ + + while (server_active || + walklist (root_directories, set_root_directory, NULL)) + { + /* Fiddling with CVSROOT doesn't make sense if we're running + in server mode, since the client will send the repository + directory after the connection is made. */ + + if (!server_active) + { + /* Now we're 100% sure that we have a valid CVSROOT + variable. Parse it to see if we're supposed to do + remote accesses or use a special access method. */ + + if (trace) + fprintf (stderr, "%s-> main loop with CVSROOT=%s\n", + CLIENT_SERVER_STR, current_parsed_root->original); + + /* + * Check to see if the repository exists. + */ + if (!current_parsed_root->isremote) + { + char *path; + int save_errno; + + path = xmalloc (strlen (current_parsed_root->directory) + + strlen (CVSROOTADM) + 2); + sprintf (path, "%s/%s", current_parsed_root->directory, + CVSROOTADM); + if (!isaccessible (path, R_OK | X_OK)) + { + save_errno = errno; + /* If this is "cvs init", the root need not exist yet. + */ + if (strcmp (cvs_cmd_name, "init")) + error (1, save_errno, "%s", path); + } + free (path); + } + +#ifdef HAVE_PUTENV + /* Update the CVSROOT environment variable. */ + if (cvsroot_update_env) + { + static char *prev; + char *env; + + env = xmalloc (strlen (CVSROOT_ENV) + + strlen (current_parsed_root->original) + + 2); + sprintf (env, "%s=%s", CVSROOT_ENV, + current_parsed_root->original); + (void) putenv (env); + /* do not free env yet, as putenv has control of it */ + /* but do free the previous value, if any */ + if (prev != NULL) + free (prev); + prev = env; + } +#endif + } + + /* Parse the CVSROOT/config file, but only for local. For the + server, we parse it after we know $CVSROOT. For the + client, it doesn't get parsed at all, obviously. The + presence of the parse_config call here is not mean to + predetermine whether CVSROOT/config overrides things from + read_cvsrc and other such places or vice versa. That sort + of thing probably needs more thought. */ + if (!server_active && !current_parsed_root->isremote) + { + /* If there was an error parsing the config file, parse_config + already printed an error. We keep going. Why? Because + if we didn't, then there would be no way to check in a new + CVSROOT/config file to fix the broken one! */ + parse_config (current_parsed_root->directory); + + /* Now is a convenient time to read CVSROOT/options */ + parseopts(current_parsed_root->directory); + } + +#ifdef CLIENT_SUPPORT + /* Need to check for current_parsed_root != NULL here since + * we could still be in server mode before the server function + * gets called below and sets the root + */ + if (current_parsed_root != NULL && current_parsed_root->isremote) + { + /* Create a new list for directory names that we've + sent to the server. */ + if (dirs_sent_to_server != NULL) + dellist (&dirs_sent_to_server); + dirs_sent_to_server = getlist (); + } +#endif + + err = (*(cm->func)) (argc, argv); + + /* Mark this root directory as done. When the server is + active, our list will be empty -- don't try and + remove it from the list. */ + + if (!server_active) + { + Node *n = findnode (root_directories, + current_parsed_root->original); + assert (n != NULL); + assert (n->data != NULL); + free_cvsroot_t (n->data); + n->data = NULL; + current_parsed_root = NULL; + } + + if (server_active) + { + server_active = 0; + break; + } + } /* end of loop for cvsroot values */ + + dellist (&root_directories); + } /* end of stuff that gets done if the user DOESN'T ask for help */ + + Lock_Cleanup (); + + /* It's okay to cast out the const below since we know we allocated this in + * this function. The const was to keep other functions from messing with + * this. + */ + free ((char *)program_path); + if (CVSroot_cmdline != NULL) + free (CVSroot_cmdline); + if (free_Editor) + free (Editor); + if (free_Tmpdir) + free (Tmpdir); + root_allow_free (); + +#ifdef SYSTEM_CLEANUP + /* Hook for OS-specific behavior, for example socket subsystems on + NT and OS2 or dealing with windows and arguments on Mac. */ + SYSTEM_CLEANUP (); +#endif + + /* This is exit rather than return because apparently that keeps + some tools which check for memory leaks happier. */ + exit (err ? EXIT_FAILURE : 0); + /* Keep picky/stupid compilers (e.g. Visual C++ 5.0) happy. */ + return 0; +} + +char * +Make_Date (rawdate) + char *rawdate; +{ + time_t unixtime; + + unixtime = get_date (rawdate, (struct timeb *) NULL); + if (unixtime == (time_t) - 1) + error (1, 0, "Can't parse date/time: %s", rawdate); + return date_from_time_t (unixtime); +} + +/* Convert a time_t to an RCS format date. This is mainly for the + use of "cvs history", because the CVSROOT/history file contains + time_t format dates; most parts of CVS will want to avoid using + time_t's directly, and instead use RCS_datecmp, Make_Date, &c. + Assuming that the time_t is in GMT (as it generally should be), + then the result will be in GMT too. + + Returns a newly malloc'd string. */ + +char * +date_from_time_t (unixtime) + time_t unixtime; +{ + struct tm *ftm; + char date[MAXDATELEN]; + char *ret; + + ftm = gmtime (&unixtime); + if (ftm == NULL) + /* This is a system, like VMS, where the system clock is in local + time. Hopefully using localtime here matches the "zero timezone" + hack I added to get_date (get_date of course being the relevant + issue for Make_Date, and for history.c too I think). */ + ftm = localtime (&unixtime); + + (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); +} + +/* Convert a date to RFC822/1123 format. This is used in contexts like + dates to send in the protocol; it should not vary based on locale or + other such conventions for users. We should have another routine which + does that kind of thing. + + The SOURCE date is in our internal RCS format. DEST should point to + storage managed by the caller, at least MAXDATELEN characters. */ +void +date_to_internet (dest, source) + char *dest; + const char *source; +{ + struct tm date; + + date_to_tm (&date, source); + tm_to_internet (dest, &date); +} + +void +date_to_tm (dest, source) + struct tm *dest; + const char *source; +{ + if (sscanf (source, SDATEFORM, + &dest->tm_year, &dest->tm_mon, &dest->tm_mday, + &dest->tm_hour, &dest->tm_min, &dest->tm_sec) + != 6) + /* Is there a better way to handle errors here? I made this + non-fatal in case we are called from the code which can't + deal with fatal errors. */ + error (0, 0, "internal error: bad date %s", source); + + if (dest->tm_year > 100) + dest->tm_year -= 1900; + + dest->tm_mon -= 1; +} + +/* Convert a date to RFC822/1123 format. This is used in contexts like + dates to send in the protocol; it should not vary based on locale or + other such conventions for users. We should have another routine which + does that kind of thing. + + The SOURCE date is a pointer to a struct tm. DEST should point to + storage managed by the caller, at least MAXDATELEN characters. */ +void +tm_to_internet (dest, source) + char *dest; + const struct tm *source; +{ + /* Just to reiterate, these strings are from RFC822 and do not vary + according to locale. */ + static const char *const month_names[] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + sprintf (dest, "%d %s %d %02d:%02d:%02d -0000", source->tm_mday, + source->tm_mon < 0 || source->tm_mon > 11 ? "???" : month_names[source->tm_mon], + source->tm_year + 1900, source->tm_hour, source->tm_min, source->tm_sec); +} + +void +usage (cpp) + register const char *const *cpp; +{ + (void) fprintf (stderr, *cpp++, program_name, cvs_cmd_name); + for (; *cpp; cpp++) + (void) fprintf (stderr, *cpp); + error_exit (); +} + +void +parseopts(root) + const char *root; +{ + char path[PATH_MAX]; + int save_errno; + char buf[1024]; + const char *p; + char *q; + FILE *fp; + + if (root == NULL) { + printf("no CVSROOT in parseopts\n"); + return; + } + p = strchr (root, ':'); + if (p) + p++; + else + p = root; + if (p == NULL) { + printf("mangled CVSROOT in parseopts\n"); + return; + } + (void) sprintf (path, "%s/%s/%s", p, CVSROOTADM, CVSROOTADM_OPTIONS); + if ((fp = fopen(path, "r")) != NULL) { + while (fgets(buf, sizeof buf, fp) != NULL) { + if (buf[0] == '#') + continue; + q = strrchr(buf, '\n'); + if (q) + *q = '\0'; + + if (!strcmp(buf, "iso8601")) { + datesep = '-'; + } + if (!strncmp(buf, "tag=", 4)) { + char *what; + char *rcs_localid; + + rcs_localid = buf + 4; + RCS_setlocalid(rcs_localid); + } + if (!strncmp(buf, "tagexpand=", 10)) { + char *what; + char *rcs_incexc; + + rcs_incexc = buf + 10; + RCS_setincexc(rcs_incexc); + } + /* + * OpenBSD has a "umask=" and "dlimit=" command, we silently + * ignore them here since they are not much use to us. cvsumask + * defaults to 002 already, and the dlimit (data size limit) + * should really be handled elsewhere (eg: login.conf). + */ + } + fclose(fp); + } +} |