xref: /openbsd-src/usr.bin/cvs/util.c (revision 94fd4554194a14f126fba33b837cc68a1df42468)
1 /*	$OpenBSD: util.c,v 1.109 2007/05/09 21:19:28 xsa Exp $	*/
2 /*
3  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4  * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
5  * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 
32 #include <errno.h>
33 #include <md5.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include "cvs.h"
39 
40 /* letter -> mode type map */
41 static const int cvs_modetypes[26] = {
42 	-1, -1, -1, -1, -1, -1,  1, -1, -1, -1, -1, -1, -1,
43 	-1,  2, -1, -1, -1, -1, -1,  0, -1, -1, -1, -1, -1,
44 };
45 
46 /* letter -> mode map */
47 static const mode_t cvs_modes[3][26] = {
48 	{
49 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
50 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
51 		0,  0,       0,       S_IRUSR, 0,  0,  0,    /* n - u */
52 		0,  S_IWUSR, S_IXUSR, 0,       0             /* v - z */
53 	},
54 	{
55 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
56 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
57 		0,  0,       0,       S_IRGRP, 0,  0,  0,    /* n - u */
58 		0,  S_IWGRP, S_IXGRP, 0,       0             /* v - z */
59 	},
60 	{
61 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
62 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
63 		0,  0,       0,       S_IROTH, 0,  0,  0,    /* n - u */
64 		0,  S_IWOTH, S_IXOTH, 0,       0             /* v - z */
65 	}
66 };
67 
68 
69 /* octal -> string */
70 static const char *cvs_modestr[8] = {
71 	"", "x", "w", "wx", "r", "rx", "rw", "rwx"
72 };
73 
74 /*
75  * cvs_strtomode()
76  *
77  * Read the contents of the string <str> and generate a permission mode from
78  * the contents of <str>, which is assumed to have the mode format of CVS.
79  * The CVS protocol specification states that any modes or mode types that are
80  * not recognized should be silently ignored.  This function does not return
81  * an error in such cases, but will issue warnings.
82  */
83 void
84 cvs_strtomode(const char *str, mode_t *mode)
85 {
86 	char type;
87 	size_t l;
88 	mode_t m;
89 	char buf[32], ms[4], *sp, *ep;
90 
91 	m = 0;
92 	l = strlcpy(buf, str, sizeof(buf));
93 	if (l >= sizeof(buf))
94 		fatal("cvs_strtomode: string truncation");
95 
96 	sp = buf;
97 	ep = sp;
98 
99 	for (sp = buf; ep != NULL; sp = ep + 1) {
100 		ep = strchr(sp, ',');
101 		if (ep != NULL)
102 			*ep = '\0';
103 
104 		memset(ms, 0, sizeof ms);
105 		if (sscanf(sp, "%c=%3s", &type, ms) != 2 &&
106 			sscanf(sp, "%c=", &type) != 1) {
107 			fatal("failed to scan mode string `%s'", sp);
108 		}
109 
110 		if (type <= 'a' || type >= 'z' ||
111 		    cvs_modetypes[type - 'a'] == -1) {
112 			cvs_log(LP_ERR,
113 			    "invalid mode type `%c'"
114 			    " (`u', `g' or `o' expected), ignoring", type);
115 			continue;
116 		}
117 
118 		/* make type contain the actual mode index */
119 		type = cvs_modetypes[type - 'a'];
120 
121 		for (sp = ms; *sp != '\0'; sp++) {
122 			if (*sp <= 'a' || *sp >= 'z' ||
123 			    cvs_modes[(int)type][*sp - 'a'] == 0) {
124 				fatal("invalid permission bit `%c'", *sp);
125 			} else
126 				m |= cvs_modes[(int)type][*sp - 'a'];
127 		}
128 	}
129 
130 	*mode = m;
131 }
132 
133 /*
134  * cvs_modetostr()
135  *
136  * Generate a CVS-format string to represent the permissions mask on a file
137  * from the mode <mode> and store the result in <buf>, which can accept up to
138  * <len> bytes (including the terminating NUL byte).  The result is guaranteed
139  * to be NUL-terminated.
140  */
141 void
142 cvs_modetostr(mode_t mode, char *buf, size_t len)
143 {
144 	char tmp[16], *bp;
145 	mode_t um, gm, om;
146 
147 	um = (mode & S_IRWXU) >> 6;
148 	gm = (mode & S_IRWXG) >> 3;
149 	om = mode & S_IRWXO;
150 
151 	bp = buf;
152 	*bp = '\0';
153 
154 	if (um) {
155 		if (strlcpy(tmp, "u=", sizeof(tmp)) >= sizeof(tmp) ||
156 		    strlcat(tmp, cvs_modestr[um], sizeof(tmp)) >= sizeof(tmp))
157 			fatal("cvs_modetostr: overflow for user mode");
158 
159 		if (strlcat(buf, tmp, len) >= len)
160 			fatal("cvs_modetostr: string truncation");
161 	}
162 
163 	if (gm) {
164 		if (um) {
165 			if (strlcat(buf, ",", len) >= len)
166 				fatal("cvs_modetostr: string truncation");
167 		}
168 
169 		if (strlcpy(tmp, "g=", sizeof(tmp)) >= sizeof(tmp) ||
170 		    strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp))
171 			fatal("cvs_modetostr: overflow for group mode");
172 
173 		if (strlcat(buf, tmp, len) >= len)
174 			fatal("cvs_modetostr: string truncation");
175 	}
176 
177 	if (om) {
178 		if (um || gm) {
179 			if (strlcat(buf, ",", len) >= len)
180 				fatal("cvs_modetostr: string truncation");
181 		}
182 
183 		if (strlcpy(tmp, "o=", sizeof(tmp)) >= sizeof(tmp) ||
184 		    strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp))
185 			fatal("cvs_modetostr: overflow for others mode");
186 
187 		if (strlcat(buf, tmp, len) >= len)
188 			fatal("cvs_modetostr: string truncation");
189 	}
190 }
191 
192 /*
193  * cvs_cksum()
194  *
195  * Calculate the MD5 checksum of the file whose path is <file> and generate
196  * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
197  * given in <len> and must be at least 33.
198  * Returns 0 on success, or -1 on failure.
199  */
200 int
201 cvs_cksum(const char *file, char *dst, size_t len)
202 {
203 	if (len < CVS_CKSUM_LEN) {
204 		cvs_log(LP_ERR, "buffer too small for checksum");
205 		return (-1);
206 	}
207 	if (MD5File(file, dst) == NULL) {
208 		cvs_log(LP_ERR, "failed to generate checksum for %s", file);
209 		return (-1);
210 	}
211 
212 	return (0);
213 }
214 
215 /*
216  * cvs_getargv()
217  *
218  * Parse a line contained in <line> and generate an argument vector by
219  * splitting the line on spaces and tabs.  The resulting vector is stored in
220  * <argv>, which can accept up to <argvlen> entries.
221  * Returns the number of arguments in the vector, or -1 if an error occurred.
222  */
223 int
224 cvs_getargv(const char *line, char **argv, int argvlen)
225 {
226 	size_t l;
227 	u_int i;
228 	int argc, error;
229 	char linebuf[256], qbuf[128], *lp, *cp, *arg;
230 
231 	l = strlcpy(linebuf, line, sizeof(linebuf));
232 	if (l >= sizeof(linebuf))
233 		fatal("cvs_getargv: string truncation");
234 
235 	memset(argv, 0, argvlen * sizeof(char *));
236 	argc = 0;
237 
238 	/* build the argument vector */
239 	error = 0;
240 	for (lp = linebuf; lp != NULL;) {
241 		if (*lp == '"') {
242 			/* double-quoted string */
243 			lp++;
244 			i = 0;
245 			memset(qbuf, 0, sizeof(qbuf));
246 			while (*lp != '"') {
247 				if (*lp == '\\')
248 					lp++;
249 				if (*lp == '\0') {
250 					cvs_log(LP_ERR, "no terminating quote");
251 					error++;
252 					break;
253 				}
254 
255 				qbuf[i++] = *lp++;
256 				if (i == sizeof(qbuf)) {
257 					error++;
258 					break;
259 				}
260 			}
261 
262 			arg = qbuf;
263 		} else {
264 			cp = strsep(&lp, " \t");
265 			if (cp == NULL)
266 				break;
267 			else if (*cp == '\0')
268 				continue;
269 
270 			arg = cp;
271 		}
272 
273 		if (argc == argvlen) {
274 			error++;
275 			break;
276 		}
277 
278 		argv[argc] = xstrdup(arg);
279 		argc++;
280 	}
281 
282 	if (error != 0) {
283 		/* ditch the argument vector */
284 		for (i = 0; i < (u_int)argc; i++)
285 			xfree(argv[i]);
286 		argc = -1;
287 	}
288 
289 	return (argc);
290 }
291 
292 /*
293  * cvs_makeargv()
294  *
295  * Allocate an argument vector large enough to accommodate for all the
296  * arguments found in <line> and return it.
297  */
298 char **
299 cvs_makeargv(const char *line, int *argc)
300 {
301 	int i, ret;
302 	char *argv[1024], **copy;
303 
304 	ret = cvs_getargv(line, argv, 1024);
305 	if (ret == -1)
306 		return (NULL);
307 
308 	copy = xcalloc(ret + 1, sizeof(char *));
309 
310 	for (i = 0; i < ret; i++)
311 		copy[i] = argv[i];
312 	copy[ret] = NULL;
313 
314 	*argc = ret;
315 	return (copy);
316 }
317 
318 /*
319  * cvs_freeargv()
320  *
321  * Free an argument vector previously generated by cvs_getargv().
322  */
323 void
324 cvs_freeargv(char **argv, int argc)
325 {
326 	int i;
327 
328 	for (i = 0; i < argc; i++)
329 		if (argv[i] != NULL)
330 			xfree(argv[i]);
331 }
332 
333 /*
334  * cvs_chdir()
335  *
336  * Change to directory <path>.
337  * If <rm> is equal to `1', <path> is removed if chdir() fails so we
338  * do not have temporary directories leftovers.
339  * Returns 0 on success.
340  */
341 int
342 cvs_chdir(const char *path, int rm)
343 {
344 	if (chdir(path) == -1) {
345 		if (rm == 1)
346 			cvs_unlink(path);
347 		fatal("cvs_chdir: `%s': %s", path, strerror(errno));
348 	}
349 
350 	return (0);
351 }
352 
353 /*
354  * cvs_rename()
355  * Change the name of a file.
356  * rename() wrapper with an error message.
357  * Returns 0 on success.
358  */
359 int
360 cvs_rename(const char *from, const char *to)
361 {
362 	if (cvs_server_active == 0)
363 		cvs_log(LP_TRACE, "cvs_rename(%s,%s)", from, to);
364 
365 	if (cvs_noexec == 1)
366 		return (0);
367 
368 	if (rename(from, to) == -1)
369 		fatal("cvs_rename: `%s'->`%s': %s", from, to, strerror(errno));
370 
371 	return (0);
372 }
373 
374 /*
375  * cvs_unlink()
376  *
377  * Removes the link named by <path>.
378  * unlink() wrapper with an error message.
379  * Returns 0 on success, or -1 on failure.
380  */
381 int
382 cvs_unlink(const char *path)
383 {
384 	if (cvs_server_active == 0)
385 		cvs_log(LP_TRACE, "cvs_unlink(%s)", path);
386 
387 	if (cvs_noexec == 1)
388 		return (0);
389 
390 	if (unlink(path) == -1 && errno != ENOENT) {
391 		cvs_log(LP_ERRNO, "%s", path);
392 		return (-1);
393 	}
394 
395 	return (0);
396 }
397 
398 /*
399  * cvs_rmdir()
400  *
401  * Remove a directory tree from disk.
402  * Returns 0 on success, or -1 on failure.
403  */
404 int
405 cvs_rmdir(const char *path)
406 {
407 	int type, ret = -1;
408 	DIR *dirp;
409 	struct dirent *ent;
410 	struct stat st;
411 	char fpath[MAXPATHLEN];
412 
413 	if (cvs_server_active == 0)
414 		cvs_log(LP_TRACE, "cvs_rmdir(%s)", path);
415 
416 	if (cvs_noexec == 1)
417 		return (0);
418 
419 	if ((dirp = opendir(path)) == NULL) {
420 		cvs_log(LP_ERR, "failed to open '%s'", path);
421 		return (-1);
422 	}
423 
424 	while ((ent = readdir(dirp)) != NULL) {
425 		if (!strcmp(ent->d_name, ".") ||
426 		    !strcmp(ent->d_name, ".."))
427 			continue;
428 
429 		(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
430 		    path, ent->d_name);
431 
432 		if (ent->d_type == DT_UNKNOWN) {
433 			if (lstat(fpath, &st) == -1)
434 				fatal("'%s': %s", fpath, strerror(errno));
435 
436 			switch (st.st_mode & S_IFMT) {
437 			case S_IFDIR:
438 				type = CVS_DIR;
439 				break;
440 			case S_IFREG:
441 				type = CVS_FILE;
442 				break;
443 			default:
444 				fatal("'%s': Unknown file type in copy",
445 				    fpath);
446 			}
447 		} else {
448 			switch (ent->d_type) {
449 			case DT_DIR:
450 				type = CVS_DIR;
451 				break;
452 			case DT_REG:
453 				type = CVS_FILE;
454 				break;
455 			default:
456 				fatal("'%s': Unknown file type in copy",
457 				    fpath);
458 			}
459 		}
460 		switch (type) {
461 		case CVS_DIR:
462 			if (cvs_rmdir(fpath) == -1)
463 				goto done;
464 			break;
465 		case CVS_FILE:
466 			if (cvs_unlink(fpath) == -1 && errno != ENOENT)
467 				goto done;
468 			break;
469 		default:
470 			fatal("type %d unknown, shouldn't happen", type);
471 		}
472 	}
473 
474 
475 	if (rmdir(path) == -1 && errno != ENOENT) {
476 		cvs_log(LP_ERRNO, "%s", path);
477 		goto done;
478 	}
479 
480 	ret = 0;
481 done:
482 	closedir(dirp);
483 	return (ret);
484 }
485 
486 void
487 cvs_get_repository_path(const char *dir, char *dst, size_t len)
488 {
489 	char buf[MAXPATHLEN];
490 
491 	cvs_get_repository_name(dir, buf, sizeof(buf));
492 	(void)xsnprintf(dst, len, "%s/%s", current_cvsroot->cr_dir, buf);
493 }
494 
495 void
496 cvs_get_repository_name(const char *dir, char *dst, size_t len)
497 {
498 	FILE *fp;
499 	char *s, fpath[MAXPATHLEN];
500 
501 	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
502 	    dir, CVS_PATH_REPOSITORY);
503 
504 	if ((fp = fopen(fpath, "r")) != NULL) {
505 		if ((fgets(dst, len, fp)) == NULL)
506 			fatal("cvs_get_repository_name: bad repository file");
507 
508 		if ((s = strchr(dst, '\n')) != NULL)
509 			*s = '\0';
510 
511 		(void)fclose(fp);
512 	} else {
513 		dst[0] = '\0';
514 
515 		if (cvs_cmdop == CVS_OP_IMPORT) {
516 			if (strlcpy(dst, import_repository, len) >= len)
517 				fatal("cvs_get_repository_name: truncation");
518 			if (strlcat(dst, "/", len) >= len)
519 				fatal("cvs_get_repository_name: truncation");
520 
521 			if (strcmp(dir, ".")) {
522 				if (strlcat(dst, dir, len) >= len) {
523 					fatal("cvs_get_repository_name: "
524 					    "truncation");
525 				}
526 			}
527 		} else {
528 			if (cvs_cmdop != CVS_OP_CHECKOUT) {
529 				if (strlcat(dst, dir, len) >= len)
530 					fatal("cvs_get_repository_name: "
531 					    "truncation");
532 			}
533 		}
534 	}
535 }
536 
537 void
538 cvs_mkadmin(const char *path, const char *root, const char *repo,
539     char *tag, char *date, int nb)
540 {
541 	FILE *fp;
542 	struct stat st;
543 	char buf[MAXPATHLEN];
544 
545 	if (cvs_server_active == 0)
546 		cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s, %d)",
547 		    path, root, repo, (tag != NULL) ? tag : "",
548 		    (date != NULL) ? date : "", nb);
549 
550 	(void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_CVSDIR);
551 
552 	if (stat(buf, &st) != -1)
553 		return;
554 
555 	if (mkdir(buf, 0755) == -1 && errno != EEXIST)
556 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
557 
558 	(void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_ROOTSPEC);
559 
560 	if ((fp = fopen(buf, "w")) == NULL)
561 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
562 
563 	fprintf(fp, "%s\n", root);
564 	(void)fclose(fp);
565 
566 	(void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_REPOSITORY);
567 
568 	if ((fp = fopen(buf, "w")) == NULL)
569 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
570 
571 	fprintf(fp, "%s\n", repo);
572 	(void)fclose(fp);
573 
574 	(void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_ENTRIES);
575 
576 	if ((fp = fopen(buf, "w")) == NULL)
577 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
578 	(void)fclose(fp);
579 
580 	cvs_write_tagfile(path, tag, date, nb);
581 }
582 
583 void
584 cvs_mkpath(const char *path)
585 {
586 	FILE *fp;
587 	size_t len;
588 	char *sp, *dp, *dir, rpath[MAXPATHLEN], repo[MAXPATHLEN];
589 
590 	dir = xstrdup(path);
591 
592 	STRIP_SLASH(dir);
593 
594 	if (cvs_server_active == 0)
595 		cvs_log(LP_TRACE, "cvs_mkpath(%s)", dir);
596 
597 	repo[0] = '\0';
598 	rpath[0] = '\0';
599 
600 	if (cvs_cmdop == CVS_OP_UPDATE) {
601 		if ((fp = fopen(CVS_PATH_REPOSITORY, "r")) != NULL) {
602 			if ((fgets(repo, sizeof(repo), fp)) == NULL)
603 				fatal("cvs_mkpath: bad repository file");
604 			if ((len = strlen(repo)) && repo[len - 1] == '\n')
605 				repo[len - 1] = '\0';
606 			(void)fclose(fp);
607 		}
608 	}
609 
610 	for (sp = dir; sp != NULL; sp = dp) {
611 		dp = strchr(sp, '/');
612 		if (dp != NULL)
613 			*(dp++) = '\0';
614 
615 		if (repo[0] != '\0') {
616 			len = strlcat(repo, "/", sizeof(repo));
617 			if (len >= (int)sizeof(repo))
618 				fatal("cvs_mkpath: overflow");
619 		}
620 
621 		len = strlcat(repo, sp, sizeof(repo));
622 		if (len >= (int)sizeof(repo))
623 			fatal("cvs_mkpath: overflow");
624 
625 		if (rpath[0] != '\0') {
626 			len = strlcat(rpath, "/", sizeof(rpath));
627 			if (len >= (int)sizeof(rpath))
628 				fatal("cvs_mkpath: overflow");
629 		}
630 
631 		len = strlcat(rpath, sp, sizeof(rpath));
632 		if (len >= (int)sizeof(rpath))
633 			fatal("cvs_mkpath: overflow");
634 
635 		if (mkdir(rpath, 0755) == -1 && errno != EEXIST)
636 			fatal("cvs_mkpath: %s: %s", rpath, strerror(errno));
637 
638 		cvs_mkadmin(rpath, current_cvsroot->cr_str, repo,
639 		    NULL, NULL, 0);
640 	}
641 
642 	xfree(dir);
643 }
644 
645 /*
646  * Split the contents of a file into a list of lines.
647  */
648 struct cvs_lines *
649 cvs_splitlines(u_char *data, size_t len)
650 {
651 	u_char *p, *c;
652 	size_t i, tlen;
653 	struct cvs_lines *lines;
654 	struct cvs_line *lp;
655 
656 	lines = xmalloc(sizeof(*lines));
657 	memset(lines, 0, sizeof(*lines));
658 	TAILQ_INIT(&(lines->l_lines));
659 
660 	lp = xmalloc(sizeof(*lp));
661 	memset(lp, 0, sizeof(*lp));
662 	TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
663 
664 	p = c = data;
665 	for (i = 0; i < len; i++) {
666 		if (*p == '\n' || (i == len - 1)) {
667 			tlen = p - c + 1;
668 			lp = xmalloc(sizeof(*lp));
669 			memset(lp, 0, sizeof(*lp));
670 			lp->l_line = c;
671 			lp->l_len = tlen;
672 			lp->l_lineno = ++(lines->l_nblines);
673 			TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
674 			c = p + 1;
675 		}
676 		p++;
677 	}
678 
679 	return (lines);
680 }
681 
682 void
683 cvs_freelines(struct cvs_lines *lines)
684 {
685 	struct cvs_line *lp;
686 
687 	while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
688 		TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
689 		if (lp->l_needsfree == 1)
690 			xfree(lp->l_line);
691 		xfree(lp);
692 	}
693 
694 	xfree(lines);
695 }
696 
697 BUF *
698 cvs_patchfile(u_char *data, size_t dlen, u_char *patch, size_t plen,
699     int (*p)(struct cvs_lines *, struct cvs_lines *))
700 {
701 	struct cvs_lines *dlines, *plines;
702 	struct cvs_line *lp;
703 	BUF *res;
704 
705 	if ((dlines = cvs_splitlines(data, dlen)) == NULL)
706 		return (NULL);
707 
708 	if ((plines = cvs_splitlines(patch, plen)) == NULL)
709 		return (NULL);
710 
711 	if (p(dlines, plines) < 0) {
712 		cvs_freelines(dlines);
713 		cvs_freelines(plines);
714 		return (NULL);
715 	}
716 
717 	res = cvs_buf_alloc(1024, BUF_AUTOEXT);
718 	TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
719 		if (lp->l_line == NULL)
720 			continue;
721 		cvs_buf_append(res, lp->l_line, lp->l_len);
722 	}
723 
724 	cvs_freelines(dlines);
725 	cvs_freelines(plines);
726 	return (res);
727 }
728 
729 /*
730  * cvs_strsplit()
731  *
732  * Split a string <str> of <sep>-separated values and allocate
733  * an argument vector for the values found.
734  */
735 struct cvs_argvector *
736 cvs_strsplit(char *str, const char *sep)
737 {
738 	struct cvs_argvector *av;
739 	size_t i = 0;
740 	char **nargv;
741 	char *cp, *p;
742 
743 	cp = xstrdup(str);
744 	av = xmalloc(sizeof(*av));
745 	av->str = cp;
746 	av->argv = xcalloc(i + 1, sizeof(*(av->argv)));
747 
748 	while ((p = strsep(&cp, sep)) != NULL) {
749 		av->argv[i++] = p;
750 		nargv = xrealloc(av->argv,
751 		    i + 1, sizeof(*(av->argv)));
752 		av->argv = nargv;
753 	}
754 	av->argv[i] = NULL;
755 
756 	return (av);
757 }
758 
759 /*
760  * cvs_argv_destroy()
761  *
762  * Free an argument vector previously allocated by cvs_strsplit().
763  */
764 void
765 cvs_argv_destroy(struct cvs_argvector *av)
766 {
767 	xfree(av->str);
768 	xfree(av->argv);
769 	xfree(av);
770 }
771 
772 u_int
773 cvs_revision_select(RCSFILE *file, char *range)
774 {
775 	int i;
776 	u_int nrev;
777 	char *lstr, *rstr;
778 	struct rcs_delta *rdp;
779 	struct cvs_argvector *revargv, *revrange;
780 	RCSNUM *lnum, *rnum;
781 
782 	nrev = 0;
783 	lnum = rnum = NULL;
784 
785 	revargv = cvs_strsplit(range, ",");
786 	for (i = 0; revargv->argv[i] != NULL; i++) {
787 		revrange = cvs_strsplit(revargv->argv[i], ":");
788 		if (revrange->argv[0] == NULL)
789 			fatal("invalid revision range: %s", revargv->argv[i]);
790 		else if (revrange->argv[1] == NULL)
791 			lstr = rstr = revrange->argv[0];
792 		else {
793 			if (revrange->argv[2] != NULL)
794 				fatal("invalid revision range: %s",
795 				    revargv->argv[i]);
796 
797 			lstr = revrange->argv[0];
798 			rstr = revrange->argv[1];
799 
800 			if (strcmp(lstr, "") == 0)
801 				lstr = NULL;
802 			if (strcmp(rstr, "") == 0)
803 				rstr = NULL;
804 		}
805 
806 		if (lstr == NULL)
807 			lstr = RCS_HEAD_INIT;
808 
809 		if ((lnum = rcs_translate_tag(lstr, file)) == NULL)
810 			fatal("cvs_revision_select: could not translate tag `%s'", lstr);
811 
812 		if (rstr != NULL) {
813 			if ((rnum = rcs_translate_tag(rstr, file)) == NULL)
814 				fatal("cvs_revision_select: could not translate tag `%s'", rstr);
815 		} else {
816 			rnum = rcsnum_alloc();
817 			rcsnum_cpy(file->rf_head, rnum, 0);
818 		}
819 
820 		cvs_argv_destroy(revrange);
821 
822 		TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) {
823 			if (rcsnum_cmp(rdp->rd_num, lnum, 0) <= 0 &&
824 			    rcsnum_cmp(rdp->rd_num, rnum, 0) >= 0 &&
825 			    !(rdp->rd_flags & RCS_RD_SELECT)) {
826 				rdp->rd_flags |= RCS_RD_SELECT;
827 				nrev++;
828 			}
829 		}
830 	}
831 
832 	cvs_argv_destroy(revargv);
833 
834 	if (lnum != NULL)
835 		rcsnum_free(lnum);
836 	if (rnum != NULL)
837 		rcsnum_free(rnum);
838 
839 	return (nrev);
840 }
841 
842 int
843 cvs_yesno(void)
844 {
845 	int c, ret;
846 
847 	ret = 0;
848 
849 	fflush(stderr);
850 	fflush(stdout);
851 
852 	if ((c = getchar()) != 'y' && c != 'Y')
853 		ret = -1;
854 	else
855 		while (c != EOF && c != '\n')
856 			c = getchar();
857 
858 	return (ret);
859 }
860