xref: /openbsd-src/usr.bin/cvs/entries.c (revision 4deeb87832e5d00dbfea5b08ed9c463296b435ef)
1 /*	$OpenBSD: entries.c,v 1.15 2004/08/13 13:24:13 jfb Exp $	*/
2 /*
3  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. The name of the author may not be used to endorse or promote products
13  *    derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/param.h>
28 #include <sys/stat.h>
29 
30 #include <stdio.h>
31 #include <fcntl.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35 
36 #include "log.h"
37 #include "cvs.h"
38 
39 
40 #define CVS_ENTRIES_NFIELDS  6
41 #define CVS_ENTRIES_DELIM   '/'
42 
43 
44 
45 /*
46  * cvs_ent_open()
47  *
48  * Open the CVS Entries file for the directory <dir>.
49  * Returns a pointer to the CVSENTRIES file structure on success, or NULL
50  * on failure.
51  */
52 
53 CVSENTRIES*
54 cvs_ent_open(const char *dir, int flags)
55 {
56 	size_t len;
57 	int exists;
58 	char entpath[MAXPATHLEN], ebuf[128], mode[4];
59 	FILE *fp;
60 	struct stat st;
61 	struct cvs_ent *ent;
62 	CVSENTRIES *ep;
63 
64 	exists = 0;
65 	memset(mode, 0, sizeof(mode));
66 
67 	snprintf(entpath, sizeof(entpath), "%s/" CVS_PATH_ENTRIES, dir);
68 
69 	switch (flags & O_ACCMODE) {
70 	case O_WRONLY:
71 	case O_RDWR:
72 		/* we have to use append otherwise the file gets truncated */
73 		mode[0] = 'w';
74 		mode[1] = '+';
75 		break;
76 	case O_RDONLY:
77 		mode[0] = 'r';
78 		break;
79 	}
80 
81 	/* we can use 'r' if the file already exists */
82 	if (stat(entpath, &st) == 0) {
83 		exists = 1;
84 		mode[0] = 'r';
85 	}
86 
87 	fp = fopen(entpath, mode);
88 	if (fp == NULL) {
89 		cvs_log(LP_ERRNO, "cannot open %s for %s", entpath,
90 		    mode[1] == '+' ? "writing" : "reading");
91 		return (NULL);
92 	}
93 
94 	ep = (CVSENTRIES *)malloc(sizeof(CVSENTRIES));
95 	if (ep == NULL) {
96 		cvs_log(LP_ERRNO, "failed to allocate Entries data");
97 		(void)fclose(fp);
98 		return (NULL);
99 	}
100 	memset(ep, 0, sizeof(*ep));
101 
102 	ep->cef_path = strdup(dir);
103 	if (ep->cef_path == NULL) {
104 		cvs_log(LP_ERRNO, "failed to copy Entries path");
105 		free(ep);
106 		(void)fclose(fp);
107 		return (NULL);
108 	}
109 
110 	ep->cef_cur = NULL;
111 	TAILQ_INIT(&(ep->cef_ent));
112 
113 	while (fgets(ebuf, sizeof(ebuf), fp) != NULL) {
114 		len = strlen(ebuf);
115 		if ((len > 0) && (ebuf[len - 1] == '\n'))
116 			ebuf[--len] = '\0';
117 		if (strcmp(ebuf, "D") == 0)
118 			break;
119 		ent = cvs_ent_parse(ebuf);
120 		if (ent == NULL)
121 			continue;
122 
123 		TAILQ_INSERT_TAIL(&(ep->cef_ent), ent, ce_list);
124 	}
125 	if (ferror(fp)) {
126 		cvs_ent_close(ep);
127 		return (NULL);
128 	}
129 
130 	/* only keep a pointer to the open file if we're in writing mode */
131 	if ((flags & O_WRONLY) || (flags & O_RDWR)) {
132 		ep->cef_flags |= CVS_ENTF_WR;
133 		ep->cef_file = fp;
134 	}
135 	else
136 		(void)fclose(fp);
137 
138 	if (exists)
139 		ep->cef_flags |= CVS_ENTF_SYNC;
140 
141 	return (ep);
142 }
143 
144 
145 /*
146  * cvs_ent_close()
147  *
148  * Close the Entries file <ep> and free all data.  Any reference to entries
149  * structure within that file become invalid.
150  */
151 
152 void
153 cvs_ent_close(CVSENTRIES *ep)
154 {
155 	struct cvs_ent *ent;
156 
157 	if ((ep->cef_flags & CVS_ENTF_WR) &&
158 	    !(ep->cef_flags & CVS_ENTF_SYNC)) {
159 		/* implicit sync with disk */
160 		(void)cvs_ent_write(ep);
161 	}
162 
163 	if (ep->cef_file != NULL)
164 		(void)fclose(ep->cef_file);
165 	if (ep->cef_path != NULL)
166 		free(ep->cef_path);
167 
168 	while (!TAILQ_EMPTY(&(ep->cef_ent))) {
169 		ent = TAILQ_FIRST(&(ep->cef_ent));
170 		TAILQ_REMOVE(&(ep->cef_ent), ent, ce_list);
171 		cvs_ent_free(ent);
172 	}
173 
174 	free(ep);
175 }
176 
177 
178 /*
179  * cvs_ent_add()
180  *
181  * Add the entry <ent> to the Entries file <ef>.  The disk contents are not
182  * modified until a call to cvs_ent_write() is performed.  This is done
183  * implicitly on a call to cvs_ent_close() on an Entries file that has been
184  * opened for writing.
185  * Returns 0 on success, or -1 on failure.
186  */
187 
188 int
189 cvs_ent_add(CVSENTRIES *ef, struct cvs_ent *ent)
190 {
191 	if (ef->cef_file == NULL) {
192 		cvs_log(LP_ERR, "Entries file is opened in read-only mode");
193 		return (-1);
194 	}
195 
196 	if (cvs_ent_get(ef, ent->ce_name) != NULL)
197 		return (-1);
198 
199 	TAILQ_INSERT_TAIL(&(ef->cef_ent), ent, ce_list);
200 
201 	ef->cef_flags &= ~CVS_ENTF_SYNC;
202 
203 	return (0);
204 }
205 
206 
207 /*
208  * cvs_ent_addln()
209  *
210  * Add a line to the Entries file.
211  */
212 
213 int
214 cvs_ent_addln(CVSENTRIES *ef, const char *line)
215 {
216 	struct cvs_ent *ent;
217 
218 	if (ef->cef_file == NULL) {
219 		cvs_log(LP_ERR, "Entries file is opened in read-only mode");
220 		return (-1);
221 	}
222 
223 	ent = cvs_ent_parse(line);
224 	if (ent == NULL)
225 		return (-1);
226 
227 	if (cvs_ent_get(ef, ent->ce_name) != NULL)
228 		return (-1);
229 
230 	TAILQ_INSERT_TAIL(&(ef->cef_ent), ent, ce_list);
231 	ef->cef_flags &= ~CVS_ENTF_SYNC;
232 
233 	return (0);
234 }
235 
236 
237 /*
238  * cvs_ent_remove()
239  *
240  * Remove an entry from the Entries file <ef>.  The entry's name is given
241  * by <name>.
242  */
243 
244 int
245 cvs_ent_remove(CVSENTRIES *ef, const char *name)
246 {
247 	struct cvs_ent *ent;
248 
249 	ent = cvs_ent_get(ef, name);
250 	if (ent == NULL)
251 		return (-1);
252 
253 	TAILQ_REMOVE(&(ef->cef_ent), ent, ce_list);
254 	cvs_ent_free(ent);
255 
256 	ef->cef_flags &= ~CVS_ENTF_SYNC;
257 
258 	return (0);
259 }
260 
261 
262 /*
263  * cvs_ent_get()
264  *
265  * Get the CVS entry from the Entries file <ef> whose 'name' portion matches
266  * <file>.
267  * Returns a pointer to the cvs entry structure on success, or NULL on failure.
268  */
269 
270 struct cvs_ent*
271 cvs_ent_get(CVSENTRIES *ef, const char *file)
272 {
273 	struct cvs_ent *ep;
274 
275 	TAILQ_FOREACH(ep, &(ef->cef_ent), ce_list)
276 		if (strcmp(ep->ce_name, file) == 0)
277 			return (ep);
278 
279 	return (NULL);
280 }
281 
282 
283 /*
284  * cvs_ent_next()
285  *
286  * This function is used to iterate over the entries in an Entries file.  The
287  * first call will return the first entry of the file and each subsequent call
288  * will return the entry following the last one returned.
289  * Returns a pointer to the cvs entry structure on success, or NULL on failure.
290  */
291 
292 struct cvs_ent*
293 cvs_ent_next(CVSENTRIES *ef)
294 {
295 	if (ef->cef_cur == NULL)
296 		ef->cef_cur = TAILQ_FIRST(&(ef->cef_ent));
297 	else
298 		ef->cef_cur = TAILQ_NEXT(ef->cef_cur, ce_list);
299 	return (ef->cef_cur);
300 }
301 
302 
303 /*
304  * cvs_ent_parse()
305  *
306  * Parse a single line from a CVS/Entries file and return a cvs_entry structure
307  * containing all the parsed information.
308  */
309 
310 struct cvs_ent*
311 cvs_ent_parse(const char *entry)
312 {
313 	int i;
314 	char *fields[CVS_ENTRIES_NFIELDS], *buf, *sp, *dp;
315 	struct cvs_ent *entp;
316 
317 	buf = strdup(entry);
318 	if (buf == NULL) {
319 		cvs_log(LP_ERRNO, "failed to allocate entry copy");
320 		return (NULL);
321 	}
322 
323 	sp = buf;
324 	i = 0;
325 	do {
326 		dp = strchr(sp, CVS_ENTRIES_DELIM);
327 		if (dp != NULL)
328 			*(dp++) = '\0';
329 		fields[i++] = sp;
330 		sp = dp;
331 	} while ((dp != NULL) && (i < CVS_ENTRIES_NFIELDS));
332 
333 	if (i < CVS_ENTRIES_NFIELDS) {
334 		cvs_log(LP_ERR, "missing fields in entry line `%s'", entry);
335 		return (NULL);
336 	}
337 
338 	entp = (struct cvs_ent *)malloc(sizeof(*entp));
339 	if (entp == NULL) {
340 		cvs_log(LP_ERRNO, "failed to allocate CVS entry");
341 		return (NULL);
342 	}
343 	memset(entp, 0, sizeof(*entp));
344 	entp->ce_buf = buf;
345 
346 	entp->ce_rev = rcsnum_alloc();
347 	if (entp->ce_rev == NULL) {
348 		cvs_ent_free(entp);
349 		return (NULL);
350 	}
351 
352 	entp->ce_line = strdup(entry);
353 	if (entp->ce_line == NULL) {
354 		cvs_ent_free(entp);
355 		return (NULL);
356 	}
357 
358 	if (*fields[0] == '\0')
359 		entp->ce_type = CVS_ENT_FILE;
360 	else if (*fields[0] == 'D')
361 		entp->ce_type = CVS_ENT_DIR;
362 	else
363 		entp->ce_type = CVS_ENT_NONE;
364 
365 	entp->ce_name = fields[1];
366 
367 	if (entp->ce_type == CVS_ENT_FILE) {
368 		rcsnum_aton(fields[2], NULL, entp->ce_rev);
369 		entp->ce_mtime = cvs_datesec(fields[3], CVS_DATE_CTIME, 0);
370 		entp->ce_opts = fields[4];
371 		entp->ce_tag = fields[5];
372 	}
373 
374 	return (entp);
375 }
376 
377 
378 /*
379  * cvs_ent_free()
380  *
381  * Free a single CVS entries structure.
382  */
383 
384 void
385 cvs_ent_free(struct cvs_ent *ent)
386 {
387 	if (ent->ce_rev != NULL)
388 		rcsnum_free(ent->ce_rev);
389 	if (ent->ce_line != NULL)
390 		free(ent->ce_line);
391 	if (ent->ce_buf != NULL)
392 		free(ent->ce_buf);
393 	free(ent);
394 }
395 
396 
397 /*
398  * cvs_ent_getent()
399  *
400  * Get a single entry from the CVS/Entries file of the basename portion of
401  * path <path> and return that entry.  That entry must later be freed using
402  * cvs_ent_free().
403  */
404 
405 struct cvs_ent*
406 cvs_ent_getent(const char *path)
407 {
408 	char base[MAXPATHLEN], *file;
409 	CVSENTRIES *entf;
410 	struct cvs_ent *ep;
411 
412 	cvs_splitpath(path, base, sizeof(base), &file);
413 
414 	entf = cvs_ent_open(base, O_RDONLY);
415 	if (entf == NULL)
416 		return (NULL);
417 
418 	ep = cvs_ent_get(entf, file);
419 	if (ep != NULL) {
420 		/* take it out of the queue so it doesn't get freed */
421 		TAILQ_REMOVE(&(entf->cef_ent), ep, ce_list);
422 	}
423 
424 	cvs_ent_close(entf);
425 	return (ep);
426 }
427 
428 
429 /*
430  * cvs_ent_write()
431  *
432  * Explicitly write the contents of the Entries file <ef> to disk.
433  * Returns 0 on success, or -1 on failure.
434  */
435 
436 int
437 cvs_ent_write(CVSENTRIES *ef)
438 {
439 	size_t len;
440 	char revbuf[64], timebuf[32];
441 	struct cvs_ent *ent;
442 
443 	if (ef->cef_file == NULL)
444 		return (-1);
445 
446 	if (ef->cef_flags & CVS_ENTF_SYNC)
447 		return (0);
448 
449 	/* reposition ourself at beginning of file */
450 	rewind(ef->cef_file);
451 	TAILQ_FOREACH(ent, &(ef->cef_ent), ce_list) {
452 		if (ent->ce_type == CVS_ENT_DIR) {
453 			putc('D', ef->cef_file);
454 			timebuf[0] = '\0';
455 			revbuf[0] = '\0';
456 		}
457 		else {
458 			rcsnum_tostr(ent->ce_rev, revbuf, sizeof(revbuf));
459 			if (ent->ce_mtime == CVS_DATE_DMSEC)
460 				strlcpy(timebuf, CVS_DATE_DUMMY,
461 				    sizeof(timebuf));
462 			else {
463 				ctime_r(&(ent->ce_mtime), timebuf);
464 				len = strlen(timebuf);
465 				if ((len > 0) && (timebuf[len - 1] == '\n'))
466 					timebuf[--len] = '\0';
467 			}
468 		}
469 
470 		fprintf(ef->cef_file, "/%s/%s/%s/%s/%s\n", ent->ce_name,
471 		    revbuf, timebuf, "", "");
472 	}
473 
474 	/* terminating line */
475 	fprintf(ef->cef_file, "D\n");
476 
477 	ef->cef_flags |= CVS_ENTF_SYNC;
478 
479 	return (0);
480 }
481