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