xref: /openbsd-src/usr.bin/cvs/add.c (revision cd1eb269cafb12c415be1749cd4a4b5422710415)
1 /*	$OpenBSD: add.c,v 1.108 2010/04/04 17:11:11 zinovik 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 = cvs_buf_alloc(1024);
214 
215 	cvs_trigger_loginfo_header(buf, repo);
216 
217 	cvs_buf_puts(buf, "Log Message:\nDirectory ");
218 	cvs_buf_puts(buf, current_cvsroot->cr_dir);
219 	cvs_buf_putc(buf, '/');
220 	cvs_buf_puts(buf, repo);
221 	cvs_buf_puts(buf, " added to the repository\n");
222 
223 	cvs_buf_putc(buf, '\0');
224 
225 	loginfo = cvs_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 	struct rcs_delta *rdp;
236 	RCSNUM *branch;
237 
238 	cvs_log(LP_TRACE, "cvs_add_tobranch(%s)", cf->file_name);
239 
240 	if (cvs_noexec == 1)
241 		return;
242 
243 	if (fstat(cf->fd, &st) == -1)
244 		fatal("cvs_add_tobranch: %s", strerror(errno));
245 
246 	cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
247 	(void)xsnprintf(attic, MAXPATHLEN, "%s/%s",
248 	    repo, CVS_PATH_ATTIC);
249 
250 	if (mkdir(attic, 0755) == -1 && errno != EEXIST)
251 		fatal("cvs_add_tobranch: failed to create Attic");
252 
253 	(void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo,
254 	    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
255 
256 	xfree(cf->file_rpath);
257 	cf->file_rpath = xstrdup(attic);
258 
259 	cf->repo_fd = open(cf->file_rpath, O_CREAT|O_RDONLY);
260 	if (cf->repo_fd < 0)
261 		fatal("cvs_add_tobranch: %s: %s", cf->file_rpath,
262 		    strerror(errno));
263 
264 	cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd,
265 	    RCS_CREATE|RCS_WRITE, 0444);
266 	if (cf->file_rcs == NULL)
267 		fatal("cvs_add_tobranch: failed to create RCS file for %s",
268 		    cf->file_path);
269 
270 	if ((branch = rcsnum_parse("1.1.2")) == NULL)
271 		fatal("cvs_add_tobranch: failed to parse branch");
272 
273 	if (rcs_sym_add(cf->file_rcs, tag, branch) == -1)
274 		fatal("cvs_add_tobranch: failed to add vendor tag");
275 
276 	(void)xasprintf(&msg, "file %s was initially added on branch %s.",
277 	    cf->file_name, tag);
278 	if (rcs_rev_add(cf->file_rcs, RCS_HEAD_REV, msg, -1, NULL) == -1)
279 		fatal("cvs_add_tobranch: failed to create first branch "
280 		    "revision");
281 	xfree(msg);
282 
283 	if ((rdp = rcs_findrev(cf->file_rcs, cf->file_rcs->rf_head)) == NULL)
284 		fatal("cvs_add_tobranch: cannot find newly added revision");
285 
286 	bp = cvs_buf_alloc(1);
287 
288 	if (rcs_deltatext_set(cf->file_rcs,
289 	    cf->file_rcs->rf_head, bp) == -1)
290 		fatal("cvs_add_tobranch: failed to set deltatext");
291 
292 	rcs_comment_set(cf->file_rcs, " * ");
293 
294 	if (rcs_state_set(cf->file_rcs, cf->file_rcs->rf_head, RCS_STATE_DEAD)
295 	    == -1)
296 		fatal("cvs_add_tobranch: failed to set state");
297 }
298 
299 static void
300 add_directory(struct cvs_file *cf)
301 {
302 	int added, nb;
303 	struct stat st;
304 	CVSENTRIES *entlist;
305 	char *date, entry[MAXPATHLEN], msg[1024], repo[MAXPATHLEN], *tag, *p;
306 	struct file_info_list files_info;
307 	struct file_info *fi;
308 	struct trigger_list *line_list;
309 
310 	cvs_log(LP_TRACE, "add_directory(%s)", cf->file_path);
311 
312 	(void)xsnprintf(entry, MAXPATHLEN, "%s%s",
313 	    cf->file_rpath, RCS_FILE_EXT);
314 
315 	added = 1;
316 	if (stat(entry, &st) != -1) {
317 		cvs_log(LP_NOTICE, "cannot add directory %s: "
318 		    "a file with that name already exists",
319 		    cf->file_path);
320 		added = 0;
321 	} else {
322 		/* Let's see if we have any per-directory tags first. */
323 		cvs_parse_tagfile(cf->file_wd, &tag, &date, &nb);
324 
325 		(void)xsnprintf(entry, MAXPATHLEN, "%s/%s",
326 		    cf->file_path, CVS_PATH_CVSDIR);
327 
328 		if (cvs_server_active) {
329 			if (mkdir(cf->file_rpath, 0755) == -1 &&
330 			    errno != EEXIST)
331 				fatal("add_directory: %s: %s", cf->file_rpath,
332 				    strerror(errno));
333 		} else if (stat(entry, &st) != -1) {
334 			if (!S_ISDIR(st.st_mode)) {
335 				cvs_log(LP_ERR, "%s exists but is not "
336 				    "directory", entry);
337 			} else {
338 				cvs_log(LP_NOTICE, "%s already exists",
339 				    entry);
340 			}
341 			added = 0;
342 		} else if (cvs_noexec != 1) {
343 			if (mkdir(cf->file_rpath, 0755) == -1 &&
344 			    errno != EEXIST)
345 				fatal("add_directory: %s: %s", cf->file_rpath,
346 				    strerror(errno));
347 
348 			cvs_get_repository_name(cf->file_wd, repo,
349 			    MAXPATHLEN);
350 
351 			(void)xsnprintf(entry, MAXPATHLEN, "%s/%s",
352 			    repo, cf->file_name);
353 
354 			cvs_mkadmin(cf->file_path, current_cvsroot->cr_dir,
355 			    entry, tag, date);
356 
357 			p = xmalloc(CVS_ENT_MAXLINELEN);
358 			cvs_ent_line_str(cf->file_name, NULL, NULL, NULL,
359 			    NULL, 1, 0, p, CVS_ENT_MAXLINELEN);
360 
361 			entlist = cvs_ent_open(cf->file_wd);
362 			cvs_ent_add(entlist, p);
363 			xfree(p);
364 		}
365 	}
366 
367 	if (added == 1 && current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
368 		(void)xsnprintf(msg, sizeof(msg),
369 		    "Directory %s added to the repository", cf->file_rpath);
370 
371 		if (tag != NULL) {
372 			(void)strlcat(msg,
373 			    "\n--> Using per-directory sticky tag ",
374 			    sizeof(msg));
375 			(void)strlcat(msg, tag, sizeof(msg));
376 		}
377 		if (date != NULL) {
378 			(void)strlcat(msg,
379 			    "\n--> Using per-directory sticky date ",
380 			    sizeof(msg));
381 			(void)strlcat(msg, date, sizeof(msg));
382 		}
383 		cvs_printf("%s\n", msg);
384 
385 		if (tag != NULL)
386 			xfree(tag);
387 		if (date != NULL)
388 			xfree(date);
389 
390 		cvs_get_repository_name(cf->file_path, repo, MAXPATHLEN);
391 		line_list = cvs_trigger_getlines(CVS_PATH_LOGINFO, repo);
392 		if (line_list != NULL) {
393 			TAILQ_INIT(&files_info);
394 			fi = xcalloc(1, sizeof(*fi));
395 			fi->file_path = xstrdup(cf->file_path);
396 			TAILQ_INSERT_TAIL(&files_info, fi, flist);
397 
398 			cvs_add_loginfo(repo);
399 			cvs_trigger_handle(CVS_TRIGGER_LOGINFO, repo,
400 			    loginfo, line_list, &files_info);
401 
402 			cvs_trigger_freeinfo(&files_info);
403 			cvs_trigger_freelist(line_list);
404 			if (loginfo != NULL)
405 				xfree(loginfo);
406 		}
407 	}
408 
409 	cf->file_status = FILE_SKIP;
410 }
411 
412 static void
413 add_file(struct cvs_file *cf)
414 {
415 	int nb, stop;
416 	char revbuf[CVS_REV_BUFSZ];
417 	RCSNUM *head = NULL;
418 	char *tag;
419 
420 	cvs_parse_tagfile(cf->file_wd, &tag, NULL, &nb);
421 	if (nb) {
422 		cvs_log(LP_ERR, "cannot add file on non-branch tag %s", tag);
423 		return;
424 	}
425 
426 	if (cf->file_rcs != NULL) {
427 		head = rcs_head_get(cf->file_rcs);
428 		if (head == NULL) {
429 			cvs_log(LP_NOTICE, "no head revision in RCS file for "
430 			    "%s", cf->file_path);
431 		}
432 		rcsnum_tostr(head, revbuf, sizeof(revbuf));
433 	}
434 
435 	stop = 0;
436 	switch (cf->file_status) {
437 	case FILE_ADDED:
438 	case FILE_CHECKOUT:
439 		if (verbosity > 1)
440 			cvs_log(LP_NOTICE, "%s has already been entered",
441 			    cf->file_path);
442 		stop = 1;
443 		break;
444 	case FILE_REMOVED:
445 		if (cf->file_rcs == NULL) {
446 			cvs_log(LP_NOTICE, "cannot resurrect %s; "
447 			    "RCS file removed by second party", cf->file_name);
448 		} else if (!(cf->file_flags & FILE_ON_DISK)) {
449 			add_entry(cf);
450 
451 			/* Restore the file. */
452 			cvs_checkout_file(cf, head, NULL, 0);
453 
454 			cvs_printf("U %s\n", cf->file_path);
455 
456 			cvs_log(LP_NOTICE, "%s, version %s, resurrected",
457 			    cf->file_name, revbuf);
458 
459 			cf->file_status = FILE_UPTODATE;
460 		}
461 		stop = 1;
462 		break;
463 	case FILE_CONFLICT:
464 	case FILE_LOST:
465 	case FILE_MODIFIED:
466 	case FILE_UPTODATE:
467 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 0) {
468 			cvs_log(LP_NOTICE, "%s already exists, with version "
469 			     "number %s", cf->file_path, revbuf);
470 			stop = 1;
471 		}
472 		break;
473 	case FILE_UNKNOWN:
474 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1) {
475 			cvs_log(LP_NOTICE, "re-adding file %s "
476 			    "(instead of dead revision %s)",
477 			    cf->file_path, revbuf);
478 			added_files++;
479 		} else if (cf->file_flags & FILE_ON_DISK) {
480 			cvs_log(LP_NOTICE, "scheduling file '%s' for addition",
481 			    cf->file_path);
482 			added_files++;
483 		} else {
484 			stop = 1;
485 		}
486 		break;
487 	default:
488 		break;
489 	}
490 
491 	if (head != NULL)
492 		rcsnum_free(head);
493 
494 	if (stop == 1)
495 		return;
496 
497 	add_entry(cf);
498 }
499 
500 static void
501 add_entry(struct cvs_file *cf)
502 {
503 	FILE *fp;
504 	char *entry, path[MAXPATHLEN];
505 	char revbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ];
506 	char sticky[CVS_ENT_MAXLINELEN];
507 	CVSENTRIES *entlist;
508 
509 	if (cvs_noexec == 1)
510 		return;
511 
512 	sticky[0] = '\0';
513 	entry = xmalloc(CVS_ENT_MAXLINELEN);
514 
515 	if (cf->file_status == FILE_REMOVED) {
516 		rcsnum_tostr(cf->file_ent->ce_rev, revbuf, sizeof(revbuf));
517 
518 		ctime_r(&cf->file_ent->ce_mtime, tbuf);
519 		tbuf[strcspn(tbuf, "\n")] = '\0';
520 
521 		if (cf->file_ent->ce_tag != NULL)
522 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
523 			    cf->file_ent->ce_tag);
524 
525 		/* Remove the '-' prefixing the version number. */
526 		cvs_ent_line_str(cf->file_name, revbuf, tbuf,
527 		    cf->file_ent->ce_opts ? cf->file_ent->ce_opts : "", sticky,
528 		    0, 0, entry, CVS_ENT_MAXLINELEN);
529 	} else {
530 		if (logmsg != NULL) {
531 			(void)xsnprintf(path, MAXPATHLEN, "%s/%s/%s%s",
532 			    cf->file_wd, CVS_PATH_CVSDIR, cf->file_name,
533 			    CVS_DESCR_FILE_EXT);
534 
535 			if ((fp = fopen(path, "w+")) == NULL)
536 				fatal("add_entry: fopen `%s': %s",
537 				    path, strerror(errno));
538 
539 			if (fputs(logmsg, fp) == EOF) {
540 				(void)unlink(path);
541 				fatal("add_entry: fputs `%s': %s",
542 				    path, strerror(errno));
543 			}
544 			(void)fclose(fp);
545 		}
546 
547 		if (cvs_directory_tag != NULL)
548 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
549 			    cvs_directory_tag);
550 
551 		tbuf[0] = '\0';
552 		if (!cvs_server_active)
553 			(void)xsnprintf(tbuf, sizeof(tbuf), "Initial %s",
554 			    cf->file_name);
555 
556 		cvs_ent_line_str(cf->file_name, "0", tbuf, kflag ? kbuf : "",
557 		    sticky, 0, 0, entry, CVS_ENT_MAXLINELEN);
558 	}
559 
560 	if (cvs_server_active) {
561 		cvs_server_send_response("Checked-in %s/", cf->file_wd);
562 		cvs_server_send_response("%s", cf->file_path);
563 		cvs_server_send_response("%s", entry);
564 	} else {
565 		entlist = cvs_ent_open(cf->file_wd);
566 		cvs_ent_add(entlist, entry);
567 	}
568 	xfree(entry);
569 }
570