summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src/main.c
blob: e7ffe56540746098b8fee35aaaf6ce112e1e526b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
/*
 *    Copyright (c) 1992, Brian Berliner and Jeff Polk
 *    Copyright (c) 1989-1992, Brian Berliner
 *
 *    You may distribute under the terms of the GNU General Public License
 *    as specified in the README file that comes with the CVS 1.4 kit.
 *
 * This is the main C driver for the CVS system.
 *
 * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing
 * the shell-script CVS system that this is based on.
 *
 * Usage:
 *	cvs [options] command [options] [files/modules...]
 *
 * Where "command" is composed of:
 *		admin		RCS command
 *		checkout	Check out a module/dir/file
 *		export		Like checkout, but used for exporting sources
 *		update		Brings work tree in sync with repository
 *		commit		Checks files into the repository
 *		diff		Runs diffs between revisions
 *		log		Prints "rlog" information for files
 *		login		Record user, host, repos, password
 *		add		Adds an entry to the repository
 *		remove		Removes an entry from the repository
 *		status		Status info on the revisions
 *		rdiff		"patch" format diff listing between releases
 *		tag		Add/delete a symbolic tag to the RCS file
 *		rtag		Add/delete a symbolic tag to the RCS file
 *		import		Import sources into CVS, using vendor branches
 *		release		Indicate that Module is no longer in use.
 *		history		Display history of Users and Modules.
 */

#include "cvs.h"

#ifdef HAVE_WINSOCK_H
#include <winsock.h>
#else
extern int gethostname ();
#endif

#if HAVE_KERBEROS
#include <sys/socket.h>
#include <netinet/in.h>
#include <krb.h>
#ifndef HAVE_KRB_GET_ERR_TEXT
#define krb_get_err_text(status) krb_err_txt[status]
#endif
#endif

char *program_name;
char *program_path;
/*
 * Initialize comamnd_name to "cvs" so that the first call to
 * read_cvsrc tries to find global cvs options.
 */
char *command_name = "";

/*
 * Since some systems don't define this...
 */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN  256
#endif

char hostname[MAXHOSTNAMELEN];

#ifdef AUTH_CLIENT_SUPPORT
int use_authenticating_server = FALSE;
#endif /* AUTH_CLIENT_SUPPORT */
int use_editor = TRUE;
int use_cvsrc = TRUE;
int cvswrite = !CVSREAD_DFLT;
int really_quiet = FALSE;
int quiet = FALSE;
int trace = FALSE;
int noexec = FALSE;
int logoff = FALSE;
mode_t cvsumask = UMASK_DFLT;

char *CurDir;

/*
 * Defaults, for the environment variables that are not set
 */
char *Rcsbin = RCSBIN_DFLT;
char *Editor = EDITOR_DFLT;
char *CVSroot = CVSROOT_DFLT;
/*
 * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root'
 */
char *CVSADM_Root = CVSROOT_DFLT;

int add PROTO((int argc, char **argv));
int admin PROTO((int argc, char **argv));
int checkout PROTO((int argc, char **argv));
int commit PROTO((int argc, char **argv));
int diff PROTO((int argc, char **argv));
int history PROTO((int argc, char **argv));
int import PROTO((int argc, char **argv));
int cvslog PROTO((int argc, char **argv));
#ifdef AUTH_CLIENT_SUPPORT
int login PROTO((int argc, char **argv));
#endif /* AUTH_CLIENT_SUPPORT */
int patch PROTO((int argc, char **argv));
int release PROTO((int argc, char **argv));
int cvsremove PROTO((int argc, char **argv));
int rtag PROTO((int argc, char **argv));
int status PROTO((int argc, char **argv));
int tag PROTO((int argc, char **argv));
int update PROTO((int argc, char **argv));

const struct cmd
{
    char *fullname;		/* Full name of the function (e.g. "commit") */
    char *nick1;		/* alternate name (e.g. "ci") */
    char *nick2;		/* another alternate names (e.g. "ci") */
    int (*func) ();		/* Function takes (argc, argv) arguments. */
#ifdef CLIENT_SUPPORT
    int (*client_func) ();	/* Function to do it via the protocol.  */
#endif
} cmds[] =

{
#ifdef CLIENT_SUPPORT
#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1, f2 }
#else
#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1 }
#endif

    CMD_ENTRY("add",      "ad",    "new",     add,       client_add),
    CMD_ENTRY("admin",    "adm",   "rcs",     admin,     client_admin),
    CMD_ENTRY("annotate", NULL,    NULL,      annotate,  client_annotate),
    CMD_ENTRY("checkout", "co",    "get",     checkout,  client_checkout),
    CMD_ENTRY("commit",   "ci",    "com",     commit,    client_commit),
    CMD_ENTRY("diff",     "di",    "dif",     diff,      client_diff),
    CMD_ENTRY("edit",     "edit",  "edit",    edit,      client_edit),
    CMD_ENTRY("editors",  "editors","editors",editors,   client_editors),
    CMD_ENTRY("export",   "exp",   "ex",      checkout,  client_export),
    CMD_ENTRY("history",  "hi",    "his",     history,   client_history),
    CMD_ENTRY("import",   "im",    "imp",     import,    client_import),
    CMD_ENTRY("init",     NULL,    NULL,      init,      client_init),
    CMD_ENTRY("log",      "lo",    "rlog",    cvslog,    client_log),
#ifdef AUTH_CLIENT_SUPPORT
    CMD_ENTRY("login",    "logon", "lgn",     login,     login),
#endif /* AUTH_CLIENT_SUPPORT */
    CMD_ENTRY("rdiff",    "patch", "pa",      patch,     client_rdiff),
    CMD_ENTRY("release",  "re",    "rel",     release,   client_release),
    CMD_ENTRY("remove",   "rm",    "delete",  cvsremove, client_remove),
    CMD_ENTRY("status",   "st",    "stat",    status,    client_status),
    CMD_ENTRY("rtag",     "rt",    "rfreeze", rtag,      client_rtag),
    CMD_ENTRY("tag",      "ta",    "freeze",  tag,       client_tag),
    CMD_ENTRY("unedit",   "unedit","unedit",  unedit,    client_unedit),
    CMD_ENTRY("update",   "up",    "upd",     update,    client_update),
    CMD_ENTRY("watch",    "watch", "watch",   watch,     client_watch),
    CMD_ENTRY("watchers", "watchers","watchers",watchers,client_watchers),
#ifdef SERVER_SUPPORT
    /*
     * The client_func is also server because we might have picked up a
     * CVSROOT environment variable containing a colon.  The client will send
     * the real root later.
     */
    CMD_ENTRY("server",   "server", "server", server,    server),
#endif
    CMD_ENTRY(NULL, NULL, NULL, NULL, NULL),

#undef CMD_ENTRY
};

static const char *const usg[] =
{
    "Usage: %s [cvs-options] command [command-options] [files...]\n",
    "    Where 'cvs-options' are:\n",
    "        -H           Displays Usage information for command\n",
    "        -Q           Cause CVS to be really quiet.\n",
    "        -q           Cause CVS to be somewhat quiet.\n",
    "        -r           Make checked-out files read-only\n",
    "        -w           Make checked-out files read-write (default)\n",
    "        -l           Turn History logging off\n",
    "        -n           Do not execute anything that will change the disk\n",
    "        -t           Show trace of program execution -- Try with -n\n",
    "        -v           CVS version and copyright\n",
    "        -b bindir    Find RCS programs in 'bindir'\n",
    "        -e editor    Use 'editor' for editing log information\n",
    "        -d CVS_root  Overrides $CVSROOT as the root of the CVS tree\n",
    "        -f           Do not use the ~/.cvsrc file\n",
#ifdef CLIENT_SUPPORT
    "        -z #         Use 'gzip -#' for net traffic if possible.\n",
#endif
    "        -s VAR=VAL   Set CVS user variable.\n",
    "\n",
    "    and where 'command' is: add, admin, etc. (use the --help-commands\n",
    "    option for a list of commands)\n",
    NULL,
};

static const char *const cmd_usage[] =
{
    "CVS commands are:\n",
    "        add          Adds a new file/directory to the repository\n",
    "        admin        Administration front end for rcs\n",
    "        annotate     Show revision where each line was modified\n",
    "        checkout     Checkout sources for editing\n",
    "        commit       Checks files into the repository\n",
    "        diff         Runs diffs between revisions\n",
    "        edit         Get ready to edit a watched file\n",
    "        editors      See who is editing a watched file\n",
    "        history      Shows status of files and users\n",
    "        import       Import sources into CVS, using vendor branches\n",
    "        export       Export sources from CVS, similar to checkout\n",
    "        init         Initialize a new CVS repository\n",
    "        log          Prints out 'rlog' information for files\n",
#ifdef AUTH_CLIENT_SUPPORT
    "        login        Prompt for password for authenticating server.\n",
#endif /* AUTH_CLIENT_SUPPORT */
    "        rdiff        'patch' format diffs between releases\n",
    "        release      Indicate that a Module is no longer in use\n",
    "        remove       Removes an entry from the repository\n",
    "        status       Status info on the revisions\n",
    "        tag          Add a symbolic tag to checked out version of RCS file\n",
    "        unedit       Undo an edit command\n",
    "        rtag         Add a symbolic tag to the RCS file\n",
    "        update       Brings work tree in sync with repository\n",
    "        watch        Set watches\n",
    "        watchers     See who is watching a file\n",
    NULL,
};

static RETSIGTYPE
main_cleanup ()
{
    exit (EXIT_FAILURE);
}

static void
error_cleanup PROTO((void))
{
    Lock_Cleanup();
#ifdef SERVER_SUPPORT
    if (server_active)
	server_cleanup (0);
#endif
}

int
main (argc, argv)
    int argc;
    char **argv;
{
    extern char *version_string;
    extern char *config_string;
    char *cp, *end;
    const struct cmd *cm;
    int c, err = 0;
    static int help = FALSE;
    static int version_flag = FALSE;
    static int help_commands = FALSE;
    int rcsbin_update_env, cvs_update_env = 0;
    static struct option long_options[] =
      {
        {"help", 0, &help, TRUE},
        {"version", 0, &version_flag, TRUE},
	{"help-commands", 0, &help_commands, TRUE},
        {0, 0, 0, 0}
      };
    /* `getopt_long' stores the option index here, but right now we
        don't use it. */
    int option_index = 0;

    error_set_cleanup (error_cleanup);

/* The socket subsystems on NT and OS2 must be initialized before use */
#ifdef INITIALIZE_SOCKET_SUBSYSTEM
        INITIALIZE_SOCKET_SUBSYSTEM();
#endif /* INITIALIZE_SOCKET_SUBSYSTEM */

    /*
     * Just save the last component of the path for error messages
     */
    program_path = xstrdup (argv[0]);
    program_name = last_component (argv[0]);

    CurDir = xmalloc (PATH_MAX);
#ifndef SERVER_SUPPORT
    if (!getwd (CurDir))
	error (1, 0, "cannot get working directory: %s", CurDir);
#endif

    /*
     * Query the environment variables up-front, so that
     * they can be overridden by command line arguments
     */
    rcsbin_update_env = *Rcsbin;	/* RCSBIN_DFLT must be set */
    cvs_update_env = 0;
    if ((cp = getenv (RCSBIN_ENV)) != NULL)
    {
	Rcsbin = cp;
	rcsbin_update_env = 0;		/* it's already there */
    }
    if ((cp = getenv (EDITOR1_ENV)) != NULL)
 	Editor = cp;
    else if ((cp = getenv (EDITOR2_ENV)) != NULL)
	Editor = cp;
    else if ((cp = getenv (EDITOR3_ENV)) != NULL)
	Editor = cp;
    if ((cp = getenv (CVSROOT_ENV)) != NULL)
    {
	CVSroot = cp;
	cvs_update_env = 0;		/* it's already there */
    }
    if (getenv (CVSREAD_ENV) != NULL)
	cvswrite = FALSE;
    if ((cp = getenv (CVSUMASK_ENV)) != NULL)
    {
	/* FIXME: Should be accepting symbolic as well as numeric mask.  */
	cvsumask = strtol (cp, &end, 8) & 0777;
	if (*end != '\0')
	    error (1, errno, "invalid umask value in %s (%s)",
		CVSUMASK_ENV, cp);
    }

    /* This has the effect of setting getopt's ordering to REQUIRE_ORDER,
       which is what we need to distinguish between global options and
       command options.  FIXME: It would appear to be possible to do this
       much less kludgily by passing "+" as the first character to the
       option string we pass to getopt_long.  */
    optind = 1;


    /* We have to parse the options twice because else there is no
       chance to avoid reading the global options from ".cvsrc".  Set
       opterr to 0 for avoiding error messages about invalid options.
       */
    opterr = 0;

    while ((c = getopt_long
            (argc, argv, "f", NULL, NULL))
           != EOF)
      {
	if (c == 'f')
	    use_cvsrc = FALSE;
      }
    
    /*
     * Scan cvsrc file for global options.
     */
    if (use_cvsrc)
	read_cvsrc (&argc, &argv, "cvs");

    optind = 1;
    opterr = 1;

    while ((c = getopt_long
            (argc, argv, "Qqrwtnlvb:e:d:Hfz:s:", long_options, &option_index))
           != EOF)
      {
	switch (c)
          {
            case 0:
                /* getopt_long took care of setting the flag. */ 
                break;
	    case 'Q':
		really_quiet = TRUE;
		/* FALL THROUGH */
	    case 'q':
		quiet = TRUE;
		break;
	    case 'r':
		cvswrite = FALSE;
		break;
	    case 'w':
		cvswrite = TRUE;
		break;
	    case 't':
		trace = TRUE;
		break;
	    case 'n':
		noexec = TRUE;
	    case 'l':			/* Fall through */
		logoff = TRUE;
		break;
	    case 'v':
                version_flag = TRUE;
		break;
	    case 'b':
		Rcsbin = optarg;
		rcsbin_update_env = 1;	/* need to update environment */
		break;
	    case 'e':
		Editor = optarg;
		break;
	    case 'd':
		CVSroot = optarg;
		cvs_update_env = 1;	/* need to update environment */
		break;
	    case 'H':
		use_cvsrc = FALSE;      /* this ensure that cvs -H works */
		help = TRUE;
		break;
            case 'f':
		use_cvsrc = FALSE;
		break;
	    case 'z':
#ifdef CLIENT_SUPPORT
		gzip_level = atoi (optarg);
		if (gzip_level <= 0 || gzip_level > 9)
		  error (1, 0,
			 "gzip compression level must be between 1 and 9");
#endif
		/* If no CLIENT_SUPPORT, we just silently ignore the gzip
		   level, so that users can have it in their .cvsrc and not
		   cause any trouble.  */
		break;
	    case 's':
		variable_set (optarg);
		break;
	    case '?':
	    default:
                usage (usg);
	}
    }

    if (version_flag == TRUE)
    {
        (void) fputs (version_string, stdout);
        (void) fputs (config_string, stdout);
        (void) fputs ("\n", stdout);
        (void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout);
        (void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout);
        (void) fputs ("Copyright (c) 1992, Brian Berliner and Jeff Polk\n", stdout);
        (void) fputs ("Copyright (c) 1989-1992, Brian Berliner\n", stdout);
        (void) fputs ("\n", stdout);
        (void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout);
        (void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout);
        exit (0);
    }
    else if (help_commands)
	usage (cmd_usage);

    argc -= optind;
    argv += optind;
    if (argc < 1)
	usage (usg);

#ifdef HAVE_KERBEROS
    /* If we are invoked with a single argument "kserver", then we are
       running as Kerberos server as root.  Do the authentication as
       the very first thing, to minimize the amount of time we are
       running as root.  */
    if (strcmp (argv[0], "kserver") == 0)
    {
	int status;
	char instance[INST_SZ];
	struct sockaddr_in peer;
	struct sockaddr_in laddr;
	int len;
	KTEXT_ST ticket;
	AUTH_DAT auth;
	char version[KRB_SENDAUTH_VLEN];
	Key_schedule sched;
	char user[ANAME_SZ];
	struct passwd *pw;

	strcpy (instance, "*");
	len = sizeof peer;
	if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0
	    || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr,
			    &len) < 0)
	{
	    printf ("E Fatal error, aborting.\n\
error %s getpeername or getsockname failed\n", strerror (errno));
	    exit (EXIT_FAILURE);
	}

	status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd",
			       instance, &peer, &laddr, &auth, "", sched,
			       version);
	if (status != KSUCCESS)
	{
	    printf ("E Fatal error, aborting.\n\
error 0 kerberos: %s\n", krb_get_err_text(status));
	    exit (EXIT_FAILURE);
	}

	/* Get the local name.  */
	status = krb_kntoln (&auth, user);
	if (status != KSUCCESS)
	{
	    printf ("E Fatal error, aborting.\n\
error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status));
	    exit (EXIT_FAILURE);
	}

	pw = getpwnam (user);
	if (pw == NULL)
	{
	    printf ("E Fatal error, aborting.\n\
error 0 %s: no such user\n", user);
	    exit (EXIT_FAILURE);
	}

	initgroups (pw->pw_name, pw->pw_gid);
	setgid (pw->pw_gid);
	setuid (pw->pw_uid);
	/* Inhibit access by randoms.  Don't want people randomly
	   changing our temporary tree before we check things in.  */
	umask (077);

#if HAVE_PUTENV
	/* Set LOGNAME and USER in the environment, in case they are
           already set to something else.  */
	{
	    char *env;

	    env = xmalloc (sizeof "LOGNAME=" + strlen (user));
	    (void) sprintf (env, "LOGNAME=%s", user);
	    (void) putenv (env);

	    env = xmalloc (sizeof "USER=" + strlen (user));
	    (void) sprintf (env, "USER=%s", user);
	    (void) putenv (env);
	}
#endif

	/* Pretend we were invoked as a plain server.  */
	argv[0] = "server";
    }
#endif /* HAVE_KERBEROS */


#if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT)
    if (strcmp (argv[0], "pserver") == 0)
    {
      /* Gets username and password from client, authenticates, then
         switches to run as that user and sends an ACK back to the
         client. */
      authenticate_connection ();
      
      /* Pretend we were invoked as a plain server.  */
      argv[0] = "server";
    }
#endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */


    /*
     * See if we are able to find a 'better' value for CVSroot in the
     * CVSADM_ROOT directory.
     */
#ifdef SERVER_SUPPORT
    if (strcmp (argv[0], "server") == 0 && CVSroot == NULL)
        CVSADM_Root = NULL;
    else
        CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
#else /* No SERVER_SUPPORT */
    CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
#endif /* No SERVER_SUPPORT */
    if (CVSADM_Root != NULL)
    {
        if (CVSroot == NULL || !cvs_update_env)
        {
	    CVSroot = CVSADM_Root;
	    cvs_update_env = 1;	/* need to update environment */
        }
#ifdef CLIENT_SUPPORT
        else if (!getenv ("CVS_IGNORE_REMOTE_ROOT"))
#else /* ! CLIENT_SUPPORT */
        else
#endif /* CLIENT_SUPPORT */
        {
            /*
	     * Now for the hard part, compare the two directories. If they
	     * are not identical, then abort this command.
	     */
            if ((fncmp (CVSroot, CVSADM_Root) != 0) &&
		!same_directories(CVSroot, CVSADM_Root))
	    {
              error (0, 0, "%s value for CVS Root found in %s",
                     CVSADM_Root, CVSADM_ROOT);
              error (0, 0, "does not match command line -d %s setting",
                     CVSroot);
              error (1, 0,
                      "you may wish to try the cvs command again without the -d option ");
	    }
        }
    }

    /* CVSroot may need fixing up, if an access-method was specified,
     * but not a user.  Later code assumes that if CVSroot contains an
     * access-method, then it also has a user.  We print a warning and
     * die if we can't guarantee that.
     */
    if (CVSroot
        && *CVSroot
        && (CVSroot[0] == ':')
        && (strchr (CVSroot, '@') == NULL))
      {
        error (1, 0,
               "must also give a username if specifying access method");
      }

    /*
     * Specifying just the '-H' flag to the sub-command causes a Usage
     * message to be displayed.
     */
    command_name = cp = argv[0];
    if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0))
	argc = -1;
    else
    {
	/*
	 * Check to see if we can write into the history file.  If not,
	 * we assume that we can't work in the repository.
	 * BUT, only if the history file exists.
	 */
#ifdef SERVER_SUPPORT
        if (strcmp (command_name, "server") != 0 || CVSroot != NULL)
#endif
	{
	    char path[PATH_MAX];
	    int save_errno;

	    if (!CVSroot || !*CVSroot)
		error (1, 0, "You don't have a %s environment variable",
		       CVSROOT_ENV);
	    (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM);
	    if (!isaccessible (path, R_OK | X_OK))
	    {
		save_errno = errno;
		/* If this is "cvs init", the root need not exist yet.  */
		if (strcmp (command_name, "init") != 0
#ifdef CLIENT_SUPPORT
		    /* If we are a remote client, the root need not exist
		       on the client machine (FIXME: we should also skip
		       the check for CVSROOTADM_HISTORY being writable;
		       it shouldn't matter if there is a read-only file
		       which happens to have the same name on the client
		       machine).  */
		    && strchr (CVSroot, ':') == NULL)
#endif
		{
		error (0, 0,
		    "Sorry, you don't have sufficient access to %s", CVSroot);
		error (1, save_errno, "%s", path);
		}
	    }
	    (void) strcat (path, "/");
	    (void) strcat (path, CVSROOTADM_HISTORY);
	    if (isfile (path) && !isaccessible (path, R_OK | W_OK))
	    {
		save_errno = errno;
		error (0, 0,
		 "Sorry, you don't have read/write access to the history file");
		error (1, save_errno, "%s", path);
	    }
	}
    }

#ifdef SERVER_SUPPORT
    if (strcmp (command_name, "server") == 0)
	/* This is only used for writing into the history file.  Might
	   be nice to have hostname and/or remote path, on the other hand
	   I'm not sure whether it is worth the trouble.  */
	strcpy (CurDir, "<remote>");
    else if (!getwd (CurDir))
	error (1, 0, "cannot get working directory: %s", CurDir);
#endif

#ifdef HAVE_PUTENV
    /* Now, see if we should update the environment with the Rcsbin value */
    if (cvs_update_env)
    {
	char *env;

	env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
	(void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot);
	(void) putenv (env);
	/* do not free env, as putenv has control of it */
    }
    if (rcsbin_update_env)
    {
	char *env;

	env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1);
	(void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin);
	(void) putenv (env);
	/* do not free env, as putenv has control of it */
    }
#endif

    /*
     * If Rcsbin is set to something, make sure it is terminated with
     * a slash character.  If not, add one.
     */
    if (*Rcsbin)
    {
	int len = strlen (Rcsbin);
	char *rcsbin;

	if (Rcsbin[len - 1] != '/')
	{
	    rcsbin = Rcsbin;
	    Rcsbin = xmalloc (len + 2);	/* one for '/', one for NULL */
	    (void) strcpy (Rcsbin, rcsbin);
	    (void) strcat (Rcsbin, "/");
	}
    }

    for (cm = cmds; cm->fullname; cm++)
    {
	if (cm->nick1 && !strcmp (cp, cm->nick1))
	    break;
	if (cm->nick2 && !strcmp (cp, cm->nick2))
	    break;
	if (!strcmp (cp, cm->fullname))
	    break;
    }

    if (!cm->fullname)
	usage (usg);			/* no match */
    else
    {
	command_name = cm->fullname;	/* Global pointer for later use */

	/* make sure we clean up on error */
#ifdef SIGHUP
	(void) SIG_register (SIGHUP, main_cleanup);
	(void) SIG_register (SIGHUP, Lock_Cleanup);
#endif
#ifdef SIGINT
	(void) SIG_register (SIGINT, main_cleanup);
	(void) SIG_register (SIGINT, Lock_Cleanup);
#endif
#ifdef SIGQUIT
	(void) SIG_register (SIGQUIT, main_cleanup);
	(void) SIG_register (SIGQUIT, Lock_Cleanup);
#endif
#ifdef SIGPIPE
	(void) SIG_register (SIGPIPE, main_cleanup);
	(void) SIG_register (SIGPIPE, Lock_Cleanup);
#endif
#ifdef SIGTERM
	(void) SIG_register (SIGTERM, main_cleanup);
	(void) SIG_register (SIGTERM, Lock_Cleanup);
#endif

	gethostname(hostname, sizeof (hostname));

#ifdef HAVE_SETVBUF
	/*
	 * Make stdout line buffered, so 'tail -f' can monitor progress.
	 * Patch creates too much output to monitor and it runs slowly.
	 */
	if (strcmp (cm->fullname, "patch"))
	    (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0);
#endif

	if (use_cvsrc)
	  read_cvsrc (&argc, &argv, command_name);

#ifdef CLIENT_SUPPORT
	/* If cvsroot contains a colon, try to do it via the protocol.  */
        {
	    char *p = CVSroot == NULL ? NULL : strchr (CVSroot, ':');
	    if (p)
		err = (*(cm->client_func)) (argc, argv);
	    else
		err = (*(cm->func)) (argc, argv);
	}
#else /* No CLIENT_SUPPORT */
	err = (*(cm->func)) (argc, argv);

#endif /* No CLIENT_SUPPORT */
    }
    Lock_Cleanup ();
    if (err)
	return (EXIT_FAILURE);
    return 0;
}

char *
Make_Date (rawdate)
    char *rawdate;
{
    struct tm *ftm;
    time_t unixtime;
    char date[256];			/* XXX bigger than we'll ever need? */
    char *ret;

    unixtime = get_date (rawdate, (struct timeb *) NULL);
    if (unixtime == (time_t) - 1)
	error (1, 0, "Can't parse date/time: %s", rawdate);
#ifdef HAVE_RCS5
    ftm = gmtime (&unixtime);
#else
    ftm = localtime (&unixtime);
#endif
    (void) sprintf (date, DATEFORM,
		    ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
		    ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
		    ftm->tm_min, ftm->tm_sec);
    ret = xstrdup (date);
    return (ret);
}

void
usage (cpp)
    register const char *const *cpp;
{
    (void) fprintf (stderr, *cpp++, program_name, command_name);
    for (; *cpp; cpp++)
	(void) fprintf (stderr, *cpp);
    exit (EXIT_FAILURE);
}
OpenPOWER on IntegriCloud