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