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