xref: /openbsd-src/usr.bin/rcs/rcsutil.c (revision 799f675f6700f14e59124f9825c723e9f2ce19dc)
1 /*	$OpenBSD: rcsutil.c,v 1.26 2007/01/11 18:13:33 niallo Exp $	*/
2 /*
3  * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
4  * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org>
5  * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org>
6  * Copyright (c) 2006 Ray Lai <ray@openbsd.org>
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "includes.h"
31 
32 #include "rcsprog.h"
33 
34 /*
35  * rcs_get_mtime()
36  *
37  * Get <filename> last modified time.
38  * Returns last modified time on success, or -1 on failure.
39  */
40 time_t
41 rcs_get_mtime(RCSFILE *file)
42 {
43 	struct stat st;
44 	time_t mtime;
45 
46 	if (fstat(file->rf_fd, &st) == -1) {
47 		warn("%s", file->rf_path);
48 		return (-1);
49 	}
50 
51 	mtime = (time_t)st.st_mtimespec.tv_sec;
52 
53 	return (mtime);
54 }
55 
56 /*
57  * rcs_set_mtime()
58  *
59  * Set <filename> last modified time to <mtime> if it's not set to -1.
60  */
61 void
62 rcs_set_mtime(RCSFILE *file, time_t mtime)
63 {
64 	static struct timeval tv[2];
65 
66 	if (mtime == -1)
67 		return;
68 
69 	tv[0].tv_sec = mtime;
70 	tv[1].tv_sec = tv[0].tv_sec;
71 
72 	if (futimes(file->rf_fd, tv) == -1)
73 		err(1, "utimes");
74 }
75 
76 int
77 rcs_getopt(int argc, char **argv, const char *optstr)
78 {
79 	char *a;
80 	const char *c;
81 	static int i = 1;
82 	int opt, hasargument, ret;
83 
84 	hasargument = 0;
85 	rcs_optarg = NULL;
86 
87 	if (i >= argc)
88 		return (-1);
89 
90 	a = argv[i++];
91 	if (*a++ != '-')
92 		return (-1);
93 
94 	ret = 0;
95 	opt = *a;
96 	for (c = optstr; *c != '\0'; c++) {
97 		if (*c == opt) {
98 			a++;
99 			ret = opt;
100 
101 			if (*(c + 1) == ':') {
102 				if (*(c + 2) == ':') {
103 					if (*a != '\0')
104 						hasargument = 1;
105 				} else {
106 					if (*a != '\0') {
107 						hasargument = 1;
108 					} else {
109 						ret = 1;
110 						break;
111 					}
112 				}
113 			}
114 
115 			if (hasargument == 1)
116 				rcs_optarg = a;
117 
118 			if (ret == opt)
119 				rcs_optind++;
120 			break;
121 		}
122 	}
123 
124 	if (ret == 0)
125 		warnx("unknown option -%c", opt);
126 	else if (ret == 1)
127 		warnx("missing argument for option -%c", opt);
128 
129 	return (ret);
130 }
131 
132 /*
133  * rcs_choosefile()
134  *
135  * Given a relative filename, decide where the corresponding RCS file
136  * should be.  Tries each extension until a file is found.  If no file
137  * was found, returns a path with the first extension.
138  *
139  * Opens and returns file descriptor to RCS file.
140  */
141 int
142 rcs_choosefile(const char *filename, char *out, size_t len)
143 {
144 	int fd;
145 	struct stat sb;
146 	char *p, *ext, name[MAXPATHLEN], *next, *ptr, rcsdir[MAXPATHLEN],
147 	    *suffixes, rcspath[MAXPATHLEN];
148 
149 	/* If -x flag was not given, use default. */
150 	if (rcs_suffixes == NULL)
151 		rcs_suffixes = RCS_DEFAULT_SUFFIX;
152 
153 	fd = -1;
154 
155 	/*
156 	 * If `filename' contains a directory, `rcspath' contains that
157 	 * directory, including a trailing slash.  Otherwise `rcspath'
158 	 * contains an empty string.
159 	 */
160 	if (strlcpy(rcspath, filename, sizeof(rcspath)) >= sizeof(rcspath))
161 		errx(1, "rcs_choosefile: truncation");
162 
163 	/* If `/' is found, end string after `/'. */
164 	if ((ptr = strrchr(rcspath, '/')) != NULL)
165 		*(++ptr) = '\0';
166 	else
167 		rcspath[0] = '\0';
168 
169 	/* Append RCS/ to `rcspath' if it exists. */
170 	if (strlcpy(rcsdir, rcspath, sizeof(rcsdir)) >= sizeof(rcsdir) ||
171 	    strlcat(rcsdir, RCSDIR, sizeof(rcsdir)) >= sizeof(rcsdir))
172 		errx(1, "rcs_choosefile: truncation");
173 
174 	if (stat(rcsdir, &sb) == 0 && S_ISDIR(sb.st_mode))
175 		if (strlcpy(rcspath, rcsdir, sizeof(rcspath))
176 		    >= sizeof(rcspath) ||
177 		    strlcat(rcspath, "/", sizeof(rcspath)) >= sizeof(rcspath))
178 			errx(1, "rcs_choosefile: truncation");
179 
180 	/* Name of file without path. */
181 	if ((ptr = strrchr(filename, '/')) == NULL) {
182 		if (strlcpy(name, filename, sizeof(name)) >= sizeof(name))
183 			errx(1, "rcs_choosefile: truncation");
184 	} else {
185 		/* Skip `/'. */
186 		if (strlcpy(name, ptr + 1, sizeof(name)) >= sizeof(name))
187 			errx(1, "rcs_choosefile: truncation");
188 	}
189 
190 	/* Name of RCS file without an extension. */
191 	if (strlcat(rcspath, name, sizeof(rcspath)) >= sizeof(rcspath))
192 		errx(1, "rcs_choosefile: truncation");
193 
194 	/*
195 	 * If only the empty suffix was given, use existing rcspath.
196 	 * This ensures that there is at least one suffix for strsep().
197 	 */
198 	if (strcmp(rcs_suffixes, "") == 0) {
199 		if (strlcpy(out, rcspath, len) >= len)
200 			errx(1, "rcs_choosefile: truncation");
201 		fd = open(rcspath, O_RDONLY);
202 		return (fd);
203 	}
204 
205 	/*
206 	 * Cycle through slash-separated `rcs_suffixes', appending each
207 	 * extension to `rcspath' and testing if the file exists.  If it
208 	 * does, return that string.  Otherwise return path with first
209 	 * extension.
210 	 */
211 	suffixes = xstrdup(rcs_suffixes);
212 	for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) {
213 		char fpath[MAXPATHLEN];
214 
215 		if ((p = strrchr(rcspath, ',')) != NULL) {
216 			if (!strcmp(p, ext)) {
217 				if ((fd = open(rcspath, O_RDONLY)) == -1)
218 					continue;
219 
220 				if (fstat(fd, &sb) == -1)
221 					err(1, "%s", rcspath);
222 
223 				if (strlcpy(out, rcspath, len) >= len)
224 					errx(1, "rcs_choosefile; truncation");
225 
226 				return (fd);
227 			}
228 
229 			continue;
230 		}
231 
232 		/* Construct RCS file path. */
233 		if (strlcpy(fpath, rcspath, sizeof(fpath)) >= sizeof(fpath) ||
234 		    strlcat(fpath, ext, sizeof(fpath)) >= sizeof(fpath))
235 			errx(1, "rcs_choosefile: truncation");
236 
237 		/* Don't use `filename' as RCS file. */
238 		if (strcmp(fpath, filename) == 0)
239 			continue;
240 
241 		if ((fd = open(fpath, O_RDONLY)) == -1)
242 			continue;
243 
244 		if (fstat(fd, &sb) == -1)
245 			err(1, "%s", fpath);
246 
247 		if (strlcpy(out, fpath, len) >= len)
248 			errx(1, "rcs_choosefile: truncation");
249 
250 		return (fd);
251 	}
252 
253 	/*
254 	 * `suffixes' should now be NUL separated, so the first
255 	 * extension can be read just by reading `suffixes'.
256 	 */
257 	if (strlcat(rcspath, suffixes, sizeof(rcspath)) >= sizeof(rcspath))
258 		errx(1, "rcs_choosefile: truncation");
259 
260 	xfree(suffixes);
261 
262 	if (strlcpy(out, rcspath, len) >= len)
263 		errx(1, "rcs_choosefile: truncation");
264 
265 	fd = open(rcspath, O_RDONLY);
266 
267 	return (fd);
268 }
269 
270 /*
271  * Allocate an RCSNUM and store in <rev>.
272  */
273 void
274 rcs_set_rev(const char *str, RCSNUM **rev)
275 {
276 	if (str == NULL || (*rev = rcsnum_parse(str)) == NULL)
277 		errx(1, "bad revision number `%s'", str);
278 }
279 
280 /*
281  * Set <str> to <new_str>.  Print warning if <str> is redefined.
282  */
283 void
284 rcs_setrevstr(char **str, char *new_str)
285 {
286 	if (new_str == NULL)
287 		return;
288 	if (*str != NULL)
289 		warnx("redefinition of revision number");
290 	*str = new_str;
291 }
292 
293 /*
294  * Set <str1> or <str2> to <new_str>, depending on which is not set.
295  * If both are set, error out.
296  */
297 void
298 rcs_setrevstr2(char **str1, char **str2, char *new_str)
299 {
300 	if (new_str == NULL)
301 		return;
302 	if (*str1 == NULL)
303 		*str1 = new_str;
304 	else if (*str2 == NULL)
305 		*str2 = new_str;
306 	else
307 		errx(1, "too many revision numbers");
308 }
309 
310 /*
311  * Get revision from file.  The revision can be specified as a symbol or
312  * a revision number.
313  */
314 RCSNUM *
315 rcs_getrevnum(const char *rev_str, RCSFILE *file)
316 {
317 	RCSNUM *rev;
318 
319 	/* Search for symbol. */
320 	rev = rcs_sym_getrev(file, rev_str);
321 
322 	/* Search for revision number. */
323 	if (rev == NULL)
324 		rev = rcsnum_parse(rev_str);
325 
326 	return (rev);
327 }
328 
329 /*
330  * Prompt for and store user's input in an allocated string.
331  *
332  * Returns the string's pointer.
333  */
334 char *
335 rcs_prompt(const char *prompt)
336 {
337 	BUF *bp;
338 	size_t len;
339 	char *buf;
340 
341 	bp = rcs_buf_alloc(0, BUF_AUTOEXT);
342 	if (isatty(STDIN_FILENO))
343 		(void)fprintf(stderr, "%s", prompt);
344 	if (isatty(STDIN_FILENO))
345 		(void)fprintf(stderr, ">> ");
346 	clearerr(stdin);
347 	while ((buf = fgetln(stdin, &len)) != NULL) {
348 		/* The last line may not be EOL terminated. */
349 		if (buf[0] == '.' && (len == 1 || buf[1] == '\n'))
350 			break;
351 		else
352 			rcs_buf_append(bp, buf, len);
353 
354 		if (isatty(STDIN_FILENO))
355 			(void)fprintf(stderr, ">> ");
356 	}
357 	rcs_buf_putc(bp, '\0');
358 
359 	return (rcs_buf_release(bp));
360 }
361 
362 u_int
363 rcs_rev_select(RCSFILE *file, const char *range)
364 {
365 	int i;
366 	u_int nrev;
367 	char *ep;
368 	char *lstr, *rstr;
369 	struct rcs_delta *rdp;
370 	struct rcs_argvector *revargv, *revrange;
371 	RCSNUM lnum, rnum;
372 
373 	nrev = 0;
374 	(void)memset(&lnum, 0, sizeof(lnum));
375 	(void)memset(&rnum, 0, sizeof(rnum));
376 
377 	if (range == NULL) {
378 		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)
379 			if (rcsnum_cmp(rdp->rd_num, file->rf_head, 0) == 0) {
380 				rdp->rd_flags |= RCS_RD_SELECT;
381 				return (1);
382 			}
383 		return (0);
384 	}
385 
386 	revargv = rcs_strsplit(range, ",");
387 	for (i = 0; revargv->argv[i] != NULL; i++) {
388 		revrange = rcs_strsplit(revargv->argv[i], ":");
389 		if (revrange->argv[0] == NULL)
390 			/* should not happen */
391 			errx(1, "invalid revision range: %s", revargv->argv[i]);
392 		else if (revrange->argv[1] == NULL)
393 			lstr = rstr = revrange->argv[0];
394 		else {
395 			if (revrange->argv[2] != NULL)
396 				errx(1, "invalid revision range: %s",
397 				    revargv->argv[i]);
398 			lstr = revrange->argv[0];
399 			rstr = revrange->argv[1];
400 			if (strcmp(lstr, "") == 0)
401 				lstr = NULL;
402 			if (strcmp(rstr, "") == 0)
403 				rstr = NULL;
404 		}
405 
406 		if (lstr == NULL)
407 			lstr = RCS_HEAD_INIT;
408 		if (rcsnum_aton(lstr, &ep, &lnum) == 0 || (*ep != '\0'))
409 			errx(1, "invalid revision: %s", lstr);
410 
411 		if (rstr != NULL) {
412 			if (rcsnum_aton(rstr, &ep, &rnum) == 0 || (*ep != '\0'))
413 				errx(1, "invalid revision: %s", rstr);
414 		} else
415 			rcsnum_cpy(file->rf_head, &rnum, 0);
416 
417 		rcs_argv_destroy(revrange);
418 
419 		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)
420 			if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 &&
421 			    rcsnum_cmp(rdp->rd_num, &rnum, 0) >= 0 &&
422 			    !(rdp->rd_flags & RCS_RD_SELECT)) {
423 				rdp->rd_flags |= RCS_RD_SELECT;
424 				nrev++;
425 			}
426 	}
427 	rcs_argv_destroy(revargv);
428 
429 	if (lnum.rn_id != NULL)
430 		xfree(lnum.rn_id);
431 	if (rnum.rn_id != NULL)
432 		xfree(rnum.rn_id);
433 
434 	return (nrev);
435 }
436 
437 /*
438  * Load description from <in> to <file>.
439  * If <in> starts with a `-', <in> is taken as the description.
440  * Otherwise <in> is the name of the file containing the description.
441  * If <in> is NULL, the description is read from stdin.
442  * Returns 0 on success, -1 on failure, setting errno.
443  */
444 int
445 rcs_set_description(RCSFILE *file, const char *in)
446 {
447 	BUF *bp;
448 	char *content;
449 	const char *prompt =
450 	    "enter description, terminated with single '.' or end of file:\n"
451 	    "NOTE: This is NOT the log message!\n";
452 
453 	/* Description is in file <in>. */
454 	if (in != NULL && *in != '-') {
455 		if ((bp = rcs_buf_load(in, BUF_AUTOEXT)) == NULL)
456 			return (-1);
457 		rcs_buf_putc(bp, '\0');
458 		content = rcs_buf_release(bp);
459 	/* Description is in <in>. */
460 	} else if (in != NULL)
461 		/* Skip leading `-'. */
462 		content = xstrdup(in + 1);
463 	/* Get description from stdin. */
464 	else
465 		content = rcs_prompt(prompt);
466 
467 	rcs_desc_set(file, content);
468 	xfree(content);
469 	return (0);
470 }
471 
472 /*
473  * Split the contents of a file into a list of lines.
474  */
475 struct rcs_lines *
476 rcs_splitlines(const u_char *data, size_t len)
477 {
478 	u_char *c, *p;
479 	struct rcs_lines *lines;
480 	struct rcs_line *lp;
481 	size_t i, tlen;
482 
483 	lines = xmalloc(sizeof(*lines));
484 	memset(lines, 0, sizeof(*lines));
485 	TAILQ_INIT(&(lines->l_lines));
486 
487 	lp = xmalloc(sizeof(*lp));
488 	memset(lp, 0, sizeof(*lp));
489 	TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
490 
491 
492 	p = c = data;
493 	for (i = 0; i < len; i++) {
494 		if (*p == '\n' || (i == len - 1)) {
495 			tlen = p - c + 1;
496 			lp = xmalloc(sizeof(*lp));
497 			lp->l_line = c;
498 			lp->l_len = tlen;
499 			lp->l_lineno = ++(lines->l_nblines);
500 			TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
501 			c = p + 1;
502 		}
503 		p++;
504 	}
505 
506 	return (lines);
507 }
508 
509 void
510 rcs_freelines(struct rcs_lines *lines)
511 {
512 	struct rcs_line *lp;
513 
514 	while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
515 		TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
516 		xfree(lp);
517 	}
518 
519 	xfree(lines);
520 }
521 
522 BUF *
523 rcs_patchfile(const u_char *data, size_t dlen, const u_char *patch, size_t plen,
524     int (*p)(struct rcs_lines *, struct rcs_lines *))
525 {
526 	struct rcs_lines *dlines, *plines;
527 	struct rcs_line *lp;
528 	BUF *res;
529 
530 	dlines = rcs_splitlines(data, dlen);
531 	plines = rcs_splitlines(patch, plen);
532 
533 	if (p(dlines, plines) < 0) {
534 		rcs_freelines(dlines);
535 		rcs_freelines(plines);
536 		return (NULL);
537 	}
538 
539 	res = rcs_buf_alloc(1024, BUF_AUTOEXT);
540 	TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
541 		if (lp->l_line == NULL)
542 			continue;
543 		rcs_buf_append(res, lp->l_line, lp->l_len);
544 	}
545 
546 	rcs_freelines(dlines);
547 	rcs_freelines(plines);
548 	return (res);
549 }
550 
551 /*
552  * rcs_yesno()
553  *
554  * Read a char from standard input, returns defc if the
555  * user enters an equivalent to defc, else whatever char
556  * was entered.  Converts input to lower case.
557  */
558 int
559 rcs_yesno(int defc)
560 {
561 	int c, ret;
562 
563 	fflush(stderr);
564 	fflush(stdout);
565 
566 	clearerr(stdin);
567 	if (isalpha(c = getchar()))
568 		c = tolower(c);
569 	if (c == defc || c == '\n' || (c == EOF && feof(stdin)))
570 		ret = defc;
571 	else
572 		ret = c;
573 
574 	while (c != EOF && c != '\n')
575 		c = getchar();
576 
577 	return (ret);
578 }
579 
580 /*
581  * rcs_strsplit()
582  *
583  * Split a string <str> of <sep>-separated values and allocate
584  * an argument vector for the values found.
585  */
586 struct rcs_argvector *
587 rcs_strsplit(const char *str, const char *sep)
588 {
589 	struct rcs_argvector *av;
590 	size_t i = 0;
591 	char **nargv;
592 	char *cp, *p;
593 
594 	cp = xstrdup(str);
595 	av = xmalloc(sizeof(*av));
596 	av->str = cp;
597 	av->argv = xcalloc(i + 1, sizeof(*(av->argv)));
598 
599 	while ((p = strsep(&cp, sep)) != NULL) {
600 		av->argv[i++] = p;
601 		nargv = xrealloc(av->argv,
602 		    i + 1, sizeof(*(av->argv)));
603 		av->argv = nargv;
604 	}
605 	av->argv[i] = NULL;
606 
607 	return (av);
608 }
609 
610 /*
611  * rcs_argv_destroy()
612  *
613  * Free an argument vector previously allocated by rcs_strsplit().
614  */
615 void
616 rcs_argv_destroy(struct rcs_argvector *av)
617 {
618 	xfree(av->str);
619 	xfree(av->argv);
620 	xfree(av);
621 }
622