xref: /openbsd/usr.bin/cvs/add.c (revision e28eda4e)
1 /*	$OpenBSD: add.c,v 1.104 2008/06/14 04:34:07 tobias 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 <string.h>
23 #include <unistd.h>
24 
25 #include "cvs.h"
26 #include "remote.h"
27 
28 extern char *__progname;
29 
30 void	cvs_add_loginfo(char *);
31 void	cvs_add_entry(struct cvs_file *);
32 void	cvs_add_remote(struct cvs_file *);
33 
34 static void add_directory(struct cvs_file *);
35 static void add_file(struct cvs_file *);
36 static void add_entry(struct cvs_file *);
37 
38 int		kflag = 0;
39 static char	kbuf[8];
40 
41 extern char	*logmsg;
42 extern char	*loginfo;
43 
44 struct cvs_cmd cvs_cmd_add = {
45 	CVS_OP_ADD, CVS_USE_WDIR, "add",
46 	{ "ad", "new" },
47 	"Add a new file or directory to the repository",
48 	"[-k mode] [-m message] ...",
49 	"k:m:",
50 	NULL,
51 	cvs_add
52 };
53 
54 int
55 cvs_add(int argc, char **argv)
56 {
57 	int ch;
58 	int flags;
59 	struct cvs_recursion cr;
60 
61 	flags = CR_REPO;
62 
63 	while ((ch = getopt(argc, argv, cvs_cmd_add.cmd_opts)) != -1) {
64 		switch (ch) {
65 		case 'k':
66 			kflag = rcs_kflag_get(optarg);
67 			if (RCS_KWEXP_INVAL(kflag)) {
68 				cvs_log(LP_ERR,
69 				    "invalid RCS keyword expansion mode");
70 				fatal("%s", cvs_cmd_add.cmd_synopsis);
71 			}
72 			(void)xsnprintf(kbuf, sizeof(kbuf), "-k%s", optarg);
73 			break;
74 		case 'm':
75 			logmsg = optarg;
76 			break;
77 		default:
78 			fatal("%s", cvs_cmd_add.cmd_synopsis);
79 		}
80 	}
81 
82 	argc -= optind;
83 	argv += optind;
84 
85 	if (argc == 0)
86 		fatal("%s", cvs_cmd_add.cmd_synopsis);
87 
88 	cr.enterdir = NULL;
89 	cr.leavedir = NULL;
90 
91 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
92 		cvs_client_connect_to_server();
93 		cr.fileproc = cvs_add_remote;
94 		flags = 0;
95 
96 		if (kflag)
97 			cvs_client_send_request("Argument %s", kbuf);
98 
99 		if (logmsg != NULL)
100 			cvs_client_send_logmsg(logmsg);
101 	} else {
102 		if (logmsg != NULL && cvs_logmsg_verify(logmsg))
103 			return (0);
104 
105 		cr.fileproc = cvs_add_local;
106 	}
107 
108 	cr.flags = flags;
109 
110 	cvs_file_run(argc, argv, &cr);
111 
112 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
113 		cvs_client_senddir(".");
114 		cvs_client_send_files(argv, argc);
115 		cvs_client_send_request("add");
116 		cvs_client_get_responses();
117 
118 		if (server_response == SERVER_OK) {
119 			cr.fileproc = cvs_add_entry;
120 			cvs_file_run(argc, argv, &cr);
121 		}
122 	}
123 
124 	return (0);
125 }
126 
127 void
128 cvs_add_entry(struct cvs_file *cf)
129 {
130 	char *entry;
131 	CVSENTRIES *entlist;
132 
133 	if (cf->file_type == CVS_DIR) {
134 		entry = xmalloc(CVS_ENT_MAXLINELEN);
135 		cvs_ent_line_str(cf->file_name, NULL, NULL, NULL, NULL, 1, 0,
136 		    entry, CVS_ENT_MAXLINELEN);
137 
138 		entlist = cvs_ent_open(cf->file_wd);
139 		cvs_ent_add(entlist, entry);
140 
141 		xfree(entry);
142 	} else {
143 		add_entry(cf);
144 	}
145 }
146 
147 void
148 cvs_add_local(struct cvs_file *cf)
149 {
150 	cvs_log(LP_TRACE, "cvs_add_local(%s)", cf->file_path);
151 
152 	if (cvs_cmdop != CVS_OP_CHECKOUT && cvs_cmdop != CVS_OP_UPDATE)
153 		cvs_file_classify(cf, cvs_directory_tag);
154 
155 	/* dont use `cvs add *' */
156 	if (strcmp(cf->file_name, ".") == 0 ||
157 	    strcmp(cf->file_name, "..") == 0 ||
158 	    strcmp(cf->file_name, CVS_PATH_CVSDIR) == 0) {
159 		if (verbosity > 1)
160 			cvs_log(LP_ERR,
161 			    "cannot add special file `%s'; skipping",
162 			    cf->file_name);
163 		return;
164 	}
165 
166 	if (cf->file_type == CVS_DIR)
167 		add_directory(cf);
168 	else
169 		add_file(cf);
170 }
171 
172 void
173 cvs_add_remote(struct cvs_file *cf)
174 {
175 	char path[MAXPATHLEN];
176 
177 	cvs_log(LP_TRACE, "cvs_add_remote(%s)", cf->file_path);
178 
179 	cvs_file_classify(cf, cvs_directory_tag);
180 
181 	if (cf->file_type == CVS_DIR) {
182 		cvs_get_repository_path(cf->file_wd, path, MAXPATHLEN);
183 		if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
184 			fatal("cvs_add_remote: truncation");
185 		if (strlcat(path, cf->file_path, sizeof(path)) >= sizeof(path))
186 			fatal("cvs_add_remote: truncation");
187 		cvs_client_send_request("Directory %s\n%s", cf->file_path,
188 		    path);
189 
190 		add_directory(cf);
191 	} else {
192 		cvs_client_sendfile(cf);
193 	}
194 }
195 
196 void
197 cvs_add_loginfo(char *repo)
198 {
199 	BUF *buf;
200 	char pwd[MAXPATHLEN];
201 
202 	if (getcwd(pwd, sizeof(pwd)) == NULL)
203 		fatal("Can't get working directory");
204 
205 	buf = cvs_buf_alloc(1024);
206 
207 	cvs_trigger_loginfo_header(buf, repo);
208 
209 	cvs_buf_puts(buf, "Log Message:\nDirectory ");
210 	cvs_buf_puts(buf, current_cvsroot->cr_dir);
211 	cvs_buf_putc(buf, '/');
212 	cvs_buf_puts(buf, repo);
213 	cvs_buf_puts(buf, " added to the repository\n");
214 
215 	cvs_buf_putc(buf, '\0');
216 
217 	loginfo = cvs_buf_release(buf);
218 }
219 
220 static void
221 add_directory(struct cvs_file *cf)
222 {
223 	int added, nb;
224 	struct stat st;
225 	CVSENTRIES *entlist;
226 	char *date, entry[MAXPATHLEN], msg[1024], repo[MAXPATHLEN], *tag, *p;
227 	struct file_info_list files_info;
228 	struct file_info *fi;
229 	struct trigger_list *line_list;
230 
231 	cvs_log(LP_TRACE, "add_directory(%s)", cf->file_path);
232 
233 	(void)xsnprintf(entry, MAXPATHLEN, "%s%s",
234 	    cf->file_rpath, RCS_FILE_EXT);
235 
236 	added = 1;
237 	if (stat(entry, &st) != -1) {
238 		cvs_log(LP_NOTICE, "cannot add directory %s: "
239 		    "a file with that name already exists",
240 		    cf->file_path);
241 		added = 0;
242 	} else {
243 		/* Let's see if we have any per-directory tags first. */
244 		cvs_parse_tagfile(cf->file_wd, &tag, &date, &nb);
245 
246 		(void)xsnprintf(entry, MAXPATHLEN, "%s/%s",
247 		    cf->file_path, CVS_PATH_CVSDIR);
248 
249 		if (cvs_server_active) {
250 			if (mkdir(cf->file_rpath, 0755) == -1 &&
251 			    errno != EEXIST)
252 				fatal("add_directory: %s: %s", cf->file_rpath,
253 				    strerror(errno));
254 		} else if (stat(entry, &st) != -1) {
255 			if (!S_ISDIR(st.st_mode)) {
256 				cvs_log(LP_ERR, "%s exists but is not "
257 				    "directory", entry);
258 			} else {
259 				cvs_log(LP_NOTICE, "%s already exists",
260 				    entry);
261 			}
262 			added = 0;
263 		} else if (cvs_noexec != 1) {
264 			if (mkdir(cf->file_rpath, 0755) == -1 &&
265 			    errno != EEXIST)
266 				fatal("add_directory: %s: %s", cf->file_rpath,
267 				    strerror(errno));
268 
269 			cvs_get_repository_name(cf->file_wd, repo,
270 			    MAXPATHLEN);
271 
272 			(void)xsnprintf(entry, MAXPATHLEN, "%s/%s",
273 			    repo, cf->file_name);
274 
275 			cvs_mkadmin(cf->file_path, current_cvsroot->cr_dir,
276 			    entry, tag, date);
277 
278 			p = xmalloc(CVS_ENT_MAXLINELEN);
279 			cvs_ent_line_str(cf->file_name, NULL, NULL, NULL,
280 			    NULL, 1, 0, p, CVS_ENT_MAXLINELEN);
281 
282 			entlist = cvs_ent_open(cf->file_wd);
283 			cvs_ent_add(entlist, p);
284 			xfree(p);
285 		}
286 	}
287 
288 	if (added == 1 && current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
289 		(void)xsnprintf(msg, sizeof(msg),
290 		    "Directory %s added to the repository", cf->file_rpath);
291 
292 		if (tag != NULL) {
293 			(void)strlcat(msg,
294 			    "\n--> Using per-directory sticky tag ",
295 			    sizeof(msg));
296 			(void)strlcat(msg, tag, sizeof(msg));
297 		}
298 		if (date != NULL) {
299 			(void)strlcat(msg,
300 			    "\n--> Using per-directory sticky date ",
301 			    sizeof(msg));
302 			(void)strlcat(msg, date, sizeof(msg));
303 		}
304 		cvs_printf("%s\n", msg);
305 
306 		if (tag != NULL)
307 			xfree(tag);
308 		if (date != NULL)
309 			xfree(date);
310 
311 		cvs_get_repository_name(cf->file_path, repo, MAXPATHLEN);
312 		line_list = cvs_trigger_getlines(CVS_PATH_LOGINFO, repo);
313 		if (line_list != NULL) {
314 			TAILQ_INIT(&files_info);
315 			fi = xcalloc(1, sizeof(*fi));
316 			fi->file_path = xstrdup(cf->file_path);
317 			TAILQ_INSERT_TAIL(&files_info, fi, flist);
318 
319 			cvs_add_loginfo(repo);
320 			cvs_trigger_handle(CVS_TRIGGER_LOGINFO, repo,
321 			    loginfo, line_list, &files_info);
322 
323 			cvs_trigger_freeinfo(&files_info);
324 			cvs_trigger_freelist(line_list);
325 			if (loginfo != NULL)
326 				xfree(loginfo);
327 		}
328 	}
329 
330 	cf->file_status = FILE_SKIP;
331 }
332 
333 static void
334 add_file(struct cvs_file *cf)
335 {
336 	int added, nb, stop;
337 	char revbuf[CVS_REV_BUFSZ];
338 	RCSNUM *head = NULL;
339 	char *tag;
340 
341 	cvs_parse_tagfile(cf->file_wd, &tag, NULL, &nb);
342 	if (nb) {
343 		cvs_log(LP_ERR, "cannot add file on non-branch tag %s", tag);
344 		return;
345 	}
346 
347 	if (cf->file_rcs != NULL) {
348 		head = rcs_head_get(cf->file_rcs);
349 		if (head == NULL) {
350 			cvs_log(LP_NOTICE, "no head revision in RCS file for "
351 			    "%s", cf->file_path);
352 		}
353 		rcsnum_tostr(head, revbuf, sizeof(revbuf));
354 	}
355 
356 	added = stop = 0;
357 	switch (cf->file_status) {
358 	case FILE_ADDED:
359 	case FILE_CHECKOUT:
360 		if (verbosity > 1)
361 			cvs_log(LP_NOTICE, "%s has already been entered",
362 			    cf->file_path);
363 		stop = 1;
364 		break;
365 	case FILE_REMOVED:
366 		if (cf->file_rcs == NULL) {
367 			cvs_log(LP_NOTICE, "cannot resurrect %s; "
368 			    "RCS file removed by second party", cf->file_name);
369 		} else if (cf->fd == -1) {
370 			add_entry(cf);
371 
372 			/* Restore the file. */
373 			cvs_checkout_file(cf, head, NULL, 0);
374 
375 			cvs_printf("U %s\n", cf->file_path);
376 
377 			cvs_log(LP_NOTICE, "%s, version %s, resurrected",
378 			    cf->file_name, revbuf);
379 
380 			cf->file_status = FILE_UPTODATE;
381 		}
382 		stop = 1;
383 		break;
384 	case FILE_CONFLICT:
385 	case FILE_LOST:
386 	case FILE_MODIFIED:
387 	case FILE_UPTODATE:
388 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 0) {
389 			cvs_log(LP_NOTICE, "%s already exists, with version "
390 			     "number %s", cf->file_path, revbuf);
391 			stop = 1;
392 		}
393 		break;
394 	case FILE_UNKNOWN:
395 		if (cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1) {
396 			cvs_log(LP_NOTICE, "re-adding file %s "
397 			    "(instead of dead revision %s)",
398 			    cf->file_path, revbuf);
399 			added++;
400 		} else if (cf->fd != -1) {
401 			cvs_log(LP_NOTICE, "scheduling file '%s' for addition",
402 			    cf->file_path);
403 			added++;
404 		} else {
405 			stop = 1;
406 		}
407 		break;
408 	default:
409 		break;
410 	}
411 
412 	if (head != NULL)
413 		rcsnum_free(head);
414 
415 	if (stop == 1)
416 		return;
417 
418 	add_entry(cf);
419 
420 	if (added != 0) {
421 		cvs_log(LP_NOTICE, "use '%s commit' to add %s "
422 		    "permanently", __progname,
423 		    (added == 1) ? "this file" : "these files");
424 	}
425 }
426 
427 static void
428 add_entry(struct cvs_file *cf)
429 {
430 	FILE *fp;
431 	char *entry, path[MAXPATHLEN];
432 	char revbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ];
433 	char sticky[CVS_ENT_MAXLINELEN];
434 	CVSENTRIES *entlist;
435 
436 	if (cvs_noexec == 1)
437 		return;
438 
439 	sticky[0] = '\0';
440 	entry = xmalloc(CVS_ENT_MAXLINELEN);
441 
442 	if (cf->file_status == FILE_REMOVED) {
443 		rcsnum_tostr(cf->file_ent->ce_rev, revbuf, sizeof(revbuf));
444 
445 		ctime_r(&cf->file_ent->ce_mtime, tbuf);
446 		tbuf[strcspn(tbuf, "\n")] = '\0';
447 
448 		if (cf->file_ent->ce_tag != NULL)
449 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
450 			    cf->file_ent->ce_tag);
451 
452 		/* Remove the '-' prefixing the version number. */
453 		cvs_ent_line_str(cf->file_name, revbuf, tbuf,
454 		    cf->file_ent->ce_opts ? cf->file_ent->ce_opts : "", sticky,
455 		    0, 0, entry, CVS_ENT_MAXLINELEN);
456 	} else {
457 		if (logmsg != NULL) {
458 			(void)xsnprintf(path, MAXPATHLEN, "%s/%s/%s%s",
459 			    cf->file_wd, CVS_PATH_CVSDIR, cf->file_name,
460 			    CVS_DESCR_FILE_EXT);
461 
462 			if ((fp = fopen(path, "w+")) == NULL)
463 				fatal("add_entry: fopen `%s': %s",
464 				    path, strerror(errno));
465 
466 			if (fputs(logmsg, fp) == EOF) {
467 				(void)unlink(path);
468 				fatal("add_entry: fputs `%s': %s",
469 				    path, strerror(errno));
470 			}
471 			(void)fclose(fp);
472 		}
473 
474 		if (cvs_directory_tag != NULL)
475 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
476 			    cvs_directory_tag);
477 
478 		tbuf[0] = '\0';
479 		if (!cvs_server_active)
480 			(void)xsnprintf(tbuf, sizeof(tbuf), "Initial %s",
481 			    cf->file_name);
482 
483 		cvs_ent_line_str(cf->file_name, "0", tbuf, kflag ? kbuf : "",
484 		    sticky, 0, 0, entry, CVS_ENT_MAXLINELEN);
485 	}
486 
487 	if (cvs_server_active) {
488 		cvs_server_send_response("Checked-in %s/", cf->file_wd);
489 		cvs_server_send_response(cf->file_path);
490 		cvs_server_send_response(entry);
491 	} else {
492 		entlist = cvs_ent_open(cf->file_wd);
493 		cvs_ent_add(entlist, entry);
494 	}
495 	xfree(entry);
496 }
497