xref: /openbsd/usr.bin/cvs/add.c (revision 3d8817e4)
1 /*	$OpenBSD: add.c,v 1.110 2010/11/11 21:00:59 nicm Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/stat.h>
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #include "cvs.h"
27 #include "remote.h"
28 
29 extern char *__progname;
30 
31 void	cvs_add_loginfo(char *);
32 void	cvs_add_entry(struct cvs_file *);
33 void	cvs_add_remote(struct cvs_file *);
34 
35 static void add_directory(struct cvs_file *);
36 static void add_file(struct cvs_file *);
37 static void add_entry(struct cvs_file *);
38 
39 int		kflag = 0;
40 static u_int	added_files = 0;
41 static char	kbuf[8];
42 
43 extern char	*logmsg;
44 extern char	*loginfo;
45 
46 struct cvs_cmd cvs_cmd_add = {
47 	CVS_OP_ADD, CVS_USE_WDIR, "add",
48 	{ "ad", "new" },
49 	"Add a new file or directory to the repository",
50 	"[-k mode] [-m message] ...",
51 	"k:m:",
52 	NULL,
53 	cvs_add
54 };
55 
56 int
57 cvs_add(int argc, char **argv)
58 {
59 	int ch;
60 	int flags;
61 	struct cvs_recursion cr;
62 
63 	flags = CR_REPO;
64 
65 	while ((ch = getopt(argc, argv, cvs_cmd_add.cmd_opts)) != -1) {
66 		switch (ch) {
67 		case 'k':
68 			kflag = rcs_kflag_get(optarg);
69 			if (RCS_KWEXP_INVAL(kflag)) {
70 				cvs_log(LP_ERR,
71 				    "invalid RCS keyword expansion mode");
72 				fatal("%s", cvs_cmd_add.cmd_synopsis);
73 			}
74 			(void)xsnprintf(kbuf, sizeof(kbuf), "-k%s", optarg);
75 			break;
76 		case 'm':
77 			logmsg = optarg;
78 			break;
79 		default:
80 			fatal("%s", cvs_cmd_add.cmd_synopsis);
81 		}
82 	}
83 
84 	argc -= optind;
85 	argv += optind;
86 
87 	if (argc == 0)
88 		fatal("%s", cvs_cmd_add.cmd_synopsis);
89 
90 	cr.enterdir = NULL;
91 	cr.leavedir = NULL;
92 
93 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
94 		cvs_client_connect_to_server();
95 		cr.fileproc = cvs_add_remote;
96 		flags = 0;
97 
98 		if (kflag)
99 			cvs_client_send_request("Argument %s", kbuf);
100 
101 		if (logmsg != NULL)
102 			cvs_client_send_logmsg(logmsg);
103 	} else {
104 		if (logmsg != NULL && cvs_logmsg_verify(logmsg))
105 			return (0);
106 
107 		cr.fileproc = cvs_add_local;
108 	}
109 
110 	cr.flags = flags;
111 
112 	cvs_file_run(argc, argv, &cr);
113 
114 	if (added_files != 0) {
115 		cvs_log(LP_NOTICE, "use '%s commit' to add %s "
116 		    "permanently", __progname,
117 		    (added_files == 1) ? "this file" : "these files");
118 	}
119 
120 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
121 		cvs_client_senddir(".");
122 		cvs_client_send_files(argv, argc);
123 		cvs_client_send_request("add");
124 		cvs_client_get_responses();
125 
126 		if (server_response == SERVER_OK) {
127 			cr.fileproc = cvs_add_entry;
128 			cvs_file_run(argc, argv, &cr);
129 		}
130 	}
131 
132 	return (0);
133 }
134 
135 void
136 cvs_add_entry(struct cvs_file *cf)
137 {
138 	char *entry;
139 	CVSENTRIES *entlist;
140 
141 	if (cf->file_type == CVS_DIR) {
142 		entry = xmalloc(CVS_ENT_MAXLINELEN);
143 		cvs_ent_line_str(cf->file_name, NULL, NULL, NULL, NULL, 1, 0,
144 		    entry, CVS_ENT_MAXLINELEN);
145 
146 		entlist = cvs_ent_open(cf->file_wd);
147 		cvs_ent_add(entlist, entry);
148 
149 		xfree(entry);
150 	} else {
151 		add_entry(cf);
152 	}
153 }
154 
155 void
156 cvs_add_local(struct cvs_file *cf)
157 {
158 	cvs_log(LP_TRACE, "cvs_add_local(%s)", cf->file_path);
159 
160 	if (cvs_cmdop != CVS_OP_CHECKOUT && cvs_cmdop != CVS_OP_UPDATE)
161 		cvs_file_classify(cf, cvs_directory_tag);
162 
163 	/* dont use `cvs add *' */
164 	if (strcmp(cf->file_name, ".") == 0 ||
165 	    strcmp(cf->file_name, "..") == 0 ||
166 	    strcmp(cf->file_name, CVS_PATH_CVSDIR) == 0) {
167 		if (verbosity > 1)
168 			cvs_log(LP_ERR,
169 			    "cannot add special file `%s'; skipping",
170 			    cf->file_name);
171 		return;
172 	}
173 
174 	if (cf->file_type == CVS_DIR)
175 		add_directory(cf);
176 	else
177 		add_file(cf);
178 }
179 
180 void
181 cvs_add_remote(struct cvs_file *cf)
182 {
183 	char path[MAXPATHLEN];
184 
185 	cvs_log(LP_TRACE, "cvs_add_remote(%s)", cf->file_path);
186 
187 	cvs_file_classify(cf, cvs_directory_tag);
188 
189 	if (cf->file_type == CVS_DIR) {
190 		cvs_get_repository_path(cf->file_wd, path, MAXPATHLEN);
191 		if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
192 			fatal("cvs_add_remote: truncation");
193 		if (strlcat(path, cf->file_path, sizeof(path)) >= sizeof(path))
194 			fatal("cvs_add_remote: truncation");
195 		cvs_client_send_request("Directory %s\n%s", cf->file_path,
196 		    path);
197 
198 		add_directory(cf);
199 	} else {
200 		cvs_client_sendfile(cf);
201 	}
202 }
203 
204 void
205 cvs_add_loginfo(char *repo)
206 {
207 	BUF *buf;
208 	char pwd[MAXPATHLEN];
209 
210 	if (getcwd(pwd, sizeof(pwd)) == NULL)
211 		fatal("Can't get working directory");
212 
213 	buf = buf_alloc(1024);
214 
215 	cvs_trigger_loginfo_header(buf, repo);
216 
217 	buf_puts(buf, "Log Message:\nDirectory ");
218 	buf_puts(buf, current_cvsroot->cr_dir);
219 	buf_putc(buf, '/');
220 	buf_puts(buf, repo);
221 	buf_puts(buf, " added to the repository\n");
222 
223 	buf_putc(buf, '\0');
224 
225 	loginfo = buf_release(buf);
226 }
227 
228 void
229 cvs_add_tobranch(struct cvs_file *cf, char *tag)
230 {
231 	BUF *bp;
232 	char attic[MAXPATHLEN], repo[MAXPATHLEN];
233 	char *msg;
234 	struct stat st;
235 	RCSNUM *branch;
236 
237 	cvs_log(LP_TRACE, "cvs_add_tobranch(%s)", cf->file_name);
238 
239 	if (cvs_noexec == 1)
240 		return;
241 
242 	if (fstat(cf->fd, &st) == -1)
243 		fatal("cvs_add_tobranch: %s", strerror(errno));
244 
245 	cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
246 	(void)xsnprintf(attic, MAXPATHLEN, "%s/%s",
247 	    repo, CVS_PATH_ATTIC);
248 
249 	if (mkdir(attic, 0755) == -1 && errno != EEXIST)
250 		fatal("cvs_add_tobranch: failed to create Attic");
251 
252 	(void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo,
253 	    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
254 
255 	xfree(cf->file_rpath);
256 	cf->file_rpath = xstrdup(attic);
257 
258 	cf->repo_fd = open(cf->file_rpath, O_CREAT|O_RDONLY);
259 	if (cf->repo_fd < 0)
260 		fatal("cvs_add_tobranch: %s: %s", cf->file_rpath,
261 		    strerror(errno));
262 
263 	cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd,
264 	    RCS_CREATE|RCS_WRITE, 0444);
265 	if (cf->file_rcs == NULL)
266 		fatal("cvs_add_tobranch: failed to create RCS file for %s",
267 		    cf->file_path);
268 
269 	if ((branch = rcsnum_parse("1.1.2")) == NULL)
270 		fatal("cvs_add_tobranch: failed to parse branch");
271 
272 	if (rcs_sym_add(cf->file_rcs, tag, branch) == -1)
273 		fatal("cvs_add_tobranch: failed to add vendor tag");
274 
275 	(void)xasprintf(&msg, "file %s was initially added on branch %s.",
276 	    cf->file_name, tag);
277 	if (rcs_rev_add(cf->file_rcs, RCS_HEAD_REV, msg, -1, NULL) == -1)
278 		fatal("cvs_add_tobranch: failed to create first branch "
279 		    "revision");
280 	xfree(msg);
281 
282 	if (rcs_findrev(cf->file_rcs, cf->file_rcs->rf_head) == NULL)
283 		fatal("cvs_add_tobranch: cannot find newly added revision");
284 
285 	bp = buf_alloc(1);
286 
287 	if (rcs_deltatext_set(cf->file_rcs,
288 	    cf->file_rcs->rf_head, bp) == -1)
289 		fatal("cvs_add_tobranch: failed to set deltatext");
290 
291 	rcs_comment_set(cf->file_rcs, " * ");
292 
293 	if (rcs_state_set(cf->file_rcs, cf->file_rcs->rf_head, RCS_STATE_DEAD)
294 	    == -1)
295 		fatal("cvs_add_tobranch: failed to set state");
296 }
297 
298 static void
299 add_directory(struct cvs_file *cf)
300 {
301 	int added, nb;
302 	struct stat st;
303 	CVSENTRIES *entlist;
304 	char *date, entry[MAXPATHLEN], msg[1024], repo[MAXPATHLEN], *tag, *p;
305 	struct file_info_list files_info;
306 	struct file_info *fi;
307 	struct trigger_list *line_list;
308 
309 	cvs_log(LP_TRACE, "add_directory(%s)", cf->file_path);
310 
311 	(void)xsnprintf(entry, MAXPATHLEN, "%s%s",
312 	    cf->file_rpath, RCS_FILE_EXT);
313 
314 	added = 1;
315 	if (stat(entry, &st) != -1) {
316 		cvs_log(LP_NOTICE, "cannot add directory %s: "
317 		    "a file with that name already exists",
318 		    cf->file_path);
319 		added = 0;
320 	} else {
321 		/* Let's see if we have any per-directory tags first. */
322 		cvs_parse_tagfile(cf->file_wd, &tag, &date, &nb);
323 
324 		(void)xsnprintf(entry, MAXPATHLEN, "%s/%s",
325 		    cf->file_path, CVS_PATH_CVSDIR);
326 
327 		if (cvs_server_active) {
328 			if (mkdir(cf->file_rpath, 0755) == -1 &&
329 			    errno != EEXIST)
330 				fatal("add_directory: %s: %s", cf->file_rpath,
331 				    strerror(errno));
332 		} else if (stat(entry, &st) != -1) {
333 			if (!S_ISDIR(st.st_mode)) {
334 				cvs_log(LP_ERR, "%s exists but is not "
335 				    "directory", entry);
336 			} else {
337 				cvs_log(LP_NOTICE, "%s already exists",
338 				    entry);
339 			}
340 			added = 0;
341 		} else if (cvs_noexec != 1) {
342 			if (mkdir(cf->file_rpath, 0755) == -1 &&
343 			    errno != EEXIST)
344 				fatal("add_directory: %s: %s", cf->file_rpath,
345 				    strerror(errno));
346 
347 			cvs_get_repository_name(cf->file_wd, repo,
348 			    MAXPATHLEN);
349 
350 			(void)xsnprintf(entry, MAXPATHLEN, "%s/%s",
351 			    repo, cf->file_name);
352 
353 			cvs_mkadmin(cf->file_path, current_cvsroot->cr_dir,
354 			    entry, tag, date);
355 
356 			p = xmalloc(CVS_ENT_MAXLINELEN);
357 			cvs_ent_line_str(cf->file_name, NULL, NULL, NULL,
358 			    NULL, 1, 0, p, CVS_ENT_MAXLINELEN);
359 
360 			entlist = cvs_ent_open(cf->file_wd);
361 			cvs_ent_add(entlist, p);
362 			xfree(p);
363 		}
364 	}
365 
366 	if (added == 1 && current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
367 		(void)xsnprintf(msg, sizeof(msg),
368 		    "Directory %s added to the repository", cf->file_rpath);
369 
370 		if (tag != NULL) {
371 			(void)strlcat(msg,
372 			    "\n--> Using per-directory sticky tag ",
373 			    sizeof(msg));
374 			(void)strlcat(msg, tag, sizeof(msg));
375 		}
376 		if (date != NULL) {
377 			(void)strlcat(msg,
378 			    "\n--> Using per-directory sticky date ",
379 			    sizeof(msg));
380 			(void)strlcat(msg, date, sizeof(msg));
381 		}
382 		cvs_printf("%s\n", msg);
383 
384 		if (tag != NULL)
385 			xfree(tag);
386 		if (date != NULL)
387 			xfree(date);
388 
389 		cvs_get_repository_name(cf->file_path, repo, MAXPATHLEN);
390 		line_list = cvs_trigger_getlines(CVS_PATH_LOGINFO, repo);
391 		if (line_list != NULL) {
392 			TAILQ_INIT(&files_info);
393 			fi = xcalloc(1, sizeof(*fi));
394 			fi->file_path = xstrdup(cf->file_path);
395 			TAILQ_INSERT_TAIL(&files_info, fi, flist);
396 
397 			cvs_add_loginfo(repo);
398 			cvs_trigger_handle(CVS_TRIGGER_LOGINFO, repo,
399 			    loginfo, line_list, &files_info);
400 
401 			cvs_trigger_freeinfo(&files_info);
402 			cvs_trigger_freelist(line_list);
403 			if (loginfo != NULL)
404 				xfree(loginfo);
405 		}
406 	}
407 
408 	cf->file_status = FILE_SKIP;
409 }
410 
411 static void
412 add_file(struct cvs_file *cf)
413 {
414 	int nb, stop;
415 	char revbuf[CVS_REV_BUFSZ];
416 	RCSNUM *head = NULL;
417 	char *tag;
418 
419 	cvs_parse_tagfile(cf->file_wd, &tag, NULL, &nb);
420 	if (nb) {
421 		cvs_log(LP_ERR, "cannot add file on non-branch tag %s", tag);
422 		return;
423 	}
424 
425 	if (cf->file_rcs != NULL) {
426 		head = rcs_head_get(cf->file_rcs);
427 		if (head == NULL) {
428 			cvs_log(LP_NOTICE, "no head revision in RCS file for "
429 			    "%s", cf->file_path);
430 		}
431 		rcsnum_tostr(head, revbuf, sizeof(revbuf));
432 	}
433 
434 	stop = 0;
435 	switch (cf->file_status) {
436 	case FILE_ADDED:
437 	case FILE_CHECKOUT:
438 		if (verbosity > 1)
439 			cvs_log(LP_NOTICE, "%s has already been entered",
440 			    cf->file_path);
441 		stop = 1;
442 		break;
443 	case FILE_REMOVED:
444 		if (cf->file_rcs == NULL) {
445 			cvs_log(LP_NOTICE, "cannot resurrect %s; "
446 			    "RCS file removed by second party", cf->file_name);
447 		} else if (!(cf->file_flags & FILE_ON_DISK)) {
448 			add_entry(cf);
449 
450 			/* Restore the file. */
451 			cvs_checkout_file(cf, head, NULL, 0);
452 
453 			cvs_printf("U %s\n", cf->file_path);
454 
455 			cvs_log(LP_NOTICE, "%s, version %s, resurrected",
456 			    cf->file_name, revbuf);
457 
458 			cf->file_status = FILE_UPTODATE;
459 		}
460 		stop = 1;
461 		break;
462 	case FILE_CONFLICT:
463 	case FILE_LOST:
464 	case FILE_MODIFIED:
465 	case FILE_UPTODATE:
466 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 0) {
467 			cvs_log(LP_NOTICE, "%s already exists, with version "
468 			     "number %s", cf->file_path, revbuf);
469 			stop = 1;
470 		}
471 		break;
472 	case FILE_UNKNOWN:
473 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1) {
474 			cvs_log(LP_NOTICE, "re-adding file %s "
475 			    "(instead of dead revision %s)",
476 			    cf->file_path, revbuf);
477 			added_files++;
478 		} else if (cf->file_flags & FILE_ON_DISK) {
479 			cvs_log(LP_NOTICE, "scheduling file '%s' for addition",
480 			    cf->file_path);
481 			added_files++;
482 		} else {
483 			stop = 1;
484 		}
485 		break;
486 	default:
487 		break;
488 	}
489 
490 	if (head != NULL)
491 		rcsnum_free(head);
492 
493 	if (stop == 1)
494 		return;
495 
496 	add_entry(cf);
497 }
498 
499 static void
500 add_entry(struct cvs_file *cf)
501 {
502 	FILE *fp;
503 	char *entry, path[MAXPATHLEN];
504 	char revbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ];
505 	char sticky[CVS_ENT_MAXLINELEN];
506 	CVSENTRIES *entlist;
507 
508 	if (cvs_noexec == 1)
509 		return;
510 
511 	sticky[0] = '\0';
512 	entry = xmalloc(CVS_ENT_MAXLINELEN);
513 
514 	if (cf->file_status == FILE_REMOVED) {
515 		rcsnum_tostr(cf->file_ent->ce_rev, revbuf, sizeof(revbuf));
516 
517 		ctime_r(&cf->file_ent->ce_mtime, tbuf);
518 		tbuf[strcspn(tbuf, "\n")] = '\0';
519 
520 		if (cf->file_ent->ce_tag != NULL)
521 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
522 			    cf->file_ent->ce_tag);
523 
524 		/* Remove the '-' prefixing the version number. */
525 		cvs_ent_line_str(cf->file_name, revbuf, tbuf,
526 		    cf->file_ent->ce_opts ? cf->file_ent->ce_opts : "", sticky,
527 		    0, 0, entry, CVS_ENT_MAXLINELEN);
528 	} else {
529 		if (logmsg != NULL) {
530 			(void)xsnprintf(path, MAXPATHLEN, "%s/%s/%s%s",
531 			    cf->file_wd, CVS_PATH_CVSDIR, cf->file_name,
532 			    CVS_DESCR_FILE_EXT);
533 
534 			if ((fp = fopen(path, "w+")) == NULL)
535 				fatal("add_entry: fopen `%s': %s",
536 				    path, strerror(errno));
537 
538 			if (fputs(logmsg, fp) == EOF) {
539 				(void)unlink(path);
540 				fatal("add_entry: fputs `%s': %s",
541 				    path, strerror(errno));
542 			}
543 			(void)fclose(fp);
544 		}
545 
546 		if (cvs_directory_tag != NULL)
547 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
548 			    cvs_directory_tag);
549 
550 		tbuf[0] = '\0';
551 		if (!cvs_server_active)
552 			(void)xsnprintf(tbuf, sizeof(tbuf), "Initial %s",
553 			    cf->file_name);
554 
555 		cvs_ent_line_str(cf->file_name, "0", tbuf, kflag ? kbuf : "",
556 		    sticky, 0, 0, entry, CVS_ENT_MAXLINELEN);
557 	}
558 
559 	if (cvs_server_active) {
560 		cvs_server_send_response("Checked-in %s/", cf->file_wd);
561 		cvs_server_send_response("%s", cf->file_path);
562 		cvs_server_send_response("%s", entry);
563 	} else {
564 		entlist = cvs_ent_open(cf->file_wd);
565 		cvs_ent_add(entlist, entry);
566 	}
567 	xfree(entry);
568 }
569