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