xref: /openbsd/usr.bin/cvs/add.c (revision 09467b48)
1 /*	$OpenBSD: add.c,v 1.115 2019/06/28 13:35:00 deraadt 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 <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "cvs.h"
28 #include "remote.h"
29 
30 extern char *__progname;
31 
32 void	cvs_add_loginfo(char *);
33 void	cvs_add_entry(struct cvs_file *);
34 void	cvs_add_remote(struct cvs_file *);
35 
36 static void add_directory(struct cvs_file *);
37 static void add_file(struct cvs_file *);
38 static void add_entry(struct cvs_file *);
39 
40 int		kflag = 0;
41 static u_int	added_files = 0;
42 static char	kbuf[8];
43 
44 extern char	*logmsg;
45 extern char	*loginfo;
46 
47 struct cvs_cmd cvs_cmd_add = {
48 	CVS_OP_ADD, CVS_USE_WDIR, "add",
49 	{ "ad", "new" },
50 	"Add a new file or directory to the repository",
51 	"[-k mode] [-m message] ...",
52 	"k:m:",
53 	NULL,
54 	cvs_add
55 };
56 
57 int
58 cvs_add(int argc, char **argv)
59 {
60 	int ch;
61 	int flags;
62 	struct cvs_recursion cr;
63 
64 	flags = CR_REPO;
65 
66 	while ((ch = getopt(argc, argv, cvs_cmd_add.cmd_opts)) != -1) {
67 		switch (ch) {
68 		case 'k':
69 			kflag = rcs_kflag_get(optarg);
70 			if (RCS_KWEXP_INVAL(kflag)) {
71 				cvs_log(LP_ERR,
72 				    "invalid RCS keyword expansion mode");
73 				fatal("%s", cvs_cmd_add.cmd_synopsis);
74 			}
75 			(void)xsnprintf(kbuf, sizeof(kbuf), "-k%s", optarg);
76 			break;
77 		case 'm':
78 			logmsg = optarg;
79 			break;
80 		default:
81 			fatal("%s", cvs_cmd_add.cmd_synopsis);
82 		}
83 	}
84 
85 	argc -= optind;
86 	argv += optind;
87 
88 	if (argc == 0)
89 		fatal("%s", cvs_cmd_add.cmd_synopsis);
90 
91 	cr.enterdir = NULL;
92 	cr.leavedir = NULL;
93 
94 	if (cvsroot_is_remote()) {
95 		cvs_client_connect_to_server();
96 		cr.fileproc = cvs_add_remote;
97 		flags = 0;
98 
99 		if (kflag)
100 			cvs_client_send_request("Argument %s", kbuf);
101 
102 		if (logmsg != NULL)
103 			cvs_client_send_logmsg(logmsg);
104 	} else {
105 		if (logmsg != NULL && cvs_logmsg_verify(logmsg))
106 			return (0);
107 
108 		cr.fileproc = cvs_add_local;
109 	}
110 
111 	cr.flags = flags;
112 
113 	cvs_file_run(argc, argv, &cr);
114 
115 	if (added_files != 0) {
116 		cvs_log(LP_NOTICE, "use '%s commit' to add %s "
117 		    "permanently", __progname,
118 		    (added_files == 1) ? "this file" : "these files");
119 	}
120 
121 	if (cvsroot_is_remote()) {
122 		cvs_client_senddir(".");
123 		cvs_client_send_files(argv, argc);
124 		cvs_client_send_request("add");
125 		cvs_client_get_responses();
126 
127 		if (server_response == SERVER_OK) {
128 			cr.fileproc = cvs_add_entry;
129 			cvs_file_run(argc, argv, &cr);
130 		}
131 	}
132 
133 	return (0);
134 }
135 
136 void
137 cvs_add_entry(struct cvs_file *cf)
138 {
139 	char *entry;
140 	CVSENTRIES *entlist;
141 
142 	if (cf->file_type == CVS_DIR) {
143 		entry = xmalloc(CVS_ENT_MAXLINELEN);
144 		cvs_ent_line_str(cf->file_name, NULL, NULL, NULL, NULL, 1, 0,
145 		    entry, CVS_ENT_MAXLINELEN);
146 
147 		entlist = cvs_ent_open(cf->file_wd);
148 		cvs_ent_add(entlist, entry);
149 
150 		free(entry);
151 	} else {
152 		add_entry(cf);
153 	}
154 }
155 
156 void
157 cvs_add_local(struct cvs_file *cf)
158 {
159 	cvs_log(LP_TRACE, "cvs_add_local(%s)", cf->file_path);
160 
161 	if (cvs_cmdop != CVS_OP_CHECKOUT && cvs_cmdop != CVS_OP_UPDATE)
162 		cvs_file_classify(cf, cvs_directory_tag);
163 
164 	/* dont use `cvs add *' */
165 	if (strcmp(cf->file_name, ".") == 0 ||
166 	    strcmp(cf->file_name, "..") == 0 ||
167 	    strcmp(cf->file_name, CVS_PATH_CVSDIR) == 0) {
168 		if (verbosity > 1)
169 			cvs_log(LP_ERR,
170 			    "cannot add special file `%s'; skipping",
171 			    cf->file_name);
172 		return;
173 	}
174 
175 	if (cf->file_type == CVS_DIR)
176 		add_directory(cf);
177 	else
178 		add_file(cf);
179 }
180 
181 void
182 cvs_add_remote(struct cvs_file *cf)
183 {
184 	char path[PATH_MAX];
185 
186 	cvs_log(LP_TRACE, "cvs_add_remote(%s)", cf->file_path);
187 
188 	cvs_file_classify(cf, cvs_directory_tag);
189 
190 	if (cf->file_type == CVS_DIR) {
191 		cvs_get_repository_path(cf->file_wd, path, PATH_MAX);
192 		if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
193 			fatal("cvs_add_remote: truncation");
194 		if (strlcat(path, cf->file_path, sizeof(path)) >= sizeof(path))
195 			fatal("cvs_add_remote: truncation");
196 		cvs_client_send_request("Directory %s\n%s", cf->file_path,
197 		    path);
198 
199 		add_directory(cf);
200 	} else {
201 		cvs_client_sendfile(cf);
202 	}
203 }
204 
205 void
206 cvs_add_loginfo(char *repo)
207 {
208 	BUF *buf;
209 	char pwd[PATH_MAX];
210 
211 	if (getcwd(pwd, sizeof(pwd)) == NULL)
212 		fatal("Can't get working directory");
213 
214 	buf = buf_alloc(1024);
215 
216 	cvs_trigger_loginfo_header(buf, repo);
217 
218 	buf_puts(buf, "Log Message:\nDirectory ");
219 	buf_puts(buf, current_cvsroot->cr_dir);
220 	buf_putc(buf, '/');
221 	buf_puts(buf, repo);
222 	buf_puts(buf, " added to the repository\n");
223 
224 	buf_putc(buf, '\0');
225 
226 	loginfo = buf_release(buf);
227 }
228 
229 void
230 cvs_add_tobranch(struct cvs_file *cf, char *tag)
231 {
232 	BUF *bp;
233 	char attic[PATH_MAX], repo[PATH_MAX];
234 	char *msg;
235 	struct stat st;
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, PATH_MAX);
247 	(void)xsnprintf(attic, PATH_MAX, "%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, PATH_MAX, "%s/%s/%s%s", repo,
254 	    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
255 
256 	free(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 == -1)
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 	free(msg);
282 
283 	if (rcs_findrev(cf->file_rcs, cf->file_rcs->rf_head) == NULL)
284 		fatal("cvs_add_tobranch: cannot find newly added revision");
285 
286 	bp = 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[PATH_MAX], msg[1024], repo[PATH_MAX], *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, PATH_MAX, "%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, PATH_MAX, "%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 			    PATH_MAX);
350 
351 			(void)xsnprintf(entry, PATH_MAX, "%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 			free(p);
364 		}
365 	}
366 
367 	if (added == 1 && cvsroot_is_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 		free(tag);
386 		free(date);
387 
388 		cvs_get_repository_name(cf->file_path, repo, PATH_MAX);
389 		line_list = cvs_trigger_getlines(CVS_PATH_LOGINFO, repo);
390 		if (line_list != NULL) {
391 			TAILQ_INIT(&files_info);
392 			fi = xcalloc(1, sizeof(*fi));
393 			fi->file_path = xstrdup(cf->file_path);
394 			TAILQ_INSERT_TAIL(&files_info, fi, flist);
395 
396 			cvs_add_loginfo(repo);
397 			cvs_trigger_handle(CVS_TRIGGER_LOGINFO, repo,
398 			    loginfo, line_list, &files_info);
399 
400 			cvs_trigger_freeinfo(&files_info);
401 			cvs_trigger_freelist(line_list);
402 			free(loginfo);
403 		}
404 	}
405 
406 	cf->file_status = FILE_SKIP;
407 }
408 
409 static void
410 add_file(struct cvs_file *cf)
411 {
412 	int nb, stop;
413 	char revbuf[CVS_REV_BUFSZ];
414 	RCSNUM *head = NULL;
415 	char *tag;
416 
417 	cvs_parse_tagfile(cf->file_wd, &tag, NULL, &nb);
418 	if (nb) {
419 		cvs_log(LP_ERR, "cannot add file on non-branch tag %s", tag);
420 		return;
421 	}
422 
423 	if (cf->file_rcs != NULL) {
424 		head = rcs_head_get(cf->file_rcs);
425 		if (head == NULL) {
426 			cvs_log(LP_NOTICE, "no head revision in RCS file for "
427 			    "%s", cf->file_path);
428 		}
429 		rcsnum_tostr(head, revbuf, sizeof(revbuf));
430 	}
431 
432 	stop = 0;
433 	switch (cf->file_status) {
434 	case FILE_ADDED:
435 	case FILE_CHECKOUT:
436 		if (verbosity > 1)
437 			cvs_log(LP_NOTICE, "%s has already been entered",
438 			    cf->file_path);
439 		stop = 1;
440 		break;
441 	case FILE_REMOVED:
442 		if (cf->file_rcs == NULL) {
443 			cvs_log(LP_NOTICE, "cannot resurrect %s; "
444 			    "RCS file removed by second party", cf->file_name);
445 		} else if (!(cf->file_flags & FILE_ON_DISK)) {
446 			add_entry(cf);
447 
448 			/* Restore the file. */
449 			cvs_checkout_file(cf, head, NULL, 0);
450 
451 			cvs_printf("U %s\n", cf->file_path);
452 
453 			cvs_log(LP_NOTICE, "%s, version %s, resurrected",
454 			    cf->file_name, revbuf);
455 
456 			cf->file_status = FILE_UPTODATE;
457 		}
458 		stop = 1;
459 		break;
460 	case FILE_CONFLICT:
461 	case FILE_LOST:
462 	case FILE_MODIFIED:
463 	case FILE_UPTODATE:
464 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 0) {
465 			cvs_log(LP_NOTICE, "%s already exists, with version "
466 			     "number %s", cf->file_path, revbuf);
467 			stop = 1;
468 		}
469 		break;
470 	case FILE_UNKNOWN:
471 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1) {
472 			cvs_log(LP_NOTICE, "re-adding file %s "
473 			    "(instead of dead revision %s)",
474 			    cf->file_path, revbuf);
475 			added_files++;
476 		} else if (cf->file_flags & FILE_ON_DISK) {
477 			cvs_log(LP_NOTICE, "scheduling file '%s' for addition",
478 			    cf->file_path);
479 			added_files++;
480 		} else {
481 			stop = 1;
482 		}
483 		break;
484 	default:
485 		break;
486 	}
487 
488 	free(head);
489 
490 	if (stop == 1)
491 		return;
492 
493 	add_entry(cf);
494 }
495 
496 static void
497 add_entry(struct cvs_file *cf)
498 {
499 	FILE *fp;
500 	char *entry, path[PATH_MAX];
501 	char revbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ];
502 	char sticky[CVS_ENT_MAXLINELEN];
503 	CVSENTRIES *entlist;
504 
505 	if (cvs_noexec == 1)
506 		return;
507 
508 	sticky[0] = '\0';
509 	entry = xmalloc(CVS_ENT_MAXLINELEN);
510 
511 	if (cf->file_status == FILE_REMOVED) {
512 		rcsnum_tostr(cf->file_ent->ce_rev, revbuf, sizeof(revbuf));
513 
514 		ctime_r(&cf->file_ent->ce_mtime, tbuf);
515 		tbuf[strcspn(tbuf, "\n")] = '\0';
516 
517 		if (cf->file_ent->ce_tag != NULL)
518 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
519 			    cf->file_ent->ce_tag);
520 
521 		/* Remove the '-' prefixing the version number. */
522 		cvs_ent_line_str(cf->file_name, revbuf, tbuf,
523 		    cf->file_ent->ce_opts ? cf->file_ent->ce_opts : "", sticky,
524 		    0, 0, entry, CVS_ENT_MAXLINELEN);
525 	} else {
526 		if (logmsg != NULL) {
527 			(void)xsnprintf(path, PATH_MAX, "%s/%s/%s%s",
528 			    cf->file_wd, CVS_PATH_CVSDIR, cf->file_name,
529 			    CVS_DESCR_FILE_EXT);
530 
531 			if ((fp = fopen(path, "w+")) == NULL)
532 				fatal("add_entry: fopen `%s': %s",
533 				    path, strerror(errno));
534 
535 			if (fputs(logmsg, fp) == EOF) {
536 				(void)unlink(path);
537 				fatal("add_entry: fputs `%s': %s",
538 				    path, strerror(errno));
539 			}
540 			(void)fclose(fp);
541 		}
542 
543 		if (cvs_directory_tag != NULL)
544 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
545 			    cvs_directory_tag);
546 
547 		tbuf[0] = '\0';
548 		if (!cvs_server_active)
549 			(void)xsnprintf(tbuf, sizeof(tbuf), "Initial %s",
550 			    cf->file_name);
551 
552 		cvs_ent_line_str(cf->file_name, "0", tbuf, kflag ? kbuf : "",
553 		    sticky, 0, 0, entry, CVS_ENT_MAXLINELEN);
554 	}
555 
556 	if (cvs_server_active) {
557 		cvs_server_send_response("Checked-in %s/", cf->file_wd);
558 		cvs_server_send_response("%s", cf->file_path);
559 		cvs_server_send_response("%s", entry);
560 	} else {
561 		entlist = cvs_ent_open(cf->file_wd);
562 		cvs_ent_add(entlist, entry);
563 	}
564 	free(entry);
565 }
566