xref: /openbsd-src/usr.bin/cvs/util.c (revision cf2525843d483a385de106a1361b2b9c18d96583)
1 /*	$OpenBSD: util.c,v 1.90 2006/07/09 01:47:20 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, int fds[3])
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_ERR, "cannot remove `%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 ret = -1;
466 	size_t len;
467 	DIR *dirp;
468 	struct dirent *ent;
469 	char fpath[MAXPATHLEN];
470 
471 	if (cvs_server_active == 0)
472 		cvs_log(LP_TRACE, "cvs_rmdir(%s)", path);
473 
474 	if (cvs_noexec == 1)
475 		return (0);
476 
477 	if ((dirp = opendir(path)) == NULL) {
478 		cvs_log(LP_ERR, "failed to open '%s'", path);
479 		return (-1);
480 	}
481 
482 	while ((ent = readdir(dirp)) != NULL) {
483 		if (!strcmp(ent->d_name, ".") ||
484 		    !strcmp(ent->d_name, ".."))
485 			continue;
486 
487 		len = cvs_path_cat(path, ent->d_name, fpath, sizeof(fpath));
488 		if (len >= sizeof(fpath))
489 			fatal("cvs_rmdir: path truncation");
490 
491 		if (ent->d_type == DT_DIR) {
492 			if (cvs_rmdir(fpath) == -1)
493 				goto done;
494 		} else if (cvs_unlink(fpath) == -1 && errno != ENOENT)
495 			goto done;
496 	}
497 
498 
499 	if (rmdir(path) == -1 && errno != ENOENT) {
500 		cvs_log(LP_ERR, "failed to remove '%s'", path);
501 		goto done;
502 	}
503 
504 	ret = 0;
505 done:
506 	closedir(dirp);
507 	return (ret);
508 }
509 
510 /*
511  * cvs_path_cat()
512  *
513  * Concatenate the two paths <base> and <end> and store the generated path
514  * into the buffer <dst>, which can accept up to <dlen> bytes, including the
515  * NUL byte.  The result is guaranteed to be NUL-terminated.
516  * Returns the number of bytes necessary to store the full resulting path,
517  * not including the NUL byte (a value equal to or larger than <dlen>
518  * indicates truncation).
519  */
520 size_t
521 cvs_path_cat(const char *base, const char *end, char *dst, size_t dlen)
522 {
523 	size_t len;
524 
525 	len = strlcpy(dst, base, dlen);
526 	if (len >= dlen - 1) {
527 		errno = ENAMETOOLONG;
528 		fatal("overflow in cvs_path_cat");
529 	} else {
530 		dst[len] = '/';
531 		dst[len + 1] = '\0';
532 		len = strlcat(dst, end, dlen);
533 		if (len >= dlen) {
534 			errno = ENAMETOOLONG;
535 			cvs_log(LP_ERR, "%s", dst);
536 		}
537 	}
538 
539 	return (len);
540 }
541 
542 /*
543  * a hack to mimic and thus match gnu cvs behaviour.
544  */
545 time_t
546 cvs_hack_time(time_t oldtime, int togmt)
547 {
548 	int l;
549 	struct tm *t;
550 	char tbuf[32];
551 
552 	if (togmt == 1) {
553 		t = gmtime(&oldtime);
554 		if (t == NULL)
555 			fatal("gmtime failed");
556 
557 		return (mktime(t));
558 	}
559 
560 	t = localtime(&oldtime);
561 
562 	l = snprintf(tbuf, sizeof(tbuf), "%d/%d/%d GMT %d:%d:%d",
563 	    t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour,
564 	    t->tm_min, t->tm_sec);
565 	if (l == -1 || l >= (int)sizeof(tbuf))
566 		fatal("cvs_hack_time: overflow");
567 
568 	return (cvs_date_parse(tbuf));
569 }
570 
571 void
572 cvs_get_repository_path(const char *dir, char *dst, size_t len)
573 {
574 	int l;
575 	char buf[MAXPATHLEN];
576 
577 	cvs_get_repository_name(dir, buf, sizeof(buf));
578 	l = snprintf(dst, len, "%s/%s", current_cvsroot->cr_dir, buf);
579 	if (l == -1 || l >= (int)len)
580 		fatal("cvs_get_repository_path: overflow");
581 }
582 
583 void
584 cvs_get_repository_name(const char *dir, char *dst, size_t len)
585 {
586 	int l;
587 	FILE *fp;
588 	char *s, fpath[MAXPATHLEN];
589 
590 	l = snprintf(fpath, sizeof(fpath), "%s/%s", dir, CVS_PATH_REPOSITORY);
591 	if (l == -1 || l >= (int)sizeof(fpath))
592 		fatal("cvs_get_repository_name: overflow");
593 
594 	if ((fp = fopen(fpath, "r")) != NULL) {
595 		fgets(dst, len, fp);
596 
597 		if ((s = strchr(dst, '\n')) != NULL)
598 			*s = '\0';
599 
600 		(void)fclose(fp);
601 	} else {
602 		dst[0] = '\0';
603 
604 		if (cvs_cmdop == CVS_OP_IMPORT) {
605 			if (strlcpy(dst, import_repository, len) >= len)
606 				fatal("cvs_get_repository_name: truncation");
607 			if (strlcat(dst, "/", len) >= len)
608 				fatal("cvs_get_repository_name: truncation");
609 
610 			if (strcmp(dir, ".")) {
611 				if (strlcat(dst, dir, len) >= len) {
612 					fatal("cvs_get_repository_name: "
613 					    "truncation");
614 				}
615 			}
616 		} else {
617 			if (strlcat(dst, dir, len) >= len)
618 				fatal("cvs_get_repository_name: truncation");
619 		}
620 	}
621 }
622 
623 void
624 cvs_mkadmin(const char *path, const char *root, const char *repo,
625     char *tag, char *date, int nb)
626 {
627 	FILE *fp;
628 	size_t len;
629 	struct stat st;
630 	char buf[MAXPATHLEN];
631 
632 	if (cvs_server_active == 0)
633 		cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s, %d)",
634 		    path, root, repo, (tag != NULL) ? tag : "",
635 		    (date != NULL) ? date : "", nb);
636 
637 	len = cvs_path_cat(path, CVS_PATH_CVSDIR, buf, sizeof(buf));
638 	if (len >= sizeof(buf))
639 		fatal("cvs_mkadmin: truncation");
640 
641 	if (stat(buf, &st) != -1)
642 		return;
643 
644 	if (mkdir(buf, 0755) == -1 && errno != EEXIST)
645 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
646 
647 	len = cvs_path_cat(path, CVS_PATH_ROOTSPEC, buf, sizeof(buf));
648 	if (len >= sizeof(buf))
649 		fatal("cvs_mkadmin: truncation");
650 
651 	if ((fp = fopen(buf, "w")) == NULL)
652 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
653 
654 	fprintf(fp, "%s\n", root);
655 	(void)fclose(fp);
656 
657 	len = cvs_path_cat(path, CVS_PATH_REPOSITORY, buf, sizeof(buf));
658 	if (len >= sizeof(buf))
659 		fatal("cvs_mkadmin: truncation");
660 
661 	if ((fp = fopen(buf, "w")) == NULL)
662 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
663 
664 	fprintf(fp, "%s\n", repo);
665 	(void)fclose(fp);
666 
667 	len = cvs_path_cat(path, CVS_PATH_ENTRIES, buf, sizeof(buf));
668 	if (len >= sizeof(buf))
669 		fatal("cvs_mkadmin: truncation");
670 
671 	if ((fp = fopen(buf, "w")) == NULL)
672 		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
673 	(void)fclose(fp);
674 
675 	cvs_write_tagfile(path, tag, date, nb);
676 }
677 
678 void
679 cvs_mkpath(const char *path)
680 {
681 	FILE *fp;
682 	size_t len;
683 	char *sp, *dp, *dir, rpath[MAXPATHLEN], repo[MAXPATHLEN];
684 
685 	dir = xstrdup(path);
686 
687 	STRIP_SLASH(dir);
688 
689 	if (cvs_server_active == 0)
690 		cvs_log(LP_TRACE, "cvs_mkpath(%s)", dir);
691 
692 	repo[0] = '\0';
693 	rpath[0] = '\0';
694 
695 	if (cvs_cmdop == CVS_OP_UPDATE) {
696 		if ((fp = fopen(CVS_PATH_REPOSITORY, "r")) != NULL) {
697 			fgets(repo, sizeof(repo), fp);
698 			if (repo[strlen(repo) - 1] == '\n')
699 				repo[strlen(repo) - 1] = '\0';
700 			(void)fclose(fp);
701 		}
702 	}
703 
704 	for (sp = dir; sp != NULL; sp = dp) {
705 		dp = strchr(sp, '/');
706 		if (dp != NULL)
707 			*(dp++) = '\0';
708 
709 		if (repo[0] != '\0') {
710 			len = strlcat(repo, "/", sizeof(repo));
711 			if (len >= (int)sizeof(repo))
712 				fatal("cvs_mkpath: overflow");
713 		}
714 
715 		len = strlcat(repo, sp, sizeof(repo));
716 		if (len >= (int)sizeof(repo))
717 			fatal("cvs_mkpath: overflow");
718 
719 		if (rpath[0] != '\0') {
720 			len = strlcat(rpath, "/", sizeof(rpath));
721 			if (len >= (int)sizeof(rpath))
722 				fatal("cvs_mkpath: overflow");
723 		}
724 
725 		len = strlcat(rpath, sp, sizeof(rpath));
726 		if (len >= (int)sizeof(rpath))
727 			fatal("cvs_mkpath: overflow");
728 
729 		if (mkdir(rpath, 0755) == -1 && errno != EEXIST)
730 			fatal("cvs_mkpath: %s: %s", rpath, strerror(errno));
731 
732 		cvs_mkadmin(rpath, current_cvsroot->cr_dir, repo,
733 		    NULL, NULL, 0);
734 	}
735 
736 	xfree(dir);
737 }
738 
739 /*
740  * Split the contents of a file into a list of lines.
741  */
742 struct cvs_lines *
743 cvs_splitlines(const char *fcont)
744 {
745 	char *dcp;
746 	struct cvs_lines *lines;
747 	struct cvs_line *lp;
748 
749 	lines = xmalloc(sizeof(*lines));
750 	TAILQ_INIT(&(lines->l_lines));
751 	lines->l_nblines = 0;
752 	lines->l_data = xstrdup(fcont);
753 
754 	lp = xmalloc(sizeof(*lp));
755 	lp->l_line = NULL;
756 	lp->l_lineno = 0;
757 	TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
758 
759 	for (dcp = lines->l_data; *dcp != '\0';) {
760 		lp = xmalloc(sizeof(*lp));
761 		lp->l_line = dcp;
762 		lp->l_lineno = ++(lines->l_nblines);
763 		TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
764 
765 		dcp = strchr(dcp, '\n');
766 		if (dcp == NULL)
767 			break;
768 		*(dcp++) = '\0';
769 	}
770 
771 	return (lines);
772 }
773 
774 void
775 cvs_freelines(struct cvs_lines *lines)
776 {
777 	struct cvs_line *lp;
778 
779 	while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
780 		TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
781 		xfree(lp);
782 	}
783 
784 	xfree(lines->l_data);
785 	xfree(lines);
786 }
787 
788 BUF *
789 cvs_patchfile(const char *data, const char *patch,
790     int (*p)(struct cvs_lines *, struct cvs_lines *))
791 {
792 	struct cvs_lines *dlines, *plines;
793 	struct cvs_line *lp;
794 	size_t len;
795 	int lineno;
796 	BUF *res;
797 
798 	len = strlen(data);
799 
800 	if ((dlines = cvs_splitlines(data)) == NULL)
801 		return (NULL);
802 
803 	if ((plines = cvs_splitlines(patch)) == NULL)
804 		return (NULL);
805 
806 	if (p(dlines, plines) < 0) {
807 		cvs_freelines(dlines);
808 		cvs_freelines(plines);
809 		return (NULL);
810 	}
811 
812 	lineno = 0;
813 	res = cvs_buf_alloc(len, BUF_AUTOEXT);
814 	TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
815 		if (lineno != 0)
816 			cvs_buf_fappend(res, "%s\n", lp->l_line);
817 		lineno++;
818 	}
819 
820 	cvs_freelines(dlines);
821 	cvs_freelines(plines);
822 	return (res);
823 }
824 
825 /*
826  * cvs_strsplit()
827  *
828  * Split a string <str> of <sep>-separated values and allocate
829  * an argument vector for the values found.
830  */
831 struct cvs_argvector *
832 cvs_strsplit(char *str, const char *sep)
833 {
834 	struct cvs_argvector *av;
835 	size_t i = 0;
836 	char **nargv;
837 	char *cp, *p;
838 
839 	cp = xstrdup(str);
840 	av = xmalloc(sizeof(*av));
841 	av->str = cp;
842 	av->argv = xcalloc(i + 1, sizeof(*(av->argv)));
843 
844 	while ((p = strsep(&cp, sep)) != NULL) {
845 		av->argv[i++] = p;
846 		nargv = xrealloc(av->argv,
847 		    i + 1, sizeof(*(av->argv)));
848 		av->argv = nargv;
849 	}
850 	av->argv[i] = NULL;
851 
852 	return (av);
853 }
854 
855 /*
856  * cvs_argv_destroy()
857  *
858  * Free an argument vector previously allocated by cvs_strsplit().
859  */
860 void
861 cvs_argv_destroy(struct cvs_argvector *av)
862 {
863 	xfree(av->str);
864 	xfree(av->argv);
865 	xfree(av);
866 }
867 
868 u_int
869 cvs_revision_select(RCSFILE *file, char *range)
870 {
871 	int i;
872 	u_int nrev;
873 	char *lstr, *rstr;
874 	struct rcs_delta *rdp;
875 	struct cvs_argvector *revargv, *revrange;
876 	RCSNUM *lnum, *rnum;
877 
878 	nrev = 0;
879 	lnum = rnum = NULL;
880 
881 	revargv = cvs_strsplit(range, ",");
882 	for (i = 0; revargv->argv[i] != NULL; i++) {
883 		revrange = cvs_strsplit(revargv->argv[i], ":");
884 		if (revrange->argv[0] == NULL)
885 			fatal("invalid revision range: %s", revargv->argv[i]);
886 		else if (revrange->argv[1] == NULL)
887 			lstr = rstr = revrange->argv[0];
888 		else {
889 			if (revrange->argv[2] != NULL)
890 				fatal("invalid revision range: %s",
891 				    revargv->argv[i]);
892 
893 			lstr = revrange->argv[0];
894 			rstr = revrange->argv[1];
895 
896 			if (strcmp(lstr, "") == 0)
897 				lstr = NULL;
898 			if (strcmp(rstr, "") == 0)
899 				rstr = NULL;
900 		}
901 
902 		if (lstr == NULL)
903 			lstr = RCS_HEAD_INIT;
904 
905 		lnum = rcs_translate_tag(lstr, file);
906 
907 		if (rstr != NULL) {
908 			rnum = rcs_translate_tag(rstr, file);
909 		} else {
910 			rnum = rcsnum_alloc();
911 			rcsnum_cpy(file->rf_head, rnum, 0);
912 		}
913 
914 		cvs_argv_destroy(revrange);
915 
916 		TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) {
917 			if (rcsnum_cmp(rdp->rd_num, lnum, 0) <= 0 &&
918 			    rcsnum_cmp(rdp->rd_num, rnum, 0) >= 0 &&
919 			    !(rdp->rd_flags & RCS_RD_SELECT)) {
920 				rdp->rd_flags |= RCS_RD_SELECT;
921 				nrev++;
922 			}
923 		}
924 	}
925 
926 	cvs_argv_destroy(revargv);
927 
928 	if (lnum != NULL)
929 		rcsnum_free(lnum);
930 	if (rnum != NULL)
931 		rcsnum_free(rnum);
932 
933 	return (nrev);
934 }
935