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