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