xref: /openbsd/usr.bin/cvs/entries.c (revision 898184e3)
1 /*	$OpenBSD: entries.c,v 1.102 2009/03/19 09:53:16 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 if (*fields[2] == CVS_SERVER_QUESTIONABLE) {
160 			sp = NULL;
161 			ent->ce_status = CVS_ENT_UNKNOWN;
162 		} else {
163 			sp = fields[2];
164 			if (fields[2][0] == '0' && fields[2][1] == '\0')
165 				ent->ce_status = CVS_ENT_ADDED;
166 		}
167 
168 		if (sp != NULL) {
169 			if ((ent->ce_rev = rcsnum_parse(sp)) == NULL) {
170 				fatal("failed to parse entry revision '%s'",
171 				    entry);
172 			}
173 		}
174 
175 		if (fields[3][0] == '\0' ||
176 		    strncmp(fields[3], CVS_DATE_DUMMY,
177 		    sizeof(CVS_DATE_DUMMY) - 1) == 0 ||
178 		    strncmp(fields[3], "Initial ", 8) == 0 ||
179 		    strcmp(fields[3], "Result of merge") == 0) {
180 			ent->ce_mtime = CVS_DATE_DMSEC;
181 		} else if (cvs_server_active == 1 &&
182 		    strncmp(fields[3], CVS_SERVER_UNCHANGED,
183 		    strlen(CVS_SERVER_UNCHANGED)) == 0) {
184 			ent->ce_mtime = CVS_SERVER_UPTODATE;
185 		} else {
186 			p = fields[3];
187 			if (strncmp(fields[3], "Result of merge+", 16) == 0)
188 				p += 16;
189 
190 			/* Date field can be a '+=' with remote to indicate
191 			 * conflict.  In this case do nothing. */
192 			if (strptime(p, "%a %b %d %T %Y", &t) != NULL) {
193 				t.tm_isdst = -1;	/* Figure out DST. */
194 				t.tm_gmtoff = 0;
195 				ent->ce_mtime = mktime(&t);
196 				ent->ce_mtime += t.tm_gmtoff;
197 			}
198 		}
199 	}
200 
201 	ent->ce_conflict = fields[3];
202 	if ((dp = strchr(ent->ce_conflict, '+')) != NULL)
203 		*dp = '\0';
204 	else
205 		ent->ce_conflict = NULL;
206 
207 	if (strcmp(fields[4], ""))
208 		ent->ce_opts = fields[4];
209 	else
210 		ent->ce_opts = NULL;
211 
212 	if (strcmp(fields[5], "")) {
213 		switch (*fields[5]) {
214 		case 'D':
215 			if (sscanf(fields[5] + 1, "%d.%d.%d.%d.%d.%d",
216 			    &dt.tm_year, &dt.tm_mon, &dt.tm_mday,
217 			    &dt.tm_hour, &dt.tm_min, &dt.tm_sec) != 6)
218 				fatal("wrong date specification");
219 			dt.tm_year -= 1900;
220 			dt.tm_mon -= 1;
221 			ent->ce_date = timegm(&dt);
222 			ent->ce_tag = NULL;
223 			break;
224 		case 'T':
225 			ent->ce_tag = fields[5] + 1;
226 			break;
227 		default:
228 			fatal("invalid sticky entry");
229 		}
230 	}
231 
232 	return (ent);
233 }
234 
235 struct cvs_ent *
236 cvs_ent_get(CVSENTRIES *ep, const char *name)
237 {
238 	struct cvs_ent *ent;
239 	struct cvs_ent_line *l;
240 
241 	l = ent_get_line(ep, name);
242 	if (l == NULL)
243 		return (NULL);
244 
245 	ent = cvs_ent_parse(l->buf);
246 	return (ent);
247 }
248 
249 void
250 cvs_ent_close(CVSENTRIES *ep, int writefile)
251 {
252 	FILE *fp;
253 	struct cvs_ent_line *l;
254 	int dflag;
255 
256 	dflag = 1;
257 	cvs_log(LP_TRACE, "cvs_ent_close(%s, %d)", ep->cef_bpath, writefile);
258 
259 	if (cvs_cmdop == CVS_OP_EXPORT)
260 		writefile = 0;
261 
262 	fp = NULL;
263 	if (writefile)
264 		fp = fopen(ep->cef_bpath, "w");
265 
266 	while ((l = TAILQ_FIRST(&(ep->cef_ent))) != NULL) {
267 		if (fp != NULL) {
268 			if (l->buf[0] == 'D')
269 				dflag = 0;
270 
271 			fputs(l->buf, fp);
272 			fputc('\n', fp);
273 		}
274 
275 		TAILQ_REMOVE(&(ep->cef_ent), l, entries_list);
276 		xfree(l->buf);
277 		xfree(l);
278 	}
279 
280 	if (fp != NULL) {
281 		if (dflag) {
282 			fputc('D', fp);
283 			fputc('\n', fp);
284 		}
285 		(void)fclose(fp);
286 
287 		if (rename(ep->cef_bpath, ep->cef_path) == -1)
288 			fatal("cvs_ent_close: rename: `%s'->`%s': %s",
289 			    ep->cef_bpath, ep->cef_path, strerror(errno));
290 
291 		(void)unlink(ep->cef_lpath);
292 	}
293 
294 	xfree(ep->cef_path);
295 	xfree(ep->cef_bpath);
296 	xfree(ep->cef_lpath);
297 	xfree(ep);
298 }
299 
300 void
301 cvs_ent_add(CVSENTRIES *ep, const char *line)
302 {
303 	FILE *fp;
304 	struct cvs_ent_line *l;
305 	struct cvs_ent *ent;
306 
307 	if ((ent = cvs_ent_parse(line)) == NULL)
308 		fatal("cvs_ent_add: parsing failed '%s'", line);
309 
310 	l = ent_get_line(ep, ent->ce_name);
311 	if (l != NULL)
312 		cvs_ent_remove(ep, ent->ce_name);
313 
314 	cvs_ent_free(ent);
315 
316 	if (cvs_server_active == 0)
317 		cvs_log(LP_TRACE, "cvs_ent_add(%s, %s)", ep->cef_path, line);
318 
319 	if ((fp = fopen(ep->cef_lpath, "a")) == NULL)
320 		fatal("cvs_ent_add: fopen: `%s': %s",
321 		    ep->cef_lpath, strerror(errno));
322 
323 	fputs("A ", fp);
324 	fputs(line, fp);
325 	fputc('\n', fp);
326 
327 	(void)fclose(fp);
328 
329 	l = (struct cvs_ent_line *)xmalloc(sizeof(*l));
330 	l->buf = xstrdup(line);
331 	TAILQ_INSERT_TAIL(&(ep->cef_ent), l, entries_list);
332 }
333 
334 void
335 cvs_ent_remove(CVSENTRIES *ep, const char *name)
336 {
337 	FILE *fp;
338 	struct cvs_ent_line *l;
339 
340 	if (cvs_server_active == 0)
341 		cvs_log(LP_TRACE, "cvs_ent_remove(%s, %s)", ep->cef_path, name);
342 
343 	l = ent_get_line(ep, name);
344 	if (l == NULL)
345 		return;
346 
347 	if ((fp = fopen(ep->cef_lpath, "a")) == NULL)
348 		fatal("cvs_ent_remove: fopen: `%s': %s", ep->cef_lpath,
349 		    strerror(errno));
350 
351 	fputs("R ", fp);
352 	fputs(l->buf, fp);
353 	fputc('\n', fp);
354 
355 	(void)fclose(fp);
356 
357 	TAILQ_REMOVE(&(ep->cef_ent), l, entries_list);
358 	xfree(l->buf);
359 	xfree(l);
360 }
361 
362 /*
363  * cvs_ent_line_str()
364  *
365  * Build CVS/Entries line.
366  *
367  */
368 void
369 cvs_ent_line_str(const char *name, char *rev, char *tstamp, char *opts,
370     char *sticky, int isdir, int isremoved, char *buf, size_t len)
371 {
372 	if (isdir == 1) {
373 		(void)xsnprintf(buf, len, "D/%s////", name);
374 		return;
375 	}
376 
377 	(void)xsnprintf(buf, len, "/%s/%s%s/%s/%s/%s",
378 	    name, isremoved == 1 ? "-" : "", rev, tstamp, opts, sticky);
379 }
380 
381 void
382 cvs_ent_free(struct cvs_ent *ent)
383 {
384 	if (ent->ce_rev != NULL)
385 		rcsnum_free(ent->ce_rev);
386 	xfree(ent->ce_buf);
387 	xfree(ent);
388 }
389 
390 static struct cvs_ent_line *
391 ent_get_line(CVSENTRIES *ep, const char *name)
392 {
393 	char *p, *s;
394 	struct cvs_ent_line *l;
395 
396 	TAILQ_FOREACH(l, &(ep->cef_ent), entries_list) {
397 		if (l->buf[0] == 'D')
398 			p = &(l->buf[2]);
399 		else
400 			p = &(l->buf[1]);
401 
402 		if ((s = strchr(p, '/')) == NULL)
403 			fatal("ent_get_line: bad entry line '%s'", l->buf);
404 
405 		*s = '\0';
406 
407 		if (!strcmp(p, name)) {
408 			*s = '/';
409 			return (l);
410 		}
411 
412 		*s = '/';
413 	}
414 
415 	return (NULL);
416 }
417 
418 void
419 cvs_parse_tagfile(char *dir, char **tagp, char **datep, int *nbp)
420 {
421 	FILE *fp;
422 	int i, linenum;
423 	size_t len;
424 	struct tm datetm;
425 	char linebuf[128], tagpath[MAXPATHLEN];
426 
427 	cvs_directory_date = -1;
428 
429 	if (tagp != NULL)
430 		*tagp = NULL;
431 
432 	if (datep != NULL)
433 		*datep = NULL;
434 
435 	if (nbp != NULL)
436 		*nbp = 0;
437 
438 	i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG);
439 	if (i < 0 || i >= MAXPATHLEN)
440 		return;
441 
442 	if ((fp = fopen(tagpath, "r")) == NULL) {
443 		if (errno != ENOENT)
444 			cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath,
445 			    strerror(errno));
446 		return;
447         }
448 
449 	linenum = 0;
450 
451 	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
452 		linenum++;
453 		if ((len = strlen(linebuf)) == 0)
454 			continue;
455 		if (linebuf[len - 1] != '\n') {
456 			cvs_log(LP_NOTICE, "line too long in `%s:%d'",
457 			    tagpath, linenum);
458 			break;
459 		}
460 		linebuf[--len] = '\0';
461 
462 		switch (*linebuf) {
463 		case 'T':
464 			if (tagp != NULL)
465 				*tagp = xstrdup(linebuf + 1);
466 			break;
467 		case 'D':
468 			if (sscanf(linebuf + 1, "%d.%d.%d.%d.%d.%d",
469 			    &datetm.tm_year, &datetm.tm_mon, &datetm.tm_mday,
470 			    &datetm.tm_hour, &datetm.tm_min, &datetm.tm_sec) !=
471 			    6)
472 				fatal("wrong date specification");
473 			datetm.tm_year -= 1900;
474 			datetm.tm_mon -= 1;
475 
476 			cvs_directory_date = timegm(&datetm);
477 
478 			if (datep != NULL)
479 				*datep = xstrdup(linebuf + 1);
480 			break;
481 		case 'N':
482 			if (tagp != NULL)
483 				*tagp = xstrdup(linebuf + 1);
484 			if (nbp != NULL)
485 				*nbp = 1;
486 			break;
487 		default:
488 			break;
489 		}
490 	}
491 	if (ferror(fp))
492 		cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath);
493 
494 	(void)fclose(fp);
495 }
496 
497 void
498 cvs_write_tagfile(const char *dir, char *tag, char *date)
499 {
500 	FILE *fp;
501 	RCSNUM *rev;
502 	char tagpath[MAXPATHLEN];
503 	char sticky[CVS_REV_BUFSZ];
504 	struct tm datetm;
505 	int i;
506 
507 	cvs_log(LP_TRACE, "cvs_write_tagfile(%s, %s, %s)", dir,
508 	    tag != NULL ? tag : "", date != NULL ? date : "");
509 
510 	if (cvs_noexec == 1)
511 		return;
512 
513 	i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG);
514 	if (i < 0 || i >= MAXPATHLEN)
515 		return;
516 
517 	if (tag != NULL || cvs_specified_date != -1 ||
518 	    cvs_directory_date != -1) {
519 		if ((fp = fopen(tagpath, "w+")) == NULL) {
520 			if (errno != ENOENT) {
521 				cvs_log(LP_NOTICE, "failed to open `%s' : %s",
522 				    tagpath, strerror(errno));
523 			}
524 			return;
525 		}
526 
527 		if (tag != NULL) {
528 			if ((rev = rcsnum_parse(tag)) != NULL) {
529 				(void)xsnprintf(sticky, sizeof(sticky),
530 				    "N%s", tag);
531 				rcsnum_free(rev);
532 			} else {
533 				(void)xsnprintf(sticky, sizeof(sticky),
534 				    "T%s", tag);
535 			}
536 		} else {
537 			if (cvs_specified_date != -1)
538 				gmtime_r(&cvs_specified_date, &datetm);
539 			else
540 				gmtime_r(&cvs_directory_date, &datetm);
541 			(void)strftime(sticky, sizeof(sticky),
542 			    "D"CVS_DATE_FMT, &datetm);
543 		}
544 
545 		(void)fprintf(fp, "%s\n", sticky);
546 		(void)fclose(fp);
547 	}
548 }
549