xref: /netbsd-src/external/gpl2/xcvs/dist/src/add.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
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 as
11  * specified in the README file that comes with the CVS source distribution.
12  *
13  * Add
14  *
15  * Adds a file or directory to the RCS source repository.  For a file,
16  * the entry is marked as "needing to be added" in the user's own CVS
17  * directory, and really added to the repository when it is committed.
18  * For a directory, it is added at the appropriate place in the source
19  * repository and a CVS directory is generated within the directory.
20  *
21  * `cvs add' supports `-k <mode>' and `-m <description>' options.
22  * Some may wish to supply other standard "rcs" options here, but I've
23  * found that this causes more trouble than anything else.
24  *
25  * The user files or directories must already exist.  For a directory, it must
26  * not already have a CVS file in it.
27  *
28  * An "add" on a file that has been "remove"d but not committed will cause the
29  * file to be resurrected.
30  */
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: add.c,v 1.4 2016/05/17 14:00:09 christos Exp $");
33 
34 #include <assert.h>
35 #include "cvs.h"
36 #include "save-cwd.h"
37 #include "fileattr.h"
38 
39 static int add_directory (struct file_info *finfo);
40 static int build_entry (const char *repository, const char *user,
41                         const char *options, const char *message,
42                         List * entries, const char *tag);
43 
44 static const char *const add_usage[] =
45 {
46     "Usage: %s %s [-k rcs-kflag] [-m message] files...\n",
47     "\t-k rcs-kflag\tUse \"rcs-kflag\" to add the file with the specified\n",
48     "\t\t\tkflag.\n",
49     "\t-m message\tUse \"message\" for the creation log.\n",
50     "(Specify the --help global option for a list of other help options)\n",
51     NULL
52 };
53 
54 int
add(int argc,char ** argv)55 add (int argc, char **argv)
56 {
57     char *message = NULL;
58     int i;
59     char *repository;
60     int c;
61     int err = 0;
62     int added_files = 0;
63     char *options = NULL;
64     List *entries;
65     Vers_TS *vers;
66     struct saved_cwd cwd;
67     /* Nonzero if we found a slash, and are thus adding files in a
68        subdirectory.  */
69     int found_slash = 0;
70     size_t cvsroot_len;
71 
72     if (argc == 1 || argc == -1)
73 	usage (add_usage);
74 
75     wrap_setup ();
76 
77     /* parse args */
78     getoptreset ();
79     while ((c = getopt (argc, argv, "+k:m:")) != -1)
80     {
81 	switch (c)
82 	{
83 	    case 'k':
84 		if (options) free (options);
85 		options = RCS_check_kflag (optarg);
86 		break;
87 
88 	    case 'm':
89 		if (message) free (message);
90 		message = xstrdup (optarg);
91 		break;
92 	    case '?':
93 	    default:
94 		usage (add_usage);
95 		break;
96 	}
97     }
98     argc -= optind;
99     argv += optind;
100 
101     if (argc <= 0)
102 	usage (add_usage);
103 
104     cvsroot_len = strlen (current_parsed_root->directory);
105 
106     /* First some sanity checks.  I know that the CVS case is (sort of)
107        also handled by add_directory, but we need to check here so the
108        client won't get all confused in send_file_names.  */
109     for (i = 0; i < argc; i++)
110     {
111 	int skip_file = 0;
112 
113 	/* If it were up to me I'd probably make this a fatal error.
114 	   But some people are really fond of their "cvs add *", and
115 	   don't seem to object to the warnings.
116 	   Whatever.  */
117 	strip_trailing_slashes (argv[i]);
118 	if (strcmp (argv[i], ".") == 0
119 	    || strcmp (argv[i], "..") == 0
120 	    || fncmp (argv[i], CVSADM) == 0)
121 	{
122 	    if (!quiet)
123 		error (0, 0, "cannot add special file `%s'; skipping", argv[i]);
124 	    skip_file = 1;
125 	}
126 	else
127 	{
128 	    char *p;
129 	    p = argv[i];
130 	    while (*p != '\0')
131 	    {
132 		if (ISSLASH (*p))
133 		{
134 		    found_slash = 1;
135 		    break;
136 		}
137 		++p;
138 	    }
139 	}
140 
141 	if (skip_file)
142 	{
143 	    int j;
144 
145 	    /* FIXME: We don't do anything about free'ing argv[i].  But
146 	       the problem is that it is only sometimes allocated (see
147 	       cvsrc.c).  */
148 
149 	    for (j = i; j < argc - 1; ++j)
150 		argv[j] = argv[j + 1];
151 	    --argc;
152 	    /* Check the new argv[i] again.  */
153 	    --i;
154 	    ++err;
155 	}
156     }
157 
158 #ifdef CLIENT_SUPPORT
159     if (current_parsed_root->isremote)
160     {
161 	int j;
162 
163 	if (argc == 0)
164 	    /* We snipped out all the arguments in the above sanity
165 	       check.  We can just forget the whole thing (and we
166 	       better, because if we fired up the server and passed it
167 	       nothing, it would spit back a usage message).  */
168 	    return err;
169 
170 	start_server ();
171 	ign_setup ();
172 	if (options)
173 	{
174 	    send_arg (options);
175 	    free (options);
176 	}
177 	option_with_arg ("-m", message);
178 	send_arg ("--");
179 
180 	/* If !found_slash, refrain from sending "Directory", for
181 	   CVS 1.9 compatibility.  If we only tried to deal with servers
182 	   which are at least CVS 1.9.26 or so, we wouldn't have to
183 	   special-case this.  */
184 	if (found_slash)
185 	{
186 	    repository = Name_Repository (NULL, NULL);
187 	    send_a_repository ("", repository, "");
188 	    free (repository);
189 	}
190 
191 	for (j = 0; j < argc; ++j)
192 	{
193 	    /* FIXME: Does this erroneously call Create_Admin in error
194 	       conditions which are only detected once the server gets its
195 	       hands on things?  */
196 	    if (isdir (argv[j]))
197 	    {
198 		char *tag;
199 		char *date;
200 		int nonbranch;
201 		char *rcsdir;
202 		char *p;
203 		char *update_dir;
204 		/* This is some mungeable storage into which we can point
205 		   with p and/or update_dir.  */
206 		char *filedir;
207 
208 		if (save_cwd (&cwd))
209 		    error (1, errno, "Failed to save current directory.");
210 
211 		filedir = xstrdup (argv[j]);
212                 /* Deliberately discard the const below since we know we just
213                  * allocated filedir and can do what we like with it.
214                  */
215 		p = (char *)last_component (filedir);
216 		if (p == filedir)
217 		{
218 		    update_dir = "";
219 		}
220 		else
221 		{
222 		    p[-1] = '\0';
223 		    update_dir = filedir;
224 		    if (CVS_CHDIR (update_dir) < 0)
225 			error (1, errno,
226 			       "could not chdir to `%s'", update_dir);
227 		}
228 
229 		/* find the repository associated with our current dir */
230 		repository = Name_Repository (NULL, update_dir);
231 
232 		/* don't add stuff to Emptydir */
233 		if (strncmp (repository, current_parsed_root->directory, cvsroot_len) == 0
234 		    && ISSLASH (repository[cvsroot_len])
235 		    && strncmp (repository + cvsroot_len + 1,
236 				CVSROOTADM,
237 				sizeof CVSROOTADM - 1) == 0
238 		    && ISSLASH (repository[cvsroot_len + sizeof CVSROOTADM])
239 		    && strcmp (repository + cvsroot_len + sizeof CVSROOTADM + 1,
240 			       CVSNULLREPOS) == 0)
241 		    error (1, 0, "cannot add to `%s'", repository);
242 
243 		/* before we do anything else, see if we have any
244 		   per-directory tags */
245 		ParseTag (&tag, &date, &nonbranch);
246 
247 		rcsdir = Xasprintf ("%s/%s", repository, p);
248 
249 		Create_Admin (p, argv[j], rcsdir, tag, date,
250 			      nonbranch, 0, 1);
251 
252 		if (found_slash)
253 		    send_a_repository ("", repository, update_dir);
254 
255 		if (restore_cwd (&cwd))
256 		    error (1, errno,
257 		           "Failed to restore current directory, `%s'.",
258 		           cwd.name);
259 		free_cwd (&cwd);
260 
261 		if (tag)
262 		    free (tag);
263 		if (date)
264 		    free (date);
265 		free (rcsdir);
266 
267 		if (p == filedir)
268 		    Subdir_Register (NULL, NULL, argv[j]);
269 		else
270 		{
271 		    Subdir_Register (NULL, update_dir, p);
272 		}
273 		free (repository);
274 		free (filedir);
275 	    }
276 	}
277 	send_files (argc, argv, 0, 0, SEND_BUILD_DIRS | SEND_NO_CONTENTS);
278 	send_file_names (argc, argv, SEND_EXPAND_WILD);
279 	send_to_server ("add\012", 0);
280 	if (message)
281 	    free (message);
282 	return err + get_responses_and_close ();
283     }
284 #endif
285 
286     /* walk the arg list adding files/dirs */
287     for (i = 0; i < argc; i++)
288     {
289 	int begin_err = err;
290 #ifdef SERVER_SUPPORT
291 	int begin_added_files = added_files;
292 #endif
293 	struct file_info finfo;
294 	char *filename, *p;
295 
296 	memset (&finfo, 0, sizeof finfo);
297 
298 	if (save_cwd (&cwd))
299 	    error (1, errno, "Failed to save current directory.");
300 
301 	finfo.fullname = xstrdup (argv[i]);
302 	filename = xstrdup (argv[i]);
303 	/* We know we can discard the const below since we just allocated
304 	 * filename and can do as we like with it.
305          */
306 	p = (char *)last_component (filename);
307 	if (p == filename)
308 	{
309 	    finfo.update_dir = "";
310 	    finfo.file = p;
311 	}
312 	else
313 	{
314 	    p[-1] = '\0';
315 	    finfo.update_dir = filename;
316 	    finfo.file = p;
317 	    if (CVS_CHDIR (finfo.update_dir) < 0)
318 		error (1, errno, "could not chdir to `%s'", finfo.update_dir);
319 	}
320 
321 	/* Add wrappers for this directory.  They exist only until
322 	   the next call to wrap_add_file.  */
323 	wrap_add_file (CVSDOTWRAPPER, 1);
324 
325 	finfo.rcs = NULL;
326 
327 	/* Find the repository associated with our current dir.  */
328 	repository = Name_Repository (NULL, finfo.update_dir);
329 
330 	/* don't add stuff to Emptydir */
331 	if (strncmp (repository, current_parsed_root->directory,
332 		     cvsroot_len) == 0
333 	    && ISSLASH (repository[cvsroot_len])
334 	    && strncmp (repository + cvsroot_len + 1,
335 			CVSROOTADM,
336 			sizeof CVSROOTADM - 1) == 0
337 	    && ISSLASH (repository[cvsroot_len + sizeof CVSROOTADM])
338 	    && strcmp (repository + cvsroot_len + sizeof CVSROOTADM + 1,
339 		       CVSNULLREPOS) == 0)
340 	    error (1, 0, "cannot add to `%s'", repository);
341 
342 	entries = Entries_Open (0, NULL);
343 
344 	finfo.repository = repository;
345 	finfo.entries = entries;
346 
347 	/* We pass force_tag_match as 1.  If the directory has a
348            sticky branch tag, and there is already an RCS file which
349            does not have that tag, then the head revision is
350            meaningless to us.  */
351 	vers = Version_TS (&finfo, options, NULL, NULL, 1, 0);
352 	if (vers->vn_user == NULL)
353 	{
354 	    /* No entry available, ts_rcs is invalid */
355 	    if (vers->vn_rcs == NULL)
356 	    {
357 		/* There is no RCS file either */
358 		if (vers->ts_user == NULL)
359 		{
360 		    /* There is no user file either */
361 		    error (0, 0, "nothing known about `%s'", finfo.fullname);
362 		    err++;
363 		}
364 		else if (!isdir (finfo.file)
365 			 || wrap_name_has (finfo.file, WRAP_TOCVS))
366 		{
367 		    /*
368 		     * See if a directory exists in the repository with
369 		     * the same name.  If so, blow this request off.
370 		     */
371 		    char *dname = Xasprintf ("%s/%s", repository, finfo.file);
372 		    if (isdir (dname))
373 		    {
374 			error (0, 0,
375 			       "cannot add file `%s' since the directory",
376 			       finfo.fullname);
377 			error (0, 0, "`%s' already exists in the repository",
378 			       dname);
379 			error (1, 0, "invalid filename overlap");
380 		    }
381 		    free (dname);
382 
383 		    if (vers->options == NULL || *vers->options == '\0')
384 		    {
385 			/* No options specified on command line (or in
386 			   rcs file if it existed, e.g. the file exists
387 			   on another branch).  Check for a value from
388 			   the wrapper stuff.  */
389 			if (wrap_name_has (finfo.file, WRAP_RCSOPTION))
390 			{
391 			    if (vers->options)
392 				free (vers->options);
393 			    vers->options = wrap_rcsoption (finfo.file, 1);
394 			}
395 		    }
396 
397 		    if (vers->nonbranch)
398 		    {
399 			error (0, 0,
400 				"cannot add file on non-branch tag `%s'",
401 				vers->tag);
402 			++err;
403 		    }
404 		    else
405 		    {
406 /* cvsacl patch */
407 #ifdef SERVER_SUPPORT
408 			if (use_cvs_acl /* && server_active */)
409 			{
410 			    if (!access_allowed (finfo.file, repository,
411 						 vers->tag, 6, NULL, NULL, 1))
412 			    {
413 				if (stop_at_first_permission_denied)
414 				    error (1, 0, "permission denied for %s",
415 					   Short_Repository (finfo.repository));
416 				else
417 				    error (0, 0, "permission denied for %s/%s",
418 					   Short_Repository (finfo.repository),
419 					   finfo.file);
420 
421 				return (0);
422 			    }
423 			}
424 #endif
425 			/* There is a user file, so build the entry for it */
426 			if (build_entry (repository, finfo.file, vers->options,
427 					 message, entries, vers->tag) != 0)
428 			    err++;
429 			else
430 			{
431 			    added_files++;
432 			    if (!quiet)
433 			    {
434 				if (vers->tag)
435 				    error (0, 0, "scheduling %s `%s' for"
436 					   " addition on branch `%s'",
437 					   (wrap_name_has (finfo.file,
438 							   WRAP_TOCVS)
439 					    ? "wrapper"
440 					    : "file"),
441 					   finfo.fullname, vers->tag);
442 				else
443 				    error (0, 0,
444 					   "scheduling %s `%s' for addition",
445 					   (wrap_name_has (finfo.file,
446 							   WRAP_TOCVS)
447 					    ? "wrapper"
448 					    : "file"),
449 					   finfo.fullname);
450 			    }
451 			}
452 		    }
453 		}
454 	    }
455 	    else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
456 	    {
457 		if (isdir (finfo.file)
458 		    && !wrap_name_has (finfo.file, WRAP_TOCVS))
459 		{
460 		    error (0, 0,
461 			   "the directory `%s' cannot be added because a file"
462 			   " of the", finfo.fullname);
463 		    error (1, 0, "same name already exists in the repository.");
464 		}
465 		else
466 		{
467 		    if (vers->nonbranch)
468 		    {
469 			error (0, 0,
470 			       "cannot add file on non-branch tag `%s'",
471 			       vers->tag);
472 			++err;
473 		    }
474 		    else
475 		    {
476 			char *timestamp = NULL;
477 			if (vers->ts_user == NULL)
478 			{
479 			    /* If this file does not exist locally, assume that
480 			     * the last version on the branch is being
481 			     * resurrected.
482 			     *
483 			     * Compute previous revision.  We assume that it
484 			     * exists and that it is not a revision on the
485 			     * trunk of the form X.1 (1.1, 2.1, 3.1, ...).  We
486 			     * also assume that it is not dead, which seems
487 			     * fair since we know vers->vn_rcs is dead
488 			     * and we shouldn't see two dead revisions in a
489 			     * row.
490 			     */
491 			    char *prev = previous_rev (vers->srcfile,
492 			                               vers->vn_rcs);
493 			    int status;
494 			    if (prev == NULL)
495 			    {
496 				/* There is no previous revision.  Either:
497 				 *
498 				 *  * Revision 1.1 was dead, as when a file was
499 				 *    inititially added on a branch,
500 				 *
501 				 * or
502 				 *
503 				 *  * All previous revisions have been deleted.
504 				 *    For instance, via `admin -o'.
505 				 */
506 				if (!really_quiet)
507 				    error (0, 0,
508 "File `%s' has no previous revision to resurrect.",
509 			                   finfo.fullname);
510 				free (prev);
511 				goto skip_this_file;
512 			    }
513 			    if (!quiet)
514 				error (0, 0,
515 "Resurrecting file `%s' from revision %s.",
516 			               finfo.fullname, prev);
517 			    status = RCS_checkout (vers->srcfile, finfo.file,
518 						   prev, vers->tag,
519 						   vers->options, RUN_TTY,
520 			                           NULL, NULL);
521 			    xchmod (finfo.file, 1);
522 			    if (status != 0)
523 			    {
524 				error (0, 0, "Failed to resurrect revision %s",
525 				       prev);
526 				err++;
527 			    }
528 			    else
529 			    {
530 				/* I don't actually set vers->ts_user here
531 				 * because it would confuse server_update().
532 				 */
533 				timestamp = time_stamp (finfo.file);
534 				if (!really_quiet)
535 				    write_letter (&finfo, 'U');
536 			    }
537 			    free (prev);
538 			}
539 			if (!quiet)
540 			{
541 			    char *bbuf;
542 			    if (vers->tag)
543 			    {
544 				bbuf = Xasprintf (" on branch `%s'",
545 						  vers->tag);
546 			    }
547 			    else
548 				bbuf = "";
549 			    error (0, 0,
550 "Re-adding file `%s'%s after dead revision %s.",
551 				   finfo.fullname, bbuf, vers->vn_rcs);
552 			    if (vers->tag)
553 				free (bbuf);
554 			}
555 			Register (entries, finfo.file, "0",
556 				  timestamp ? timestamp : vers->ts_user,
557 				  vers->options, vers->tag, vers->date, NULL);
558 			if (timestamp) free (timestamp);
559 #ifdef SERVER_SUPPORT
560 			if (server_active && vers->ts_user == NULL)
561 			{
562 			    /* If we resurrected the file from the archive, we
563 			     * need to tell the client about it.
564 			     */
565 			    server_updated (&finfo, vers,
566 					    SERVER_UPDATED,
567 					    (mode_t) -1, NULL, NULL);
568 			    /* This is kinda hacky or, at least, it renders the
569 			     * name "begin_added_files" obsolete, but we want
570 			     * the added_files to be counted without triggering
571 			     * the check that causes server_checked_in() to be
572 			     * called below since we have already called
573 			     * server_updated() to complete the resurrection.
574 			     */
575 			    ++begin_added_files;
576 			}
577 #endif
578 			++added_files;
579 		    }
580 		}
581 	    }
582 	    else
583 	    {
584 		/*
585 		 * There is an RCS file already, so somebody else must've
586 		 * added it
587 		 */
588 		error (0, 0, "`%s' added independently by second party",
589 		       finfo.fullname);
590 		err++;
591 	    }
592 	}
593 	else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
594 	{
595 
596 	    /*
597 	     * An entry for a new-born file, ts_rcs is dummy, but that is
598 	     * inappropriate here
599 	     */
600 	    if (!quiet)
601 		error (0, 0, "`%s' has already been entered", finfo.fullname);
602 	    err++;
603 	}
604 	else if (vers->vn_user[0] == '-')
605 	{
606 	    /* An entry for a removed file, ts_rcs is invalid */
607 	    if (vers->ts_user == NULL)
608 	    {
609 		/* There is no user file (as it should be) */
610 		if (vers->vn_rcs == NULL)
611 		{
612 
613 		    /*
614 		     * There is no RCS file, so somebody else must've removed
615 		     * it from under us
616 		     */
617 		    error (0, 0,
618 			   "cannot resurrect `%s'; RCS file removed by"
619 			   " second party", finfo.fullname);
620 		    err++;
621 		}
622 		else
623 		{
624 		    int status;
625 		    /*
626 		     * There is an RCS file, so remove the "-" from the
627 		     * version number and restore the file
628 		     */
629 		    char *tmp = xstrdup (vers->vn_user + 1);
630 		    (void) strcpy (vers->vn_user, tmp);
631 		    free (tmp);
632 		    status = RCS_checkout (vers->srcfile, finfo.file,
633 					   vers->vn_user, vers->tag,
634 					   vers->options, RUN_TTY,
635 					   NULL, NULL);
636 		    xchmod (finfo.file, cvswrite);
637 		    if (status != 0)
638 		    {
639 			error (0, 0, "Failed to resurrect revision %s.",
640 			       vers->vn_user);
641 			err++;
642 			tmp = NULL;
643 		    }
644 		    else
645 		    {
646 			/* I don't actually set vers->ts_user here because it
647 			 * would confuse server_update().
648 			 */
649 			tmp = time_stamp (finfo.file);
650 			write_letter (&finfo, 'U');
651 			if (!quiet)
652 			     error (0, 0, "`%s', version %s, resurrected",
653 			            finfo.fullname, vers->vn_user);
654 		    }
655 		    Register (entries, finfo.file, vers->vn_user,
656                               tmp, vers->options,
657 			      vers->tag, vers->date, NULL);
658 		    if (tmp) free (tmp);
659 #ifdef SERVER_SUPPORT
660 		    if (server_active)
661 		    {
662 			/* If we resurrected the file from the archive, we
663 			 * need to tell the client about it.
664 			 */
665 			server_updated (&finfo, vers,
666 					SERVER_UPDATED,
667 					(mode_t) -1, NULL, NULL);
668 		    }
669 		   /* We don't increment added_files here because this isn't
670 		    * a change that needs to be committed.
671 		    */
672 #endif
673 		}
674 	    }
675 	    else
676 	    {
677 		/* The user file shouldn't be there */
678 		error (0, 0, "\
679 `%s' should be removed and is still there (or is back again)", finfo.fullname);
680 		err++;
681 	    }
682 	}
683 	else
684 	{
685 	    /* A normal entry, ts_rcs is valid, so it must already be there */
686 	    if (!quiet)
687 		error (0, 0, "`%s' already exists, with version number %s",
688 			finfo.fullname,
689 			vers->vn_user);
690 	    err++;
691 	}
692 	freevers_ts (&vers);
693 
694 	/* passed all the checks.  Go ahead and add it if its a directory */
695 	if (begin_err == err
696 	    && isdir (finfo.file)
697 	    && !wrap_name_has (finfo.file, WRAP_TOCVS))
698 	{
699 
700 /* cvsacl patch */
701 #ifdef SERVER_SUPPORT
702 	    if (use_cvs_acl /* && server_active */)
703 	    {
704 		if (!access_allowed (NULL, repository, NULL, 6, NULL, NULL, 1))
705 		{
706 		    if (stop_at_first_permission_denied)
707 			error (1, 0, "permission denied for %s",
708 			       Short_Repository (finfo.repository));
709 		    else
710 			error (0, 0, "permission denied for %s/%s",
711 			       Short_Repository (finfo.repository), finfo.file);
712 
713 		    return (0);
714 		}
715 	    }
716 #endif
717 
718 	    err += add_directory (&finfo);
719 	}
720 	else
721 	{
722 #ifdef SERVER_SUPPORT
723 	    if (server_active && begin_added_files != added_files)
724 		server_checked_in (finfo.file, finfo.update_dir, repository);
725 #endif
726 	}
727 
728 skip_this_file:
729 	free (repository);
730 	Entries_Close (entries);
731 
732 	if (restore_cwd (&cwd))
733 	    error (1, errno, "Failed to restore current directory, `%s'.",
734 	           cwd.name);
735 	free_cwd (&cwd);
736 
737 	/* It's okay to discard the const to free this - we allocated this
738 	 * above.  The const is for everybody else.
739 	 */
740 	free ((char *) finfo.fullname);
741 	free (filename);
742     }
743     if (added_files && !really_quiet)
744 	error (0, 0, "use `%s commit' to add %s permanently",
745 	       program_name,
746 	       (added_files == 1) ? "this file" : "these files");
747 
748     if (message)
749 	free (message);
750     if (options)
751 	free (options);
752 
753     return err;
754 }
755 
756 
757 
758 /*
759  * The specified user file is really a directory.  So, let's make sure that
760  * it is created in the RCS source repository, and that the user's directory
761  * is updated to include a CVS directory.
762  *
763  * Returns 1 on failure, 0 on success.
764  */
765 static int
add_directory(struct file_info * finfo)766 add_directory (struct file_info *finfo)
767 {
768     const char *repository = finfo->repository;
769     List *entries = finfo->entries;
770     const char *dir = finfo->file;
771 
772     char *rcsdir = NULL;
773     struct saved_cwd cwd;
774     char *message = NULL;
775     char *tag, *date;
776     int nonbranch;
777     char *attrs;
778 
779     if (strchr (dir, '/') != NULL)
780     {
781 	/* "Can't happen".  */
782 	error (0, 0,
783 	       "directory %s not added; must be a direct sub-directory", dir);
784 	return 1;
785     }
786     if (fncmp (dir, CVSADM) == 0)
787     {
788 	error (0, 0, "cannot add a `%s' directory", CVSADM);
789 	return 1;
790     }
791 
792     /* before we do anything else, see if we have any per-directory tags */
793     ParseTag (&tag, &date, &nonbranch);
794 
795     /* Remember the default attributes from this directory, so we can apply
796        them to the new directory.  */
797     fileattr_startdir (repository);
798     attrs = fileattr_getall (NULL);
799     fileattr_free ();
800 
801     /* now, remember where we were, so we can get back */
802     if (save_cwd (&cwd))
803     {
804 	error (0, errno, "Failed to save current directory.");
805 	return 1;
806     }
807     if (CVS_CHDIR (dir) < 0)
808     {
809 	error (0, errno, "cannot chdir to %s", finfo->fullname);
810 	return 1;
811     }
812     if (!server_active && isfile (CVSADM))
813     {
814 	error (0, 0, "%s/%s already exists", finfo->fullname, CVSADM);
815 	goto out;
816     }
817 
818     rcsdir = Xasprintf ("%s/%s", repository, dir);
819     if (isfile (rcsdir) && !isdir (rcsdir))
820     {
821 	error (0, 0, "%s is not a directory; %s not added", rcsdir,
822 	       finfo->fullname);
823 	goto out;
824     }
825 
826     /* setup the log message */
827     message = Xasprintf ("Directory %s added to the repository\n%s%s%s%s%s%s",
828 			 rcsdir,
829 			 tag ? "--> Using per-directory sticky tag `" : "",
830 			 tag ? tag : "", tag ? "'\n" : "",
831 			 date ? "--> Using per-directory sticky date `" : "",
832 			 date ? date : "", date ? "'\n" : "");
833 
834     if (!isdir (rcsdir))
835     {
836 	mode_t omask;
837 	Node *p;
838 	List *ulist;
839 	struct logfile_info *li;
840 
841 	/* There used to be some code here which would prompt for
842 	   whether to add the directory.  The details of that code had
843 	   bitrotted, but more to the point it can't work
844 	   client/server, doesn't ask in the right way for GUIs, etc.
845 	   A better way of making it harder to accidentally add
846 	   directories would be to have to add and commit directories
847 	   like for files.  The code was #if 0'd at least since CVS 1.5.  */
848 
849 	if (!noexec)
850 	{
851 	    omask = umask (cvsumask);
852 	    if (CVS_MKDIR (rcsdir, 0777) < 0)
853 	    {
854 		error (0, errno, "cannot mkdir %s", rcsdir);
855 		(void) umask (omask);
856 		goto out;
857 	    }
858 	    (void) umask (omask);
859 	}
860 
861 	/* Now set the default file attributes to the ones we inherited
862 	   from the parent directory.  */
863 	fileattr_startdir (rcsdir);
864 	fileattr_setall (NULL, attrs);
865 	fileattr_write ();
866 	fileattr_free ();
867 	if (attrs != NULL)
868 	    free (attrs);
869 
870 	/*
871 	 * Set up an update list with a single title node for Update_Logfile
872 	 */
873 	ulist = getlist ();
874 	p = getnode ();
875 	p->type = UPDATE;
876 	p->delproc = update_delproc;
877 	p->key = xstrdup ("- New directory");
878 	li = xmalloc (sizeof (struct logfile_info));
879 	li->type = T_TITLE;
880 	li->tag = xstrdup (tag);
881 	li->rev_old = li->rev_new = NULL;
882 	p->data = li;
883 	(void) addnode (ulist, p);
884 	Update_Logfile (rcsdir, message, NULL, ulist);
885 	dellist (&ulist);
886     }
887 
888     if (server_active)
889 	WriteTemplate (finfo->fullname, 1, rcsdir);
890     else
891 	Create_Admin (".", finfo->fullname, rcsdir, tag, date, nonbranch, 0, 1);
892 
893     if (tag)
894 	free (tag);
895     if (date)
896 	free (date);
897 
898     if (restore_cwd (&cwd))
899 	error (1, errno, "Failed to restore current directory, `%s'.",
900 	       cwd.name);
901     free_cwd (&cwd);
902 
903     Subdir_Register (entries, NULL, dir);
904 
905     if (!really_quiet)
906 	cvs_output (message, 0);
907 
908     free (rcsdir);
909     free (message);
910 
911     return 0;
912 
913 out:
914     if (restore_cwd (&cwd))
915 	error (1, errno, "Failed to restore current directory, `%s'.",
916 	       cwd.name);
917     free_cwd (&cwd);
918     if (message) free (message);
919     if (rcsdir != NULL)
920 	free (rcsdir);
921     return 0;
922 }
923 
924 
925 
926 /*
927  * Builds an entry for a new file and sets up "CVS/file",[pt] by
928  * interrogating the user.  Returns non-zero on error.
929  */
930 static int
build_entry(const char * repository,const char * user,const char * options,const char * message,List * entries,const char * tag)931 build_entry (const char *repository, const char *user, const char *options,
932              const char *message, List *entries, const char *tag)
933 {
934     char *fname;
935     char *line;
936     FILE *fp;
937 
938     if (noexec)
939 	return 0;
940 
941     /*
942      * The requested log is read directly from the user and stored in the
943      * file user,t.  If the "message" argument is set, use it as the
944      * initial creation log (which typically describes the file).
945      */
946     fname = Xasprintf ("%s/%s%s", CVSADM, user, CVSEXT_LOG);
947     fp = xfopen (fname, "w+");
948     if (message && fputs (message, fp) == EOF)
949 	    error (1, errno, "cannot write to %s", fname);
950     if (fclose (fp) == EOF)
951         error (1, errno, "cannot close %s", fname);
952     free (fname);
953 
954     /*
955      * Create the entry now, since this allows the user to interrupt us above
956      * without needing to clean anything up (well, we could clean up the
957      * ,t file, but who cares).
958      */
959     line = Xasprintf ("Initial %s", user);
960     Register (entries, user, "0", line, options, tag, NULL, NULL);
961     free (line);
962     return 0;
963 }
964