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