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