xref: /dragonfly/contrib/cvs-1.12/src/main.c (revision d600454b)
1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  *
10  * You may distribute under the terms of the GNU General Public License
11  * as specified in the README file that comes with the CVS source distribution.
12  *
13  * This is the main C driver for the CVS system.
14  *
15  * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing
16  * the shell-script CVS system that this is based on.
17  *
18  */
19 
20 #include "cvs.h"
21 
22 #include "closeout.h"
23 #include "setenv.h"
24 #include "strftime.h"
25 #include "xgethostname.h"
26 
27 const char *program_name;
28 const char *program_path;
29 const char *cvs_cmd_name;
30 
31 const char *global_session_id; /* Random session ID */
32 
33 char *hostname;
34 /* FIXME: Perhaps this should be renamed original_hostname or the like?  */
35 char *server_hostname;
36 
37 int use_editor = 1;
38 int use_cvsrc = 1;
39 int cvswrite = !CVSREAD_DFLT;
40 int really_quiet = 0;
41 int quiet = 0;
42 int trace = 0;
43 int noexec = 0;
44 int readonlyfs = 0;
45 int logoff = 0;
46 
47 
48 
49 /***
50  ***
51  ***   CVSROOT/config options
52  ***
53  ***/
54 struct config *config;
55 
56 
57 
58 mode_t cvsumask = UMASK_DFLT;
59 
60 char *CurDir;
61 
62 /*
63  * Defaults, for the environment variables that are not set
64  */
65 char *Editor = EDITOR_DFLT;
66 
67 
68 
69 /* Temp dir stuff.  */
70 
71 /* Temp dir, if set by the user.  */
72 static char *tmpdir_cmdline;
73 
74 
75 
76 /* Returns in order of precedence:
77  *
78  *	1.  Temp dir as set via the command line.
79  *	2.  Temp dir as set in CVSROOT/config.
80  *	3.  Temp dir as set in $TMPDIR env var.
81  *	4.  Contents of TMPDIR_DFLT preprocessor macro.
82  *
83  * ERRORS
84  *  It is a fatal error if this function would otherwise return NULL or an
85  *  empty string.
86  */
87 const char *
88 get_cvs_tmp_dir (void)
89 {
90     const char *retval;
91     if (tmpdir_cmdline) retval = tmpdir_cmdline;
92     else if (config && config->TmpDir) retval = config->TmpDir;
93     else retval = get_system_temp_dir ();
94     if (!retval) retval = TMPDIR_DFLT;
95 
96     if (!retval || !*retval) error (1, 0, "No temp dir specified.");
97 
98     return retval;
99 }
100 
101 
102 
103 /* When our working directory contains subdirectories with different
104    values in CVS/Root files, we maintain a list of them.  */
105 List *root_directories = NULL;
106 
107 static const struct cmd
108 {
109     const char *fullname;	/* Full name of the function (e.g. "commit") */
110 
111     /* Synonyms for the command, nick1 and nick2.  We supply them
112        mostly for two reasons: (1) CVS has always supported them, and
113        we need to maintain compatibility, (2) if there is a need for a
114        version which is shorter than the fullname, for ease in typing.
115        Synonyms have the disadvantage that people will see "new" and
116        then have to think about it, or look it up, to realize that is
117        the operation they know as "add".  Also, this means that one
118        cannot create a command "cvs new" with a different meaning.  So
119        new synonyms are probably best used sparingly, and where used
120        should be abbreviations of the fullname (preferably consisting
121        of the first 2 or 3 or so letters).
122 
123        One thing that some systems do is to recognize any unique
124        abbreviation, for example "annotat" "annota", etc., for
125        "annotate".  The problem with this is that scripts and user
126        habits will expect a certain abbreviation to be unique, and in
127        a future release of CVS it may not be.  So it is better to
128        accept only an explicit list of abbreviations and plan on
129        supporting them in the future as well as now.  */
130 
131     const char *nick1;
132     const char *nick2;
133 
134     int (*func) (int, char **);	/* Function takes (argc, argv) arguments. */
135     unsigned long attr;		/* Attributes. */
136 } cmds[] =
137 
138 {
139     { "add",      "ad",       "new",       add,       CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
140     { "admin",    "adm",      "rcs",       admin,     CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
141     { "annotate", "ann",      NULL,        annotate,  CVS_CMD_USES_WORK_DIR },
142     { "checkout", "co",       "get",       checkout,  0 },
143     { "commit",   "ci",       "com",       commit,    CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
144     { "diff",     "di",       "dif",       diff,      CVS_CMD_USES_WORK_DIR },
145     { "edit",     NULL,       NULL,        edit,      CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
146     { "editors",  NULL,       NULL,        editors,   CVS_CMD_USES_WORK_DIR },
147     { "export",   "exp",      "ex",        checkout,  CVS_CMD_USES_WORK_DIR },
148     { "history",  "hi",       "his",       history,   CVS_CMD_USES_WORK_DIR },
149     { "import",   "im",       "imp",       import,    CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR | CVS_CMD_IGNORE_ADMROOT},
150     { "init",     NULL,       NULL,        init,      CVS_CMD_MODIFIES_REPOSITORY },
151 #if defined (HAVE_KERBEROS) && defined (SERVER_SUPPORT)
152     { "kserver",  NULL,       NULL,        server,    CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, /* placeholder */
153 #endif
154     { "log",      "lo",       NULL,        cvslog,    CVS_CMD_USES_WORK_DIR },
155 #ifdef AUTH_CLIENT_SUPPORT
156     { "login",    "logon",    "lgn",       login,     0 },
157     { "logout",   NULL,       NULL,        logout,    0 },
158 #endif /* AUTH_CLIENT_SUPPORT */
159     { "ls",       "dir",      "list",      ls,        0 },
160 #if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT)
161     { "pserver",  NULL,       NULL,        server,    CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, /* placeholder */
162 #endif
163     { "rannotate","rann",     "ra",        annotate,  0 },
164     { "rdiff",    "patch",    "pa",        patch,     0 },
165     { "release",  "re",       "rel",       release,   CVS_CMD_MODIFIES_REPOSITORY },
166     { "remove",   "rm",       "delete",    cvsremove, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
167     { "rlog",     "rl",       NULL,        cvslog,    0 },
168     { "rls",      "rdir",     "rlist",     ls,        0 },
169     { "rtag",     "rt",       "rfreeze",   cvstag,    CVS_CMD_MODIFIES_REPOSITORY },
170 #ifdef SERVER_SUPPORT
171     { "server",   NULL,       NULL,        server,    CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
172 #endif
173     { "status",   "st",       "stat",      cvsstatus, CVS_CMD_USES_WORK_DIR },
174     { "tag",      "ta",       "freeze",    cvstag,    CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
175     { "unedit",   NULL,       NULL,        unedit,    CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
176     { "update",   "up",       "upd",       update,    CVS_CMD_USES_WORK_DIR },
177     { "version",  "ve",       "ver",       version,   0 },
178     { "watch",    NULL,       NULL,        watch,     CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR },
179     { "watchers", NULL,       NULL,        watchers,  CVS_CMD_USES_WORK_DIR },
180     { NULL, NULL, NULL, NULL, 0 },
181 };
182 
183 static const char *const usg[] =
184 {
185     /* CVS usage messages never have followed the GNU convention of
186        putting metavariables in uppercase.  I don't know whether that
187        is a good convention or not, but if it changes it would have to
188        change in all the usage messages.  For now, they consistently
189        use lowercase, as far as I know.  Punctuation is pretty funky,
190        though.  Sometimes they use none, as here.  Sometimes they use
191        single quotes (not the TeX-ish `' stuff), as in --help-options.
192        Sometimes they use double quotes, as in cvs -H add.
193 
194        Most (not all) of the usage messages seem to have periods at
195        the end of each line.  I haven't tried to duplicate this style
196        in --help as it is a rather different format from the rest.  */
197 
198     "Usage: %s [cvs-options] command [command-options-and-arguments]\n",
199     "  where cvs-options are -q, -n, etc.\n",
200     "    (specify --help-options for a list of options)\n",
201     "  where command is add, admin, etc.\n",
202     "    (specify --help-commands for a list of commands\n",
203     "     or --help-synonyms for a list of command synonyms)\n",
204     "  where command-options-and-arguments depend on the specific command\n",
205     "    (specify -H followed by a command name for command-specific help)\n",
206     "  Specify --help to receive this message\n",
207     "\n",
208 
209     /* Some people think that a bug-reporting address should go here.  IMHO,
210        the web sites are better because anything else is very likely to go
211        obsolete in the years between a release and when someone might be
212        reading this help.  Besides, we could never adequately discuss
213        bug reporting in a concise enough way to put in a help message.  */
214 
215     /* I was going to put this at the top, but usage() wants the %s to
216        be in the first line.  */
217     "The Concurrent Versions System (CVS) is a tool for version control.\n",
218     /* I really don't think I want to try to define "version control"
219        in one line.  I'm not sure one can get more concise than the
220        paragraph in ../cvs.spec without assuming the reader knows what
221        version control means.  */
222 
223     "For CVS updates and additional information, see\n",
224     "    the CVS home page at http://www.nongnu.org/cvs/ or\n",
225     "    the CVSNT home page at http://www.cvsnt.org/\n",
226     NULL,
227 };
228 
229 static const char *const cmd_usage[] =
230 {
231     "CVS commands are:\n",
232     "        add          Add a new file/directory to the repository\n",
233     "        admin        Administration front end for rcs\n",
234     "        annotate     Show last revision where each line was modified\n",
235     "        checkout     Checkout sources for editing\n",
236     "        commit       Check files into the repository\n",
237     "        diff         Show differences between revisions\n",
238     "        edit         Get ready to edit a watched file\n",
239     "        editors      See who is editing a watched file\n",
240     "        export       Export sources from CVS, similar to checkout\n",
241     "        history      Show repository access history\n",
242     "        import       Import sources into CVS, using vendor branches\n",
243     "        init         Create a CVS repository if it doesn't exist\n",
244 #if defined (HAVE_KERBEROS) && defined (SERVER_SUPPORT)
245     "        kserver      Kerberos server mode\n",
246 #endif
247     "        log          Print out history information for files\n",
248 #ifdef AUTH_CLIENT_SUPPORT
249     "        login        Prompt for password for authenticating server\n",
250     "        logout       Removes entry in .cvspass for remote repository\n",
251 #endif /* AUTH_CLIENT_SUPPORT */
252     "        ls           List files available from CVS\n",
253 #if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT)
254     "        pserver      Password server mode\n",
255 #endif
256     "        rannotate    Show last revision where each line of module was modified\n",
257     "        rdiff        Create 'patch' format diffs between releases\n",
258     "        release      Indicate that a Module is no longer in use\n",
259     "        remove       Remove an entry from the repository\n",
260     "        rlog         Print out history information for a module\n",
261     "        rls          List files in a module\n",
262     "        rtag         Add a symbolic tag to a module\n",
263 #ifdef SERVER_SUPPORT
264     "        server       Server mode\n",
265 #endif
266     "        status       Display status information on checked out files\n",
267     "        tag          Add a symbolic tag to checked out version of files\n",
268     "        unedit       Undo an edit command\n",
269     "        update       Bring work tree in sync with repository\n",
270     "        version      Show current CVS version(s)\n",
271     "        watch        Set watches\n",
272     "        watchers     See who is watching a file\n",
273     "(Specify the --help option for a list of other help options)\n",
274     NULL,
275 };
276 
277 static const char *const opt_usage[] =
278 {
279     /* Omit -b because it is just for compatibility.  */
280     "CVS global options (specified before the command name) are:\n",
281     "    -H           Displays usage information for command.\n",
282     "    -Q           Cause CVS to be really quiet.\n",
283     "    -q           Cause CVS to be somewhat quiet.\n",
284     "    -r           Make checked-out files read-only.\n",
285     "    -w           Make checked-out files read-write (default).\n",
286     "    -n           Do not execute anything that will change the disk.\n",
287     "    -t           Show trace of program execution (repeat for more\n",
288     "                 verbosity) -- try with -n.\n",
289     "    -R           Assume repository is read-only, such as CDROM\n",
290     "    -v           CVS version and copyright.\n",
291     "    -T tmpdir    Use 'tmpdir' for temporary files.\n",
292     "    -e editor    Use 'editor' for editing log information.\n",
293     "    -d CVS_root  Overrides $CVSROOT as the root of the CVS tree.\n",
294     "    -f           Do not use the ~/.cvsrc file.\n",
295 #ifdef CLIENT_SUPPORT
296     "    -z #         Request compression level '#' for net traffic.\n",
297 #ifdef ENCRYPTION
298     "    -x           Encrypt all net traffic.\n",
299 #endif
300     "    -a           Authenticate all net traffic.\n",
301 #endif
302     "    -s VAR=VAL   Set CVS user variable.\n",
303     "(Specify the --help option for a list of other help options)\n",
304     NULL
305 };
306 
307 
308 static int
309 set_root_directory (Node *p, void *ignored)
310 {
311     if (current_parsed_root == NULL && p->data != NULL)
312     {
313 	current_parsed_root = p->data;
314 	original_parsed_root = current_parsed_root;
315 	return 1;
316     }
317     return 0;
318 }
319 
320 
321 static const char * const*
322 cmd_synonyms (void)
323 {
324     char ** synonyms;
325     char ** line;
326     const struct cmd *c = &cmds[0];
327     /* Three more for title, "specify --help" line, and NULL.  */
328     int numcmds = 3;
329 
330     while (c->fullname != NULL)
331     {
332 	numcmds++;
333 	c++;
334     }
335 
336     synonyms = xnmalloc (numcmds, sizeof(char *));
337     line = synonyms;
338     *line++ = "CVS command synonyms are:\n";
339     for (c = &cmds[0]; c->fullname != NULL; c++)
340     {
341 	if (c->nick1 || c->nick2)
342 	{
343 	    *line = Xasprintf ("        %-12s %s %s\n", c->fullname,
344 			       c->nick1 ? c->nick1 : "",
345 			       c->nick2 ? c->nick2 : "");
346 	    line++;
347 	}
348     }
349     *line++ = "(Specify the --help option for a list of other help options)\n";
350     *line = NULL;
351 
352     return (const char * const*) synonyms; /* will never be freed */
353 }
354 
355 
356 
357 unsigned long int
358 lookup_command_attribute (const char *cmd_name)
359 {
360     const struct cmd *cm;
361 
362     for (cm = cmds; cm->fullname; cm++)
363     {
364 	if (strcmp (cmd_name, cm->fullname) == 0)
365 	    break;
366     }
367     if (!cm->fullname)
368 	error (1, 0, "unknown command: %s", cmd_name);
369     return cm->attr;
370 }
371 
372 
373 
374 /*
375  * Exit with an error code and an informative message about the signal
376  * received.  This function, by virtue of causing an actual call to exit(),
377  * causes all the atexit() handlers to be called.
378  *
379  * INPUTS
380  *   sig	The signal recieved.
381  *
382  * ERRORS
383  *   The cleanup routines registered via atexit() and the error function
384  *   itself can potentially change the exit status.  They shouldn't do this
385  *   unless they encounter problems doing their own jobs.
386  *
387  * RETURNS
388  *   Nothing.  This function will always exit.  It should exit with an exit
389  *   status of 1, but might not, as noted in the ERRORS section above.
390  */
391 #ifndef DONT_USE_SIGNALS
392 static RETSIGTYPE main_cleanup (int) __attribute__ ((__noreturn__));
393 #endif /* DONT_USE_SIGNALS */
394 static RETSIGTYPE
395 main_cleanup (int sig)
396 {
397 #ifndef DONT_USE_SIGNALS
398     const char *name;
399     char temp[10];
400 
401     switch (sig)
402     {
403 #ifdef SIGABRT
404     case SIGABRT:
405 	name = "abort";
406 	break;
407 #endif
408 #ifdef SIGHUP
409     case SIGHUP:
410 	name = "hangup";
411 	break;
412 #endif
413 #ifdef SIGINT
414     case SIGINT:
415 	name = "interrupt";
416 	break;
417 #endif
418 #ifdef SIGQUIT
419     case SIGQUIT:
420 	name = "quit";
421 	break;
422 #endif
423 #ifdef SIGPIPE
424     case SIGPIPE:
425 	name = "broken pipe";
426 	break;
427 #endif
428 #ifdef SIGTERM
429     case SIGTERM:
430 	name = "termination";
431 	break;
432 #endif
433     default:
434 	/* This case should never be reached, because we list above all
435 	   the signals for which we actually establish a signal handler.  */
436 	sprintf (temp, "%d", sig);
437 	name = temp;
438 	break;
439     }
440 
441     /* This always exits, which will cause our exit handlers to be called.  */
442     error (1, 0, "received %s signal", name);
443     /* but make the exit explicit to silence warnings when gcc processes the
444      * noreturn attribute.
445      */
446     exit (EXIT_FAILURE);
447 #endif /* !DONT_USE_SIGNALS */
448 }
449 
450 
451 
452 /* From server.c.
453  *
454  * When !defined ALLOW_CONFIG_OVERRIDE, this will never have any value but
455  * NULL.
456  */
457 extern char *gConfigPath;
458 
459 
460 
461 
462 enum {RANDOM_BYTES = 8};
463 enum {COMMITID_RAW_SIZE = (sizeof(time_t) + RANDOM_BYTES)};
464 
465 static char const alphabet[62] =
466   "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
467 
468 /* Divide BUF by D, returning the remainder.  Replace BUF by the
469    quotient.  BUF[0] is the most significant part of BUF.
470    D must not exceed UINT_MAX >> CHAR_BIT.  */
471 static unsigned int
472 divide_by (unsigned char buf[COMMITID_RAW_SIZE], unsigned int d)
473 {
474     unsigned int carry = 0;
475     int i;
476     for (i = 0; i < COMMITID_RAW_SIZE; i++)
477     {
478 	unsigned int byte = buf[i];
479 	unsigned int dividend = (carry << CHAR_BIT) + byte;
480 	buf[i] = dividend / d;
481 	carry = dividend % d;
482     }
483     return carry;
484 }
485 
486 static void
487 convert (char const input[COMMITID_RAW_SIZE], char *output)
488 {
489     static char const zero[COMMITID_RAW_SIZE] = { 0, };
490     unsigned char buf[COMMITID_RAW_SIZE];
491     size_t o = 0;
492     memcpy (buf, input, COMMITID_RAW_SIZE);
493     while (memcmp (buf, zero, COMMITID_RAW_SIZE) != 0)
494 	output[o++] = alphabet[divide_by (buf, sizeof alphabet)];
495     if (! o)
496 	output[o++] = '0';
497     output[o] = '\0';
498 }
499 
500 
501 int
502 main (int argc, char **argv)
503 {
504     cvsroot_t *CVSroot_parsed = NULL;
505     bool cvsroot_update_env = true;
506     char *cp, *end;
507     const struct cmd *cm;
508     int c, err = 0;
509     int free_Editor = 0;
510 
511     int help = 0;		/* Has the user asked for help?  This
512 				   lets us support the `cvs -H cmd'
513 				   convention to give help for cmd. */
514     static const char short_options[] = "+QqrwtnRvb:T:e:d:Hfz:s:xa";
515     static struct option long_options[] =
516     {
517         {"help", 0, NULL, 'H'},
518         {"version", 0, NULL, 'v'},
519 	{"help-commands", 0, NULL, 1},
520 	{"help-synonyms", 0, NULL, 2},
521 	{"help-options", 0, NULL, 4},
522 #ifdef SERVER_SUPPORT
523 	{"allow-root", required_argument, NULL, 3},
524 #endif /* SERVER_SUPPORT */
525         {0, 0, 0, 0}
526     };
527     /* `getopt_long' stores the option index here, but right now we
528         don't use it. */
529     int option_index = 0;
530 
531 #ifdef SYSTEM_INITIALIZE
532     /* Hook for OS-specific behavior, for example socket subsystems on
533        NT and OS2 or dealing with windows and arguments on Mac.  */
534     SYSTEM_INITIALIZE (&argc, &argv);
535 #endif
536 
537 #ifdef SYSTEM_CLEANUP
538 	/* Hook for OS-specific behavior, for example socket subsystems on
539 	   NT and OS2 or dealing with windows and arguments on Mac.  */
540 	cleanup_register (SYSTEM_CLEANUP);
541 #endif
542 
543 #ifdef HAVE_TZSET
544     /* On systems that have tzset (which is almost all the ones I know
545        of), it's a good idea to call it.  */
546     tzset ();
547 #endif
548 
549     /*
550      * Just save the last component of the path for error messages
551      */
552     program_path = xstrdup (argv[0]);
553 #ifdef ARGV0_NOT_PROGRAM_NAME
554     /* On some systems, e.g. VMS, argv[0] is not the name of the command
555        which the user types to invoke the program.  */
556     program_name = "cvs";
557 #else
558     program_name = last_component (argv[0]);
559 #endif
560 
561     /*
562      * Query the environment variables up-front, so that
563      * they can be overridden by command line arguments
564      */
565     if ((cp = getenv (EDITOR1_ENV)) != NULL)
566  	Editor = cp;
567     else if ((cp = getenv (EDITOR2_ENV)) != NULL)
568 	Editor = cp;
569     else if ((cp = getenv (EDITOR3_ENV)) != NULL)
570 	Editor = cp;
571     if (getenv (CVSREAD_ENV) != NULL)
572 	cvswrite = 0;
573     if (getenv (CVSREADONLYFS_ENV) != NULL) {
574 	readonlyfs = 1;
575 	logoff = 1;
576     }
577 
578     /* Set this to 0 to force getopt initialization.  getopt() sets
579        this to 1 internally.  */
580     optind = 0;
581 
582     /* We have to parse the options twice because else there is no
583        chance to avoid reading the global options from ".cvsrc".  Set
584        opterr to 0 for avoiding error messages about invalid options.
585        */
586     opterr = 0;
587 
588     while ((c = getopt_long
589             (argc, argv, short_options, long_options, &option_index))
590            != EOF)
591     {
592 	if (c == 'f')
593 	    use_cvsrc = 0;
594     }
595 
596 #ifdef SERVER_SUPPORT
597     /* Don't try and read a .cvsrc file if we are a server.  */
598     if (optind < argc
599 	&& (false
600 # if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
601 	    || !strcmp (argv[optind], "pserver")
602 # endif
603 # ifdef HAVE_KERBEROS
604 	    || !strcmp (argv[optind], "kserver")
605 # endif /* HAVE_KERBEROS */
606 	    || !strcmp (argv[optind], "server")))
607 	{
608 	    /* Avoid any .cvsrc file.  */
609 	    use_cvsrc = 0;
610 	    /* Pre-parse the server options to get the config path.  */
611 	    cvs_cmd_name = argv[optind];
612 	    parseServerOptions (argc - optind, argv + optind);
613 	}
614 #endif /* SERVER_SUPPORT */
615 
616     /*
617      * Scan cvsrc file for global options.
618      */
619     if (use_cvsrc)
620 	read_cvsrc (&argc, &argv, "cvs");
621 
622     optind = 0;
623     opterr = 1;
624 
625     while ((c = getopt_long
626             (argc, argv, short_options, long_options, &option_index))
627            != EOF)
628     {
629 	switch (c)
630 	{
631             case 1:
632 	        /* --help-commands */
633                 usage (cmd_usage);
634                 break;
635             case 2:
636 	        /* --help-synonyms */
637                 usage (cmd_synonyms());
638                 break;
639 	    case 4:
640 		/* --help-options */
641 		usage (opt_usage);
642 		break;
643 #ifdef SERVER_SUPPORT
644 	    case 3:
645 		/* --allow-root */
646 		root_allow_add (optarg, gConfigPath);
647 		break;
648 #endif /* SERVER_SUPPORT */
649 	    case 'Q':
650 		really_quiet = 1;
651 		/* FALL THROUGH */
652 	    case 'q':
653 		quiet = 1;
654 		break;
655 	    case 'r':
656 		cvswrite = 0;
657 		break;
658 	    case 'w':
659 		cvswrite = 1;
660 		break;
661 	    case 't':
662 		trace++;
663 		break;
664 	    case 'R':
665 		readonlyfs = -1;
666 		logoff = 1;
667 		break;
668 	    case 'n':
669 		noexec = 1;
670 		logoff = 1;
671 		break;
672 	    case 'v':
673 		(void) fputs ("\n", stdout);
674 		version (0, NULL);
675 		(void) fputs ("\n", stdout);
676 		(void) fputs ("\
677 Copyright (C) 2005 Free Software Foundation, Inc.\n\
678 \n\
679 Senior active maintainers include Larry Jones, Derek R. Price,\n\
680 and Mark D. Baushke.  Please see the AUTHORS and README files from the CVS\n\
681 distribution kit for a complete list of contributors and copyrights.\n",
682 		              stdout);
683 		(void) fputs ("\n", stdout);
684 		(void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout);
685 		(void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout);
686 		(void) fputs ("\n", stdout);
687 
688 		(void) fputs ("Specify the --help option for further information about CVS\n", stdout);
689 
690 		exit (0);
691 		break;
692 	    case 'b':
693 		/* This option used to specify the directory for RCS
694 		   executables.  But since we don't run them any more,
695 		   this is a noop.  Silently ignore it so that .cvsrc
696 		   and scripts and inetd.conf and such can work with
697 		   either new or old CVS.  */
698 		break;
699 	    case 'T':
700 		if (tmpdir_cmdline) free (tmpdir_cmdline);
701 		tmpdir_cmdline = xstrdup (optarg);
702 		break;
703 	    case 'e':
704 		if (free_Editor) free (Editor);
705 		Editor = xstrdup (optarg);
706 		free_Editor = 1;
707 		break;
708 	    case 'd':
709 		if (CVSroot_cmdline != NULL)
710 		    free (CVSroot_cmdline);
711 		CVSroot_cmdline = xstrdup (optarg);
712 		break;
713 	    case 'H':
714 	        help = 1;
715 		break;
716             case 'f':
717 		use_cvsrc = 0; /* unnecessary, since we've done it above */
718 		break;
719 	    case 'z':
720 #ifdef CLIENT_SUPPORT
721 		gzip_level = strtol (optarg, &end, 10);
722 		if (*end != '\0' || gzip_level < 0 || gzip_level > 9)
723 		  error (1, 0,
724 			 "gzip compression level must be between 0 and 9");
725 #endif /* CLIENT_SUPPORT */
726 		/* If no CLIENT_SUPPORT, we just silently ignore the gzip
727 		 * level, so that users can have it in their .cvsrc and not
728 		 * cause any trouble.
729 		 *
730 		 * We still parse the argument to -z for correctness since
731 		 * one user complained of being bitten by a run of
732 		 * `cvs -z -n up' which read -n as the argument to -z without
733 		 * complaining.  */
734 		break;
735 	    case 's':
736 		variable_set (optarg);
737 		break;
738 	    case 'x':
739 #ifdef CLIENT_SUPPORT
740 	        cvsencrypt = 1;
741 #endif /* CLIENT_SUPPORT */
742 		/* If no CLIENT_SUPPORT, ignore -x, so that users can
743                    have it in their .cvsrc and not cause any trouble.
744                    If no ENCRYPTION, we still accept -x, but issue an
745                    error if we are being run as a client.  */
746 		break;
747 	    case 'a':
748 #ifdef CLIENT_SUPPORT
749 		cvsauthenticate = 1;
750 #endif
751 		/* If no CLIENT_SUPPORT, ignore -a, so that users can
752                    have it in their .cvsrc and not cause any trouble.
753                    We will issue an error later if stream
754                    authentication is not supported.  */
755 		break;
756 	    case '?':
757 	    default:
758                 usage (usg);
759 	}
760     }
761 
762     argc -= optind;
763     argv += optind;
764     if (argc < 1)
765 	usage (usg);
766 
767     if (readonlyfs && !really_quiet) {
768 	error (0, 0,
769 	       "WARNING: Read-only repository access mode selected via `cvs -R'.\n\
770 Using this option to access a repository which some users write to may\n\
771 cause intermittent sandbox corruption.");
772     }
773 
774     /* Calculate the cvs global session ID */
775 
776     {
777 	char buf[COMMITID_RAW_SIZE] = { 0, };
778 	char out[COMMITID_RAW_SIZE * 2];
779 	ssize_t len = 0;
780 	time_t rightnow = time (NULL);
781 	char *startrand = buf + sizeof (time_t);
782 	unsigned char *p = (unsigned char *) startrand;
783 	size_t randbytes = RANDOM_BYTES;
784 	int flags = O_RDONLY;
785 	int fd;
786 #ifdef O_NOCTTY
787 	flags |= O_NOCTTY;
788 #endif
789 	if (rightnow != (time_t)-1)
790 		while (rightnow > 0) {
791 		    *--p = rightnow % (UCHAR_MAX + 1);
792 		    rightnow /= UCHAR_MAX + 1;
793 		}
794 	else {
795 	    /* try to use more random data */
796 	    randbytes = COMMITID_RAW_SIZE;
797 	    startrand = buf;
798 	}
799 	fd = open ("/dev/urandom", flags);
800 	if (fd >= 0) {
801 	    len = read (fd, startrand, randbytes);
802 	    close (fd);
803 	}
804 	if (len <= 0) {
805 	    /* no random data was available so use pid */
806 	    long int pid = (long int)getpid ();
807 	    p = (unsigned char *) (startrand + sizeof (pid));
808 	    while (pid > 0) {
809 		*--p = pid % (UCHAR_MAX + 1);
810 		pid /= UCHAR_MAX + 1;
811 	    }
812 	}
813 	convert(buf, out);
814 	global_session_id = strdup (out);
815     }
816 
817 
818     TRACE (TRACE_FUNCTION, "main: Session ID is %s", global_session_id);
819 
820     /* Look up the command name. */
821 
822     cvs_cmd_name = argv[0];
823     for (cm = cmds; cm->fullname; cm++)
824     {
825 	if (cm->nick1 && !strcmp (cvs_cmd_name, cm->nick1))
826 	    break;
827 	if (cm->nick2 && !strcmp (cvs_cmd_name, cm->nick2))
828 	    break;
829 	if (!strcmp (cvs_cmd_name, cm->fullname))
830 	    break;
831     }
832 
833     if (!cm->fullname)
834     {
835 	fprintf (stderr, "Unknown command: `%s'\n\n", cvs_cmd_name);
836 	usage (cmd_usage);
837     }
838     else
839 	cvs_cmd_name = cm->fullname;	/* Global pointer for later use */
840 
841     if (help)
842     {
843 	argc = -1;		/* some functions only check for this */
844 	err = (*(cm->func)) (argc, argv);
845     }
846     else
847     {
848 	/* The user didn't ask for help, so go ahead and authenticate,
849            set up CVSROOT, and the rest of it. */
850 
851 	short int lock_cleanup_setup = 0;
852 
853 	/* The UMASK environment variable isn't handled with the
854 	   others above, since we don't want to signal errors if the
855 	   user has asked for help.  This won't work if somebody adds
856 	   a command-line flag to set the umask, since we'll have to
857 	   parse it before we get here. */
858 
859 	if ((cp = getenv (CVSUMASK_ENV)) != NULL)
860 	{
861 	    /* FIXME: Should be accepting symbolic as well as numeric mask.  */
862 	    cvsumask = strtol (cp, &end, 8) & 0777;
863 	    if (*end != '\0')
864 		error (1, errno, "invalid umask value in %s (%s)",
865 		       CVSUMASK_ENV, cp);
866 	}
867 
868 	/* HOSTNAME & SERVER_HOSTNAME need to be set before they are
869 	 * potentially used in gserver_authenticate_connection() (called from
870 	 * pserver_authenticate_connection, below).
871 	 */
872 	hostname = xgethostname ();
873 	if (!hostname)
874 	{
875             error (0, errno,
876                    "xgethostname () returned NULL, using \"localhost\"");
877             hostname = xstrdup ("localhost");
878 	}
879 
880 	/* Keep track of this separately since the client can change
881 	 * HOSTNAME on the server.
882 	 */
883 	server_hostname = xstrdup (hostname);
884 
885 #ifdef SERVER_SUPPORT
886 
887 # ifdef HAVE_KERBEROS
888 	/* If we are invoked with a single argument "kserver", then we are
889 	   running as Kerberos server as root.  Do the authentication as
890 	   the very first thing, to minimize the amount of time we are
891 	   running as root.  */
892 	if (strcmp (cvs_cmd_name, "kserver") == 0)
893 	{
894 	    kserver_authenticate_connection ();
895 
896 	    /* Pretend we were invoked as a plain server.  */
897 	    cvs_cmd_name = "server";
898 	}
899 # endif /* HAVE_KERBEROS */
900 
901 # if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
902 	if (strcmp (cvs_cmd_name, "pserver") == 0)
903 	{
904 	    /* The reason that --allow-root is not a command option
905 	       is mainly that it seems easier to make it a global option.  */
906 
907 	    /* Gets username and password from client, authenticates, then
908 	       switches to run as that user and sends an ACK back to the
909 	       client. */
910 	    pserver_authenticate_connection ();
911 
912 	    /* Pretend we were invoked as a plain server.  */
913 	    cvs_cmd_name = "server";
914 	}
915 # endif /* AUTH_SERVER_SUPPORT || HAVE_GSSAPI */
916 #endif /* SERVER_SUPPORT */
917 
918 	server_active = strcmp (cvs_cmd_name, "server") == 0;
919 
920 #ifdef SERVER_SUPPORT
921 	if (server_active)
922 	{
923 	    /* This is only used for writing into the history file.  For
924 	       remote connections, it might be nice to have hostname
925 	       and/or remote path, on the other hand I'm not sure whether
926 	       it is worth the trouble.  */
927 	    CurDir = xstrdup ("<remote>");
928 	    cleanup_register (server_cleanup);
929 	}
930 	else
931 #endif
932 	{
933 	    cleanup_register (close_stdout);
934 	    CurDir = xgetcwd ();
935             if (CurDir == NULL)
936 		error (1, errno, "cannot get working directory");
937 	}
938 
939 	{
940 	    char *val;
941 	    /* XXX pid < 10^32 */
942 	    val = Xasprintf ("%ld", (long) getpid ());
943 	    setenv (CVS_PID_ENV, val, 1);
944 	    free (val);
945 	}
946 
947 	/* make sure we clean up on error */
948 	signals_register (main_cleanup);
949 
950 #ifdef KLUDGE_FOR_WNT_TESTSUITE
951 	/* Probably the need for this will go away at some point once
952 	   we call fflush enough places (e.g. fflush (stdout) in
953 	   cvs_outerr).  */
954 	(void) setvbuf (stdout, NULL, _IONBF, 0);
955 	(void) setvbuf (stderr, NULL, _IONBF, 0);
956 #endif /* KLUDGE_FOR_WNT_TESTSUITE */
957 
958 	if (use_cvsrc)
959 	    read_cvsrc (&argc, &argv, cvs_cmd_name);
960 
961 	/* Fiddling with CVSROOT doesn't make sense if we're running
962 	 * in server mode, since the client will send the repository
963 	 * directory after the connection is made.
964 	 */
965 	if (!server_active)
966 	{
967 	    /* First check if a root was set via the command line.  */
968 	    if (CVSroot_cmdline)
969 	    {
970 		 if (!(CVSroot_parsed = parse_cvsroot (CVSroot_cmdline)))
971 		     error (1, 0, "Bad CVSROOT: `%s'.", CVSroot_cmdline);
972 	    }
973 
974 	    /* See if we are able to find a 'better' value for CVSroot
975 	     * in the CVSADM_ROOT directory.
976 	     *
977 	     * "cvs import" shouldn't check CVS/Root; in general it
978 	     * ignores CVS directories and CVS/Root is likely to
979 	     * specify a different repository than the one we are
980 	     * importing to, but if this is not import and no root was
981 	     * specified on the command line, set the root from the
982 	     * CVS/Root file.
983 	     */
984 	    if (!CVSroot_parsed
985 		&& !(cm->attr & CVS_CMD_IGNORE_ADMROOT)
986 	       )
987 		CVSroot_parsed = Name_Root (NULL, NULL);
988 
989 	    /* Now, if there is no root on the command line and we didn't find
990 	     * one in a file, set it via the $CVSROOT env var.
991 	     */
992 	    if (!CVSroot_parsed)
993 	    {
994 		char *tmp = getenv (CVSROOT_ENV);
995 		if (tmp)
996 		{
997 		    if (!(CVSroot_parsed = parse_cvsroot (tmp)))
998 			error (1, 0, "Bad CVSROOT: `%s'.", tmp);
999 		    cvsroot_update_env = false;
1000 		}
1001 	    }
1002 
1003 #ifdef CVSROOT_DFLT
1004 	    if (!CVSroot_parsed)
1005 	    {
1006 		if (!(CVSroot_parsed = parse_cvsroot (CVSROOT_DFLT)))
1007 		    error (1, 0, "Bad CVSROOT: `%s'.", CVSROOT_DFLT);
1008 	    }
1009 #endif /* CVSROOT_DFLT */
1010 
1011 	    /* Now we've reconciled CVSROOT from the command line, the
1012 	       CVS/Root file, and the environment variable.  Do the
1013 	       last sanity checks on the variable. */
1014 	    if (!CVSroot_parsed)
1015 	    {
1016 		error (0, 0,
1017 		       "No CVSROOT specified!  Please use the `-d' option");
1018 		error (1, 0,
1019 		       "or set the %s environment variable.", CVSROOT_ENV);
1020 	    }
1021 	}
1022 
1023 	/* Here begins the big loop over unique cvsroot values.  We
1024            need to call do_recursion once for each unique value found
1025            in CVS/Root.  Prime the list with the current value. */
1026 
1027 	/* Create the list. */
1028 	assert (root_directories == NULL);
1029 	root_directories = getlist ();
1030 
1031 	/* Prime it. */
1032 	if (CVSroot_parsed)
1033 	{
1034 	    Node *n;
1035 	    n = getnode ();
1036 	    n->type = NT_UNKNOWN;
1037 	    n->key = xstrdup (CVSroot_parsed->original);
1038 	    n->data = CVSroot_parsed;
1039 
1040 	    if (addnode (root_directories, n))
1041 		error (1, 0, "cannot add initial CVSROOT %s", n->key);
1042 	}
1043 
1044 	assert (current_parsed_root == NULL);
1045 
1046 	/* If we're running the server, we want to execute this main
1047 	   loop once and only once (we won't be serving multiple roots
1048 	   from this connection, so there's no need to do it more than
1049 	   once).  To get out of the loop, we perform a "break" at the
1050 	   end of things.  */
1051 
1052 	while (server_active ||
1053 	       walklist (root_directories, set_root_directory, NULL))
1054 	{
1055 	    /* Fiddling with CVSROOT doesn't make sense if we're running
1056 	       in server mode, since the client will send the repository
1057 	       directory after the connection is made. */
1058 
1059 	    if (!server_active)
1060 	    {
1061 		/* Now we're 100% sure that we have a valid CVSROOT
1062 		   variable.  Parse it to see if we're supposed to do
1063 		   remote accesses or use a special access method. */
1064 
1065 		TRACE (TRACE_FUNCTION,
1066 		       "main loop with CVSROOT=%s",
1067 		       current_parsed_root ? current_parsed_root->directory
1068 					   : "(null)");
1069 
1070 		/*
1071 		 * Check to see if the repository exists.
1072 		 */
1073 		if (!current_parsed_root->isremote)
1074 		{
1075 		    char *path;
1076 		    int save_errno;
1077 
1078 		    path = Xasprintf ("%s/%s", current_parsed_root->directory,
1079 				      CVSROOTADM);
1080 		    if (!isaccessible (path, R_OK | X_OK))
1081 		    {
1082 			save_errno = errno;
1083 			/* If this is "cvs init", the root need not exist yet.
1084 			 */
1085 			if (strcmp (cvs_cmd_name, "init"))
1086 			    error (1, save_errno, "%s", path);
1087 		    }
1088 		    free (path);
1089 		}
1090 
1091 		/* Update the CVSROOT environment variable.  */
1092 		if (cvsroot_update_env)
1093 		    setenv (CVSROOT_ENV, current_parsed_root->original, 1);
1094 	    }
1095 
1096 	    /* Parse the CVSROOT/config file, but only for local.  For the
1097 	       server, we parse it after we know $CVSROOT.  For the
1098 	       client, it doesn't get parsed at all, obviously.  The
1099 	       presence of the parse_config call here is not meant to
1100 	       predetermine whether CVSROOT/config overrides things from
1101 	       read_cvsrc and other such places or vice versa.  That sort
1102 	       of thing probably needs more thought.  */
1103 	    if (!server_active && !current_parsed_root->isremote)
1104 	    {
1105 		/* If there was an error parsing the config file, parse_config
1106 		   already printed an error.  We keep going.  Why?  Because
1107 		   if we didn't, then there would be no way to check in a new
1108 		   CVSROOT/config file to fix the broken one!  */
1109 		if (config) free_config (config);
1110 		config = parse_config (current_parsed_root->directory, NULL);
1111 
1112 		/* Can set TMPDIR in the environment if necessary now, since
1113 		 * if it was set in config, we now know it.
1114 		 */
1115 		push_env_temp_dir ();
1116 	    }
1117 
1118 #ifdef CLIENT_SUPPORT
1119 	    /* Need to check for current_parsed_root != NULL here since
1120 	     * we could still be in server mode before the server function
1121 	     * gets called below and sets the root
1122 	     */
1123 	    if (current_parsed_root != NULL && current_parsed_root->isremote)
1124 	    {
1125 		/* Create a new list for directory names that we've
1126 		   sent to the server. */
1127 		if (dirs_sent_to_server != NULL)
1128 		    dellist (&dirs_sent_to_server);
1129 		dirs_sent_to_server = getlist ();
1130 	    }
1131 #endif
1132 
1133 	    if (
1134 #ifdef SERVER_SUPPORT
1135 		/* Don't worry about lock_cleanup_setup when the server is
1136 		 * active since we can only go through this loop once in that
1137 		 * case anyhow.
1138 		 */
1139 		server_active ||
1140 #endif
1141 	        (
1142 #ifdef CLIENT_SUPPORT
1143 		 !current_parsed_root->isremote &&
1144 #endif
1145 		 !lock_cleanup_setup))
1146 	    {
1147 		/* Set up to clean up any locks we might create on exit.  */
1148 		cleanup_register (Lock_Cleanup);
1149 		lock_cleanup_setup = 1;
1150 	    }
1151 
1152 	    /* Call our worker function.  */
1153 	    err = (*(cm->func)) (argc, argv);
1154 
1155 	    /* Mark this root directory as done.  When the server is
1156                active, our list will be empty -- don't try and
1157                remove it from the list. */
1158 
1159 	    if (!server_active)
1160 	    {
1161 		Node *n = findnode (root_directories,
1162 				    original_parsed_root->original);
1163 		assert (n != NULL);
1164 		assert (n->data != NULL);
1165 		n->data = NULL;
1166 		current_parsed_root = NULL;
1167 	    }
1168 
1169 	    if (server_active)
1170 		break;
1171 	} /* end of loop for cvsroot values */
1172 
1173 	dellist (&root_directories);
1174     } /* end of stuff that gets done if the user DOESN'T ask for help */
1175 
1176     root_allow_free ();
1177 
1178     /* This is exit rather than return because apparently that keeps
1179        some tools which check for memory leaks happier.  */
1180     exit (err ? EXIT_FAILURE : 0);
1181 	/* Keep picky/stupid compilers (e.g. Visual C++ 5.0) happy.  */
1182 	return 0;
1183 }
1184 
1185 
1186 
1187 char *
1188 Make_Date (const char *rawdate)
1189 {
1190     struct timespec t;
1191 
1192     if (!get_date (&t, rawdate, NULL))
1193 	error (1, 0, "Can't parse date/time: `%s'", rawdate);
1194 
1195     /* Truncate nanoseconds.  */
1196     return date_from_time_t (t.tv_sec);
1197 }
1198 
1199 
1200 
1201 /* Parse a string of the form TAG[:DATE], where TAG could be the empty string.
1202  *
1203  * INPUTS
1204  *   input	The string to be parsed.
1205  *
1206  * OUTPUTS
1207  *   tag	The tag found, if any.  If TAG is the empty string, then leave
1208  *		this value unchanged.
1209  *   date	The date found, if any.  If DATE is the empty string or is
1210  *		missing, leave this value unchanged.
1211  *
1212  * NOTES
1213  *   If either TAG or DATE is replaced for output, the previous value is freed.
1214  *
1215  * ERRORS
1216  *   If either TAG or DATE cannot be parsed, then this function will exit with
1217  *   a fatal error message.
1218  *
1219  * RETURNS
1220  *   Nothing.
1221  */
1222 void
1223 parse_tagdate (char **tag, char **date, const char *input)
1224 {
1225     char *p;
1226 
1227     TRACE (TRACE_FUNCTION, "parse_tagdate (%s, %s, %s)",
1228 	   *tag ? *tag : "(null)", *date ? *date : "(null)",
1229 	   input);
1230 
1231     if ((p = strchr (input, ':')))
1232     {
1233 	/* Parse the tag.  */
1234 	if (p - input)
1235 	{
1236 	    /* The tag has > 0 length.  */
1237 	    if (*tag) free (*tag);
1238 	    *tag = xmalloc (p - input + 1);
1239 	    strncpy (*tag, input, p - input);
1240 	    (*tag)[p - input] = '\0';
1241 	}
1242 
1243 	/* Parse the date.  */
1244 	if (*++p)
1245 	{
1246 	    if (*date) free (*date);
1247 	    *date = Make_Date (p);
1248 	}
1249     }
1250     else if (strlen (input))
1251     {
1252 	/* The tag has > 0 length.  */
1253 	if (*tag) free (*tag);
1254 	*tag = xstrdup (input);
1255     }
1256 
1257     TRACE (TRACE_DATA, "parse_tagdate: got tag = `%s', date = `%s'",
1258 	   *tag ? *tag : "(null)", *date ? *date : "(null)");
1259 }
1260 
1261 
1262 
1263 /* Convert a time_t to an RCS format date.  This is mainly for the
1264    use of "cvs history", because the CVSROOT/history file contains
1265    time_t format dates; most parts of CVS will want to avoid using
1266    time_t's directly, and instead use RCS_datecmp, Make_Date, &c.
1267    Assuming that the time_t is in GMT (as it generally should be),
1268    then the result will be in GMT too.
1269 
1270    Returns a newly malloc'd string.  */
1271 
1272 char *
1273 date_from_time_t (time_t unixtime)
1274 {
1275     struct tm *ftm;
1276     char date[MAXDATELEN];
1277     char *ret;
1278 
1279     ftm = gmtime (&unixtime);
1280     if (ftm == NULL)
1281 	/* This is a system, like VMS, where the system clock is in local
1282 	   time.  Hopefully using localtime here matches the "zero timezone"
1283 	   hack I added to get_date (get_date of course being the relevant
1284 	   issue for Make_Date, and for history.c too I think).  */
1285 	ftm = localtime (&unixtime);
1286 
1287     (void) sprintf (date, DATEFORM,
1288 		    ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
1289 		    ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
1290 		    ftm->tm_min, ftm->tm_sec);
1291     ret = xstrdup (date);
1292     return ret;
1293 }
1294 
1295 
1296 
1297 /* Convert a date to RFC822/1123 format.  This is used in contexts like
1298    dates to send in the protocol; it should not vary based on locale or
1299    other such conventions for users.  We should have another routine which
1300    does that kind of thing.
1301 
1302    The SOURCE date is in our internal RCS format.  DEST should point to
1303    storage managed by the caller, at least MAXDATELEN characters.  */
1304 void
1305 date_to_internet (char *dest, const char *source)
1306 {
1307     struct tm date;
1308 
1309     date_to_tm (&date, source);
1310     tm_to_internet (dest, &date);
1311 }
1312 
1313 
1314 
1315 void
1316 date_to_tm (struct tm *dest, const char *source)
1317 {
1318     if (sscanf (source, SDATEFORM,
1319 		&dest->tm_year, &dest->tm_mon, &dest->tm_mday,
1320 		&dest->tm_hour, &dest->tm_min, &dest->tm_sec)
1321 	    != 6)
1322 	/* Is there a better way to handle errors here?  I made this
1323 	   non-fatal in case we are called from the code which can't
1324 	   deal with fatal errors.  */
1325 	error (0, 0, "internal error: bad date %s", source);
1326 
1327     if (dest->tm_year > 100)
1328 	dest->tm_year -= 1900;
1329 
1330     dest->tm_mon -= 1;
1331 }
1332 
1333 
1334 
1335 /* Convert a date to RFC822/1123 format.  This is used in contexts like
1336    dates to send in the protocol; it should not vary based on locale or
1337    other such conventions for users.  We should have another routine which
1338    does that kind of thing.
1339 
1340    The SOURCE date is a pointer to a struct tm.  DEST should point to
1341    storage managed by the caller, at least MAXDATELEN characters.  */
1342 void
1343 tm_to_internet (char *dest, const struct tm *source)
1344 {
1345     /* Just to reiterate, these strings are from RFC822 and do not vary
1346        according to locale.  */
1347     static const char *const month_names[] =
1348       {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
1349 	 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
1350 
1351     sprintf (dest, "%d %s %d %02d:%02d:%02d -0000", source->tm_mday,
1352 	     source->tm_mon < 0 || source->tm_mon > 11
1353                ? "???" : month_names[source->tm_mon],
1354 	     source->tm_year + 1900, source->tm_hour, source->tm_min,
1355              source->tm_sec);
1356 }
1357 
1358 
1359 
1360 /*
1361  * Format a date for the current locale.
1362  *
1363  * INPUT
1364  *   UNIXTIME	The UNIX seconds since the epoch.
1365  *
1366  * RETURNS
1367  *   If my_strftime() encounters an error, this function can return NULL.
1368  *
1369  *   Otherwise, returns a date string in ISO8601 format, e.g.:
1370  *
1371  *	2004-04-29 13:24:22 -0700
1372  *
1373  *   It is the responsibility of the caller to return of this string.
1374  */
1375 static char *
1376 format_time_t (time_t unixtime)
1377 {
1378     static char buf[sizeof ("yyyy-mm-dd HH:MM:SS -HHMM")];
1379     /* Convert to a time in the local time zone.  */
1380     struct tm ltm = *(localtime (&unixtime));
1381 
1382     if (!my_strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S %z", &ltm, 0, 0))
1383 	return NULL;
1384 
1385     return xstrdup (buf);
1386 }
1387 
1388 
1389 
1390 /* Like format_time_t(), but return time in UTC.
1391  */
1392 char *
1393 gmformat_time_t (time_t unixtime)
1394 {
1395     static char buf[sizeof ("yyyy-mm-dd HH:MM:SS -HHMM")];
1396     /* Convert to a time in the local time zone.  */
1397     struct tm ltm = *(gmtime (&unixtime));
1398 
1399     if (!my_strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S %z", &ltm, 0, 0))
1400 	return NULL;
1401 
1402     return xstrdup (buf);
1403 }
1404 
1405 
1406 
1407 /* Format a date in the local timezone using format_time_t() given a date from
1408  * an arbitrary timezone in a string.
1409  *
1410  * INPUT
1411  *   DATESTR	A string that looks like anything get_date() can parse, e.g.:
1412  *
1413  *                      2004-04-29 20:24:22
1414  *
1415  * ERRORS
1416  *   As get_date() & format_time_t().  Prints a warning if either provide
1417  *   error return values.  See RETURNS.
1418  *
1419  * RETURNS
1420  *   A freshly allocated string that is a copy of the input string if either
1421  *   get_date() or format_time_t() encounter an error and as format_time_t()
1422  *   otherwise.
1423  */
1424 char *
1425 format_date_alloc (char *datestr)
1426 {
1427     struct timespec t;
1428     char *buf;
1429 
1430     TRACE (TRACE_FUNCTION, "format_date (%s)", datestr);
1431 
1432     /* Convert the date string to seconds since the epoch. */
1433     if (!get_date (&t, datestr, NULL))
1434     {
1435 	error (0, 0, "Can't parse date/time: `%s'.", datestr);
1436 	goto as_is;
1437     }
1438 
1439     /* Get the time into a string, truncating any nanoseconds returned by
1440      * getdate.
1441      */
1442     if ((buf = format_time_t (t.tv_sec)) == NULL)
1443     {
1444 	error (0, 0, "Unable to reformat date `%s'.", datestr);
1445 	goto as_is;
1446     }
1447 
1448     return buf;
1449 
1450  as_is:
1451     return xstrdup (datestr);
1452 }
1453 
1454 
1455 
1456 void
1457 usage (register const char *const *cpp)
1458 {
1459     (void) fprintf (stderr, *cpp++, program_name, cvs_cmd_name);
1460     for (; *cpp; cpp++)
1461 	(void) fprintf (stderr, *cpp);
1462     exit (EXIT_FAILURE);
1463 }
1464 
1465 /* vim:tabstop=8:shiftwidth=4
1466  */
1467