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
cvs_add(int argc,char ** argv)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
cvs_add_entry(struct cvs_file * cf)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
cvs_add_local(struct cvs_file * cf)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
cvs_add_remote(struct cvs_file * cf)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
cvs_add_loginfo(char * repo)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
cvs_add_tobranch(struct cvs_file * cf,char * tag)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
add_directory(struct cvs_file * cf)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
add_file(struct cvs_file * cf)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
add_entry(struct cvs_file * cf)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