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