[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Cvs-cvs] Changes to ccvs/src/main.c [signed-commits]
From: |
Derek Robert Price |
Subject: |
[Cvs-cvs] Changes to ccvs/src/main.c [signed-commits] |
Date: |
Tue, 11 Oct 2005 22:46:49 -0400 |
Index: ccvs/src/main.c
diff -u /dev/null ccvs/src/main.c:1.262.2.1
--- /dev/null Wed Oct 12 02:46:49 2005
+++ ccvs/src/main.c Wed Oct 12 02:46:37 2005
@@ -0,0 +1,1478 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 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.
+ *
+ */
+
+#include "cvs.h"
+
+#include "closeout.h"
+#include "setenv.h"
+#include "strftime.h"
+#include "xgethostname.h"
+
+#include "sign.h"
+
+
+
+const char *program_name;
+const char *program_path;
+const char *cvs_cmd_name;
+
+const char *global_session_id; /* Random session ID */
+
+char *hostname;
+/* FIXME: Perhaps this should be renamed original_hostname or the like? */
+char *server_hostname;
+
+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 logoff = 0;
+
+
+
+/***
+ ***
+ *** CVSROOT/config options
+ ***
+ ***/
+struct config *config;
+
+
+
+mode_t cvsumask = UMASK_DFLT;
+
+char *CurDir;
+
+/*
+ * Defaults, for the environment variables that are not set
+ */
+char *Editor = EDITOR_DFLT;
+
+
+
+/* Temp dir stuff. */
+
+/* Temp dir, if set by the user. */
+static char *tmpdir_cmdline;
+
+
+
+/* Returns in order of precedence:
+ *
+ * 1. Temp dir as set via the command line.
+ * 2. Temp dir as set in CVSROOT/config.
+ * 3. Temp dir as set in $TMPDIR env var.
+ * 4. Contents of TMPDIR_DFLT preprocessor macro.
+ *
+ * ERRORS
+ * It is a fatal error if this function would otherwise return NULL or an
+ * empty string.
+ */
+const char *
+get_cvs_tmp_dir (void)
+{
+ const char *retval;
+ if (tmpdir_cmdline) retval = tmpdir_cmdline;
+ else if (config && config->TmpDir) retval = config->TmpDir;
+ else retval = get_system_temp_dir ();
+ if (!retval) retval = TMPDIR_DFLT;
+
+ if (!retval || !*retval) error (1, 0, "No temp dir specified.");
+
+ return retval;
+}
+
+
+
+/* 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
+{
+ const 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. */
+
+ const char *nick1;
+ const char *nick2;
+
+ int (*func) (int, char **); /* 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", NULL, 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 */
+ { "ls", "dir", "list", ls, 0 },
+#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,
CVS_CMD_MODIFIES_REPOSITORY },
+ { "remove", "rm", "delete", cvsremove,
CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
+ { "rlog", "rl", NULL, cvslog, 0 },
+ { "rls", "rdir", "rlist", ls, 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://www.nongnu.org/cvs/ or\n",
+ " the CVSNT home page at http://www.cvsnt.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 */
+ " ls List files available from CVS\n",
+#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",
+ " rls List files in 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",
+ " -n Do not execute anything that will change the disk.\n",
+ " -t Show trace of program execution (repeat for more\n",
+ " verbosity) -- 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 # Request 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 (Node *p, void *ignored)
+{
+ if (current_parsed_root == NULL && p->data != NULL)
+ {
+ current_parsed_root = p->data;
+ original_parsed_root = current_parsed_root;
+ return 1;
+ }
+ return 0;
+}
+
+
+static const char * const*
+cmd_synonyms (void)
+{
+ 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 = xnmalloc (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 = Xasprintf (" %-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 (const 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;
+}
+
+
+
+/*
+ * Exit with an error code and an informative message about the signal
+ * received. This function, by virtue of causing an actual call to exit(),
+ * causes all the atexit() handlers to be called.
+ *
+ * INPUTS
+ * sig The signal recieved.
+ *
+ * ERRORS
+ * The cleanup routines registered via atexit() and the error function
+ * itself can potentially change the exit status. They shouldn't do this
+ * unless they encounter problems doing their own jobs.
+ *
+ * RETURNS
+ * Nothing. This function will always exit. It should exit with an exit
+ * status of 1, but might not, as noted in the ERRORS section above.
+ */
+#ifndef DONT_USE_SIGNALS
+static RETSIGTYPE main_cleanup (int) __attribute__ ((__noreturn__));
+#endif /* DONT_USE_SIGNALS */
+static RETSIGTYPE
+main_cleanup (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;
+ }
+
+ /* This always exits, which will cause our exit handlers to be called. */
+ error (1, 0, "received %s signal", name);
+ /* but make the exit explicit to silence warnings when gcc processes the
+ * noreturn attribute.
+ */
+ exit (EXIT_FAILURE);
+#endif /* !DONT_USE_SIGNALS */
+}
+
+
+
+/* From server.c.
+ *
+ * When !defined ALLOW_CONFIG_OVERRIDE, this will never have any value but
+ * NULL.
+ */
+extern char *gConfigPath;
+
+
+
+
+enum {RANDOM_BYTES = 8};
+enum {COMMITID_RAW_SIZE = (sizeof(time_t) + RANDOM_BYTES)};
+
+static char const alphabet[62] =
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+/* Divide BUF by D, returning the remainder. Replace BUF by the
+ quotient. BUF[0] is the most significant part of BUF.
+ D must not exceed UINT_MAX >> CHAR_BIT. */
+static unsigned int
+divide_by (unsigned char buf[COMMITID_RAW_SIZE], unsigned int d)
+{
+ unsigned int carry = 0;
+ int i;
+ for (i = 0; i < COMMITID_RAW_SIZE; i++)
+ {
+ unsigned int byte = buf[i];
+ unsigned int dividend = (carry << CHAR_BIT) + byte;
+ buf[i] = dividend / d;
+ carry = dividend % d;
+ }
+ return carry;
+}
+
+static void
+convert (char const input[COMMITID_RAW_SIZE], char *output)
+{
+ static char const zero[COMMITID_RAW_SIZE] = { 0, };
+ unsigned char buf[COMMITID_RAW_SIZE];
+ size_t o = 0;
+ memcpy (buf, input, COMMITID_RAW_SIZE);
+ while (memcmp (buf, zero, COMMITID_RAW_SIZE) != 0)
+ output[o++] = alphabet[divide_by (buf, sizeof alphabet)];
+ if (! o)
+ output[o++] = '0';
+ output[o] = '\0';
+}
+
+
+int
+main (int argc, char **argv)
+{
+ cvsroot_t *CVSroot_parsed = NULL;
+ bool cvsroot_update_env = true;
+ char *cp, *end;
+ const struct cmd *cm;
+ int c, err = 0;
+ int free_Editor = 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[] = "+QqrwtnRvb:T:e:d:Hfz:s:xa";
+ 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},
+ {"sign-textmode", required_argument, NULL, 5},
+#ifdef SERVER_SUPPORT
+ {"allow-root", required_argument, NULL, 3},
+#endif /* SERVER_SUPPORT */
+ {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 SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ cleanup_register (SYSTEM_CLEANUP);
+#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
+
+ /*
+ * Initialize globals.
+ */
+ /* 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
+ */
+ 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;
+ }
+
+ /* 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;
+ }
+
+#ifdef SERVER_SUPPORT
+ /* Don't try and read a .cvsrc file if we are a server. */
+ if (optind < argc
+ && (false
+# if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
+ || !strcmp (argv[optind], "pserver")
+# endif
+# ifdef HAVE_KERBEROS
+ || !strcmp (argv[optind], "kserver")
+# endif /* HAVE_KERBEROS */
+ || !strcmp (argv[optind], "server")))
+ {
+ /* Avoid any .cvsrc file. */
+ use_cvsrc = 0;
+ /* Pre-parse the server options to get the config path. */
+ cvs_cmd_name = argv[optind];
+ parseServerOptions (argc - optind, argv + optind);
+ }
+#endif /* SERVER_SUPPORT */
+
+ /*
+ * 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 5:
+ /* --sign-textmode */
+ if (sign_textmode) free (&sign_textmode);
+ sign_textmode = xstrdup (optarg);
+ break;
+#ifdef SERVER_SUPPORT
+ case 3:
+ /* --allow-root */
+ root_allow_add (optarg, gConfigPath);
+ break;
+#endif /* SERVER_SUPPORT */
+ case 'Q':
+ really_quiet = 1;
+ /* FALL THROUGH */
+ case 'q':
+ quiet = 1;
+ break;
+ case 'r':
+ cvswrite = 0;
+ break;
+ case 'w':
+ cvswrite = 1;
+ break;
+ case 't':
+ trace++;
+ break;
+ case 'R':
+ readonlyfs = -1;
+ logoff = 1;
+ break;
+ case 'n':
+ noexec = 1;
+ logoff = 1;
+ break;
+ case 'v':
+ (void) fputs ("\n", stdout);
+ version (0, NULL);
+ (void) fputs ("\n", stdout);
+ (void) fputs ("\
+Copyright (C) 2005 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);
+
+ 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 (tmpdir_cmdline) free (tmpdir_cmdline);
+ tmpdir_cmdline = xstrdup (optarg);
+ 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':
+#ifdef CLIENT_SUPPORT
+ 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");
+#endif /* CLIENT_SUPPORT */
+ /* 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 '?':
+ default:
+ usage (usg);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ usage (usg);
+
+ if (readonlyfs && !really_quiet) {
+ error (0, 0,
+ "WARNING: Read-only repository access mode selected via `cvs
-R'.\n\
+Using this option to access a repository which some users write to may\n\
+cause intermittent sandbox corruption.");
+ }
+
+ /* Calculate the cvs global session ID */
+
+ {
+ char buf[COMMITID_RAW_SIZE] = { 0, };
+ char out[COMMITID_RAW_SIZE * 2];
+ ssize_t len = 0;
+ time_t rightnow = time (NULL);
+ char *startrand = buf + sizeof (time_t);
+ unsigned char *p = (unsigned char *) startrand;
+ size_t randbytes = RANDOM_BYTES;
+ int flags = O_RDONLY;
+ int fd;
+#ifdef O_NOCTTY
+ flags |= O_NOCTTY;
+#endif
+ if (rightnow != (time_t)-1)
+ while (rightnow > 0) {
+ *--p = rightnow % (UCHAR_MAX + 1);
+ rightnow /= UCHAR_MAX + 1;
+ }
+ else {
+ /* try to use more random data */
+ randbytes = COMMITID_RAW_SIZE;
+ startrand = buf;
+ }
+ fd = open ("/dev/urandom", flags);
+ if (fd >= 0) {
+ len = read (fd, startrand, randbytes);
+ close (fd);
+ }
+ if (len <= 0) {
+ /* no random data was available so use pid */
+ long int pid = (long int)getpid ();
+ p = (unsigned char *) (startrand + sizeof (pid));
+ while (pid > 0) {
+ *--p = pid % (UCHAR_MAX + 1);
+ pid /= UCHAR_MAX + 1;
+ }
+ }
+ convert(buf, out);
+ global_session_id = strdup (out);
+ }
+
+
+ TRACE (TRACE_FUNCTION, "main: Session ID is %s", global_session_id);
+
+ /* 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. */
+
+ short int lock_cleanup_setup = 0;
+
+ /* 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);
+ }
+
+ /* HOSTNAME & SERVER_HOSTNAME need to be set before they are
+ * potentially used in gserver_authenticate_connection() (called from
+ * pserver_authenticate_connection, below).
+ */
+ hostname = xgethostname ();
+ if (!hostname)
+ {
+ error (0, errno,
+ "xgethostname () returned NULL, using \"localhost\"");
+ hostname = xstrdup ("localhost");
+ }
+
+ /* Keep track of this separately since the client can change
+ * HOSTNAME on the server.
+ */
+ server_hostname = xstrdup (hostname);
+
+#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 that 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;
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* 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. */
+ CurDir = xstrdup ("<remote>");
+ cleanup_register (server_cleanup);
+ }
+ else
+#endif
+ {
+ cleanup_register (close_stdout);
+ CurDir = xgetcwd ();
+ if (CurDir == NULL)
+ error (1, errno, "cannot get working directory");
+ }
+
+ {
+ char *val;
+ /* XXX pid < 10^32 */
+ val = Xasprintf ("%ld", (long) getpid ());
+ setenv (CVS_PID_ENV, val, 1);
+ free (val);
+ }
+
+ /* make sure we clean up on error */
+ signals_register (main_cleanup);
+
+#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, NULL, _IONBF, 0);
+ (void) setvbuf (stderr, 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 = false;
+ }
+ }
+
+#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. */
+
+ TRACE (TRACE_FUNCTION,
+ "main loop with CVSROOT=%s",
+ current_parsed_root ? current_parsed_root->directory
+ : "(null)");
+
+ /*
+ * Check to see if the repository exists.
+ */
+ if (!current_parsed_root->isremote)
+ {
+ char *path;
+ int save_errno;
+
+ path = Xasprintf ("%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);
+ }
+
+ /* Update the CVSROOT environment variable. */
+ if (cvsroot_update_env)
+ setenv (CVSROOT_ENV, current_parsed_root->original, 1);
+ }
+
+ /* 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 meant 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! */
+ if (config) free_config (config);
+ config = parse_config (current_parsed_root->directory, NULL);
+
+ /* Can set TMPDIR in the environment if necessary now, since
+ * if it was set in config, we now know it.
+ */
+ push_env_temp_dir ();
+ }
+
+#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
+
+ if (
+#ifdef SERVER_SUPPORT
+ /* Don't worry about lock_cleanup_setup when the server is
+ * active since we can only go through this loop once in that
+ * case anyhow.
+ */
+ server_active ||
+#endif
+ (
+#ifdef CLIENT_SUPPORT
+ !current_parsed_root->isremote &&
+#endif
+ !lock_cleanup_setup))
+ {
+ /* Set up to clean up any locks we might create on exit. */
+ cleanup_register (Lock_Cleanup);
+ lock_cleanup_setup = 1;
+ }
+
+ /* Call our worker function. */
+ 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,
+ original_parsed_root->original);
+ assert (n != NULL);
+ assert (n->data != NULL);
+ n->data = NULL;
+ current_parsed_root = NULL;
+ }
+
+ if (server_active)
+ break;
+ } /* end of loop for cvsroot values */
+
+ dellist (&root_directories);
+ } /* end of stuff that gets done if the user DOESN'T ask for help */
+
+ root_allow_free ();
+
+ /* 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 (const char *rawdate)
+{
+ struct timespec t;
+
+ if (!get_date (&t, rawdate, NULL))
+ error (1, 0, "Can't parse date/time: `%s'", rawdate);
+
+ /* Truncate nanoseconds. */
+ return date_from_time_t (t.tv_sec);
+}
+
+
+
+/* Parse a string of the form TAG[:DATE], where TAG could be the empty string.
+ *
+ * INPUTS
+ * input The string to be parsed.
+ *
+ * OUTPUTS
+ * tag The tag found, if any. If TAG is the empty string, then leave
+ * this value unchanged.
+ * date The date found, if any. If DATE is the empty string or is
+ * missing, leave this value unchanged.
+ *
+ * NOTES
+ * If either TAG or DATE is replaced for output, the previous value is freed.
+ *
+ * ERRORS
+ * If either TAG or DATE cannot be parsed, then this function will exit with
+ * a fatal error message.
+ *
+ * RETURNS
+ * Nothing.
+ */
+void
+parse_tagdate (char **tag, char **date, const char *input)
+{
+ char *p;
+
+ TRACE (TRACE_FUNCTION, "parse_tagdate (%s, %s, %s)",
+ *tag ? *tag : "(null)", *date ? *date : "(null)",
+ input);
+
+ if ((p = strchr (input, ':')))
+ {
+ /* Parse the tag. */
+ if (p - input)
+ {
+ /* The tag has > 0 length. */
+ if (*tag) free (*tag);
+ *tag = xmalloc (p - input + 1);
+ strncpy (*tag, input, p - input);
+ (*tag)[p - input] = '\0';
+ }
+
+ /* Parse the date. */
+ if (*++p)
+ {
+ if (*date) free (*date);
+ *date = Make_Date (p);
+ }
+ }
+ else if (strlen (input))
+ {
+ /* The tag has > 0 length. */
+ if (*tag) free (*tag);
+ *tag = xstrdup (input);
+ }
+
+ TRACE (TRACE_DATA, "parse_tagdate: got tag = `%s', date = `%s'",
+ *tag ? *tag : "(null)", *date ? *date : "(null)");
+}
+
+
+
+/* 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 (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 (char *dest, const char *source)
+{
+ struct tm date;
+
+ date_to_tm (&date, source);
+ tm_to_internet (dest, &date);
+}
+
+
+
+void
+date_to_tm (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 (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);
+}
+
+
+
+/*
+ * Format a date for the current locale.
+ *
+ * INPUT
+ * UNIXTIME The UNIX seconds since the epoch.
+ *
+ * RETURNS
+ * If my_strftime() encounters an error, this function can return NULL.
+ *
+ * Otherwise, returns a date string in ISO8601 format, e.g.:
+ *
+ * 2004-04-29 13:24:22 -0700
+ *
+ * It is the responsibility of the caller to return of this string.
+ */
+static char *
+format_time_t (time_t unixtime)
+{
+ static char buf[sizeof ("yyyy-mm-dd HH:MM:SS -HHMM")];
+ /* Convert to a time in the local time zone. */
+ struct tm ltm = *(localtime (&unixtime));
+
+ if (!my_strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S %z", <m, 0, 0))
+ return NULL;
+
+ return xstrdup (buf);
+}
+
+
+
+/* Like format_time_t(), but return time in UTC.
+ */
+char *
+gmformat_time_t (time_t unixtime)
+{
+ static char buf[sizeof ("yyyy-mm-dd HH:MM:SS -HHMM")];
+ /* Convert to a time in the local time zone. */
+ struct tm ltm = *(gmtime (&unixtime));
+
+ if (!my_strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S %z", <m, 0, 0))
+ return NULL;
+
+ return xstrdup (buf);
+}
+
+
+
+/* Format a date in the local timezone using format_time_t() given a date from
+ * an arbitrary timezone in a string.
+ *
+ * INPUT
+ * DATESTR A string that looks like anything get_date() can parse, e.g.:
+ *
+ * 2004-04-29 20:24:22
+ *
+ * ERRORS
+ * As get_date() & format_time_t(). Prints a warning if either provide
+ * error return values. See RETURNS.
+ *
+ * RETURNS
+ * A freshly allocated string that is a copy of the input string if either
+ * get_date() or format_time_t() encounter an error and as format_time_t()
+ * otherwise.
+ */
+char *
+format_date_alloc (char *datestr)
+{
+ struct timespec t;
+ char *buf;
+
+ TRACE (TRACE_FUNCTION, "format_date (%s)", datestr);
+
+ /* Convert the date string to seconds since the epoch. */
+ if (!get_date (&t, datestr, NULL))
+ {
+ error (0, 0, "Can't parse date/time: `%s'.", datestr);
+ goto as_is;
+ }
+
+ /* Get the time into a string, truncating any nanoseconds returned by
+ * getdate.
+ */
+ if ((buf = format_time_t (t.tv_sec)) == NULL)
+ {
+ error (0, 0, "Unable to reformat date `%s'.", datestr);
+ goto as_is;
+ }
+
+ return buf;
+
+ as_is:
+ return xstrdup (datestr);
+}
+
+
+
+void
+usage (register const char *const *cpp)
+{
+ (void) fprintf (stderr, *cpp++, program_name, cvs_cmd_name);
+ for (; *cpp; cpp++)
+ (void) fprintf (stderr, *cpp);
+ exit (EXIT_FAILURE);
+}
+
+/* vim:tabstop=8:shiftwidth=4
+ */
- [Cvs-cvs] Changes to ccvs/src/main.c [signed-commits],
Derek Robert Price <=