xref: /openbsd-src/usr.bin/cvs/entries.c (revision 850e275390052b330d93020bf619a739a3c277ac)
1 /*	$OpenBSD: entries.c,v 1.100 2008/06/14 20:04:14 joris Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <errno.h>
19 #include <string.h>
20 #include <time.h>
21 #include <unistd.h>
22 
23 #include "cvs.h"
24 #include "remote.h"
25 
26 #define CVS_ENTRIES_NFIELDS	6
27 #define CVS_ENTRIES_DELIM	'/'
28 
29 static struct cvs_ent_line *ent_get_line(CVSENTRIES *, const char *);
30 
31 CVSENTRIES *current_list = NULL;
32 
33 CVSENTRIES *
34 cvs_ent_open(const char *dir)
35 {
36 	FILE *fp;
37 	CVSENTRIES *ep;
38 	char *p, buf[MAXPATHLEN];
39 	struct cvs_ent *ent;
40 	struct cvs_ent_line *line;
41 
42 	cvs_log(LP_TRACE, "cvs_ent_open(%s)", dir);
43 
44 	(void)xsnprintf(buf, sizeof(buf), "%s/%s", dir, CVS_PATH_ENTRIES);
45 
46 	if (current_list != NULL && !strcmp(current_list->cef_path, buf))
47 		return (current_list);
48 
49 	if (current_list != NULL) {
50 		cvs_ent_close(current_list, ENT_SYNC);
51 		current_list = NULL;
52 	}
53 
54 	ep = (CVSENTRIES *)xcalloc(1, sizeof(*ep));
55 	ep->cef_path = xstrdup(buf);
56 
57 	(void)xsnprintf(buf, sizeof(buf), "%s/%s",
58 	    dir, CVS_PATH_BACKUPENTRIES);
59 
60 	ep->cef_bpath = xstrdup(buf);
61 
62 	(void)xsnprintf(buf, sizeof(buf), "%s/%s", dir, CVS_PATH_LOGENTRIES);
63 
64 	ep->cef_lpath = xstrdup(buf);
65 
66 	TAILQ_INIT(&(ep->cef_ent));
67 
68 	if ((fp = fopen(ep->cef_path, "r")) != NULL) {
69 		while (fgets(buf, sizeof(buf), fp)) {
70 			buf[strcspn(buf, "\n")] = '\0';
71 
72 			if (buf[0] == 'D' && buf[1] == '\0')
73 				break;
74 
75 			line = (struct cvs_ent_line *)xmalloc(sizeof(*line));
76 			line->buf = xstrdup(buf);
77 			TAILQ_INSERT_TAIL(&(ep->cef_ent), line, entries_list);
78 		}
79 
80 		(void)fclose(fp);
81 	}
82 
83 	if ((fp = fopen(ep->cef_lpath, "r")) != NULL) {
84 		while (fgets(buf, sizeof(buf), fp)) {
85 			buf[strcspn(buf, "\n")] = '\0';
86 
87 			if (strlen(buf) < 2)
88 				fatal("cvs_ent_open: %s: malformed line %s",
89 				    ep->cef_lpath, buf);
90 
91 			p = &buf[2];
92 
93 			if (buf[0] == 'A') {
94 				line = xmalloc(sizeof(*line));
95 				line->buf = xstrdup(p);
96 				TAILQ_INSERT_TAIL(&(ep->cef_ent), line,
97 				    entries_list);
98 			} else if (buf[0] == 'R') {
99 				ent = cvs_ent_parse(p);
100 				line = ent_get_line(ep, ent->ce_name);
101 				if (line != NULL) {
102 					TAILQ_REMOVE(&(ep->cef_ent), line,
103 					    entries_list);
104 					xfree(line->buf);
105 					xfree(line);
106 				}
107 				cvs_ent_free(ent);
108 			}
109 		}
110 
111 		(void)fclose(fp);
112 	}
113 
114 	current_list = ep;
115 	return (ep);
116 }
117 
118 struct cvs_ent *
119 cvs_ent_parse(const char *entry)
120 {
121 	int i;
122 	struct tm t, dt;
123 	struct cvs_ent *ent;
124 	char *fields[CVS_ENTRIES_NFIELDS], *buf, *sp, *dp, *p;
125 
126 	buf = sp = xstrdup(entry);
127 	i = 0;
128 	do {
129 		dp = strchr(sp, CVS_ENTRIES_DELIM);
130 		if (dp != NULL)
131 			*(dp++) = '\0';
132 		fields[i++] = sp;
133 		sp = dp;
134 	} while (dp != NULL && i < CVS_ENTRIES_NFIELDS);
135 
136 	if (i < CVS_ENTRIES_NFIELDS)
137 		fatal("missing fields in entry line '%s'", entry);
138 
139 	ent = xmalloc(sizeof(*ent));
140 	ent->ce_buf = buf;
141 
142 	if (*fields[0] == '\0')
143 		ent->ce_type = CVS_ENT_FILE;
144 	else if (*fields[0] == 'D')
145 		ent->ce_type = CVS_ENT_DIR;
146 	else
147 		ent->ce_type = CVS_ENT_NONE;
148 
149 	ent->ce_status = CVS_ENT_REG;
150 	ent->ce_name = fields[1];
151 	ent->ce_rev = NULL;
152 	ent->ce_date = -1;
153 	ent->ce_tag = NULL;
154 
155 	if (ent->ce_type == CVS_ENT_FILE) {
156 		if (*fields[2] == '-') {
157 			ent->ce_status = CVS_ENT_REMOVED;
158 			sp = fields[2] + 1;
159 		} else {
160 			sp = fields[2];
161 			if (fields[2][0] == '0' && fields[2][1] == '\0')
162 				ent->ce_status = CVS_ENT_ADDED;
163 		}
164 
165 		if ((ent->ce_rev = rcsnum_parse(sp)) == NULL)
166 			fatal("failed to parse entry revision '%s'", entry);
167 
168 		if (fields[3][0] == '\0' ||
169 		    strncmp(fields[3], CVS_DATE_DUMMY, sizeof(CVS_DATE_DUMMY) - 1) == 0 ||
170 		    strncmp(fields[3], "Initial ", 8) == 0 ||
171 		    strcmp(fields[3], "Result of merge") == 0) {
172 			ent->ce_mtime = CVS_DATE_DMSEC;
173 		} else if (cvs_server_active == 1 &&
174 		    strncmp(fields[3], CVS_SERVER_UNCHANGED,
175 		    strlen(CVS_SERVER_UNCHANGED)) == 0) {
176 			ent->ce_mtime = CVS_SERVER_UPTODATE;
177 		} else {
178 			p = fields[3];
179 			if (strncmp(fields[3], "Result of merge+", 16) == 0)
180 				p += 16;
181 
182 			/* Date field can be a '+=' with remote to indicate
183 			 * conflict.  In this case do nothing. */
184 			if (strptime(p, "%a %b %d %T %Y", &t) != NULL) {
185 
186 				t.tm_isdst = -1;	/* Figure out DST. */
187 				t.tm_gmtoff = 0;
188 				ent->ce_mtime = mktime(&t);
189 				ent->ce_mtime += t.tm_gmtoff;
190 			}
191 		}
192 	}
193 
194 	ent->ce_conflict = fields[3];
195 	if ((dp = strchr(ent->ce_conflict, '+')) != NULL)
196 		*dp = '\0';
197 	else
198 		ent->ce_conflict = NULL;
199 
200 	if (strcmp(fields[4], ""))
201 		ent->ce_opts = fields[4];
202 	else
203 		ent->ce_opts = NULL;
204 
205 	if (strcmp(fields[5], "")) {
206 		switch (*fields[5]) {
207 		case 'D':
208 			if (sscanf(fields[5] + 1, "%d.%d.%d.%d.%d.%d",
209 			    &dt.tm_year, &dt.tm_mon, &dt.tm_mday,
210 			    &dt.tm_hour, &dt.tm_min, &dt.tm_sec) != 6)
211 				fatal("wrong date specification");
212 			dt.tm_year -= 1900;
213 			dt.tm_mon -= 1;
214 			ent->ce_date = timegm(&dt);
215 			ent->ce_tag = NULL;
216 			break;
217 		case 'T':
218 			ent->ce_tag = fields[5] + 1;
219 			break;
220 		default:
221 			fatal("invalid sticky entry");
222 		}
223 	}
224 
225 	return (ent);
226 }
227 
228 struct cvs_ent *
229 cvs_ent_get(CVSENTRIES *ep, const char *name)
230 {
231 	struct cvs_ent *ent;
232 	struct cvs_ent_line *l;
233 
234 	l = ent_get_line(ep, name);
235 	if (l == NULL)
236 		return (NULL);
237 
238 	ent = cvs_ent_parse(l->buf);
239 	return (ent);
240 }
241 
242 void
243 cvs_ent_close(CVSENTRIES *ep, int writefile)
244 {
245 	FILE *fp;
246 	struct cvs_ent_line *l;
247 	int dflag;
248 
249 	dflag = 1;
250 	cvs_log(LP_TRACE, "cvs_ent_close(%s, %d)", ep->cef_bpath, writefile);
251 
252 	if (cvs_cmdop == CVS_OP_EXPORT)
253 		writefile = 0;
254 
255 	fp = NULL;
256 	if (writefile)
257 		fp = fopen(ep->cef_bpath, "w");
258 
259 	while ((l = TAILQ_FIRST(&(ep->cef_ent))) != NULL) {
260 		if (fp != NULL) {
261 			if (l->buf[0] == 'D')
262 				dflag = 0;
263 
264 			fputs(l->buf, fp);
265 			fputc('\n', fp);
266 		}
267 
268 		TAILQ_REMOVE(&(ep->cef_ent), l, entries_list);
269 		xfree(l->buf);
270 		xfree(l);
271 	}
272 
273 	if (fp != NULL) {
274 		if (dflag) {
275 			fputc('D', fp);
276 			fputc('\n', fp);
277 		}
278 		(void)fclose(fp);
279 
280 		if (rename(ep->cef_bpath, ep->cef_path) == -1)
281 			fatal("cvs_ent_close: rename: `%s'->`%s': %s",
282 			    ep->cef_bpath, ep->cef_path, strerror(errno));
283 
284 		(void)unlink(ep->cef_lpath);
285 	}
286 
287 	xfree(ep->cef_path);
288 	xfree(ep->cef_bpath);
289 	xfree(ep->cef_lpath);
290 	xfree(ep);
291 }
292 
293 void
294 cvs_ent_add(CVSENTRIES *ep, const char *line)
295 {
296 	FILE *fp;
297 	struct cvs_ent_line *l;
298 	struct cvs_ent *ent;
299 
300 	if ((ent = cvs_ent_parse(line)) == NULL)
301 		fatal("cvs_ent_add: parsing failed '%s'", line);
302 
303 	l = ent_get_line(ep, ent->ce_name);
304 	if (l != NULL)
305 		cvs_ent_remove(ep, ent->ce_name);
306 
307 	cvs_ent_free(ent);
308 
309 	if (cvs_server_active == 0)
310 		cvs_log(LP_TRACE, "cvs_ent_add(%s, %s)", ep->cef_path, line);
311 
312 	if ((fp = fopen(ep->cef_lpath, "a")) == NULL)
313 		fatal("cvs_ent_add: fopen: `%s': %s",
314 		    ep->cef_lpath, strerror(errno));
315 
316 	fputs("A ", fp);
317 	fputs(line, fp);
318 	fputc('\n', fp);
319 
320 	(void)fclose(fp);
321 
322 	l = (struct cvs_ent_line *)xmalloc(sizeof(*l));
323 	l->buf = xstrdup(line);
324 	TAILQ_INSERT_TAIL(&(ep->cef_ent), l, entries_list);
325 }
326 
327 void
328 cvs_ent_remove(CVSENTRIES *ep, const char *name)
329 {
330 	FILE *fp;
331 	struct cvs_ent_line *l;
332 
333 	if (cvs_server_active == 0)
334 		cvs_log(LP_TRACE, "cvs_ent_remove(%s, %s)", ep->cef_path, name);
335 
336 	l = ent_get_line(ep, name);
337 	if (l == NULL)
338 		return;
339 
340 	if ((fp = fopen(ep->cef_lpath, "a")) == NULL)
341 		fatal("cvs_ent_remove: fopen: `%s': %s", ep->cef_lpath,
342 		    strerror(errno));
343 
344 	fputs("R ", fp);
345 	fputs(l->buf, fp);
346 	fputc('\n', fp);
347 
348 	(void)fclose(fp);
349 
350 	TAILQ_REMOVE(&(ep->cef_ent), l, entries_list);
351 	xfree(l->buf);
352 	xfree(l);
353 }
354 
355 /*
356  * cvs_ent_line_str()
357  *
358  * Build CVS/Entries line.
359  *
360  */
361 void
362 cvs_ent_line_str(const char *name, char *rev, char *tstamp, char *opts,
363     char *sticky, int isdir, int isremoved, char *buf, size_t len)
364 {
365 	if (isdir == 1) {
366 		(void)xsnprintf(buf, len, "D/%s////", name);
367 		return;
368 	}
369 
370 	(void)xsnprintf(buf, len, "/%s/%s%s/%s/%s/%s",
371 	    name, isremoved == 1 ? "-" : "", rev, tstamp, opts, sticky);
372 }
373 
374 void
375 cvs_ent_free(struct cvs_ent *ent)
376 {
377 	if (ent->ce_rev != NULL)
378 		rcsnum_free(ent->ce_rev);
379 	xfree(ent->ce_buf);
380 	xfree(ent);
381 }
382 
383 static struct cvs_ent_line *
384 ent_get_line(CVSENTRIES *ep, const char *name)
385 {
386 	char *p, *s;
387 	struct cvs_ent_line *l;
388 
389 	TAILQ_FOREACH(l, &(ep->cef_ent), entries_list) {
390 		if (l->buf[0] == 'D')
391 			p = &(l->buf[2]);
392 		else
393 			p = &(l->buf[1]);
394 
395 		if ((s = strchr(p, '/')) == NULL)
396 			fatal("ent_get_line: bad entry line '%s'", l->buf);
397 
398 		*s = '\0';
399 
400 		if (!strcmp(p, name)) {
401 			*s = '/';
402 			return (l);
403 		}
404 
405 		*s = '/';
406 	}
407 
408 	return (NULL);
409 }
410 
411 void
412 cvs_parse_tagfile(char *dir, char **tagp, char **datep, int *nbp)
413 {
414 	FILE *fp;
415 	int i, linenum;
416 	size_t len;
417 	struct tm datetm;
418 	char linebuf[128], tagpath[MAXPATHLEN];
419 
420 	cvs_directory_date = -1;
421 
422 	if (tagp != NULL)
423 		*tagp = NULL;
424 
425 	if (datep != NULL)
426 		*datep = NULL;
427 
428 	if (nbp != NULL)
429 		*nbp = 0;
430 
431 	i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG);
432 	if (i < 0 || i >= MAXPATHLEN)
433 		return;
434 
435 	if ((fp = fopen(tagpath, "r")) == NULL) {
436 		if (errno != ENOENT)
437 			cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath,
438 			    strerror(errno));
439 		return;
440         }
441 
442 	linenum = 0;
443 
444 	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
445 		linenum++;
446 		if ((len = strlen(linebuf)) == 0)
447 			continue;
448 		if (linebuf[len - 1] != '\n') {
449 			cvs_log(LP_NOTICE, "line too long in `%s:%d'",
450 			    tagpath, linenum);
451 			break;
452 		}
453 		linebuf[--len] = '\0';
454 
455 		switch (*linebuf) {
456 		case 'T':
457 			if (tagp != NULL)
458 				*tagp = xstrdup(linebuf + 1);
459 			break;
460 		case 'D':
461 			if (sscanf(linebuf + 1, "%d.%d.%d.%d.%d.%d",
462 			    &datetm.tm_year, &datetm.tm_mon, &datetm.tm_mday,
463 			    &datetm.tm_hour, &datetm.tm_min, &datetm.tm_sec) !=
464 			    6)
465 				fatal("wrong date specification");
466 			datetm.tm_year -= 1900;
467 			datetm.tm_mon -= 1;
468 
469 			cvs_directory_date = timegm(&datetm);
470 
471 			if (datep != NULL)
472 				*datep = xstrdup(linebuf + 1);
473 			break;
474 		case 'N':
475 			if (tagp != NULL)
476 				*tagp = xstrdup(linebuf + 1);
477 			if (nbp != NULL)
478 				*nbp = 1;
479 			break;
480 		default:
481 			break;
482 		}
483 	}
484 	if (ferror(fp))
485 		cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath);
486 
487 	(void)fclose(fp);
488 }
489 
490 void
491 cvs_write_tagfile(const char *dir, char *tag, char *date)
492 {
493 	FILE *fp;
494 	RCSNUM *rev;
495 	char tagpath[MAXPATHLEN];
496 	char sticky[CVS_REV_BUFSZ];
497 	struct tm datetm;
498 	int i;
499 
500 	cvs_log(LP_TRACE, "cvs_write_tagfile(%s, %s, %s)", dir,
501 	    tag != NULL ? tag : "", date != NULL ? date : "");
502 
503 	if (cvs_noexec == 1)
504 		return;
505 
506 	i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG);
507 	if (i < 0 || i >= MAXPATHLEN)
508 		return;
509 
510 	if (tag != NULL || cvs_specified_date != -1 ||
511 	    cvs_directory_date != -1) {
512 		if ((fp = fopen(tagpath, "w+")) == NULL) {
513 			if (errno != ENOENT) {
514 				cvs_log(LP_NOTICE, "failed to open `%s' : %s",
515 				    tagpath, strerror(errno));
516 			}
517 			return;
518 		}
519 
520 		if (tag != NULL) {
521 			if ((rev = rcsnum_parse(tag)) != NULL) {
522 				(void)xsnprintf(sticky, sizeof(sticky),
523 				    "N%s", tag);
524 				rcsnum_free(rev);
525 			} else {
526 				(void)xsnprintf(sticky, sizeof(sticky),
527 				    "T%s", tag);
528 			}
529 		} else {
530 			if (cvs_specified_date != -1)
531 				gmtime_r(&cvs_specified_date, &datetm);
532 			else
533 				gmtime_r(&cvs_directory_date, &datetm);
534 			(void)strftime(sticky, sizeof(sticky),
535 			    "D"CVS_DATE_FMT, &datetm);
536 		}
537 
538 		if (cvs_server_active == 1)
539 			cvs_server_set_sticky(dir, sticky);
540 
541 		(void)fprintf(fp, "%s\n", sticky);
542 		(void)fclose(fp);
543 	}
544 }
545