[BACK]Return to util.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / cvs

File: [local] / src / usr.bin / cvs / util.c (download)

Revision 1.147, Sat Jun 21 15:39:15 2008 UTC (15 years, 11 months ago) by joris
Branch: MAIN
CVS Tags: OPENBSD_4_4_BASE, OPENBSD_4_4
Changes since 1.146: +20 -1 lines

add a hash table mechanism based upon hcreate(3) but one that allows
us to maintain multiple hash tables concurrently.

immediatly start using it to keep track of what directories
we have already created and what CVS dirs we already created so
we do not recreate them when we do not need to.

we will be switching more internals to use this soon.
rejoice for cheaper lookups.

ok tobias@

/*	$OpenBSD: util.c,v 1.147 2008/06/21 15:39:15 joris Exp $	*/
/*
 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
 * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
 * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <atomicio.h>
#include <errno.h>
#include <fcntl.h>
#include <md5.h>
#include <stdlib.h>
#include <string.h>
#include <paths.h>
#include <unistd.h>

#include "cvs.h"
#include "remote.h"
#include "hash.h"

extern int print_stdout;
extern int build_dirs;
extern int disable_fast_checkout;

/* letter -> mode type map */
static const int cvs_modetypes[26] = {
	-1, -1, -1, -1, -1, -1,  1, -1, -1, -1, -1, -1, -1,
	-1,  2, -1, -1, -1, -1, -1,  0, -1, -1, -1, -1, -1,
};

/* letter -> mode map */
static const mode_t cvs_modes[3][26] = {
	{
		0,  0,       0,       0,       0,  0,  0,    /* a - g */
		0,  0,       0,       0,       0,  0,  0,    /* h - m */
		0,  0,       0,       S_IRUSR, 0,  0,  0,    /* n - u */
		0,  S_IWUSR, S_IXUSR, 0,       0             /* v - z */
	},
	{
		0,  0,       0,       0,       0,  0,  0,    /* a - g */
		0,  0,       0,       0,       0,  0,  0,    /* h - m */
		0,  0,       0,       S_IRGRP, 0,  0,  0,    /* n - u */
		0,  S_IWGRP, S_IXGRP, 0,       0             /* v - z */
	},
	{
		0,  0,       0,       0,       0,  0,  0,    /* a - g */
		0,  0,       0,       0,       0,  0,  0,    /* h - m */
		0,  0,       0,       S_IROTH, 0,  0,  0,    /* n - u */
		0,  S_IWOTH, S_IXOTH, 0,       0             /* v - z */
	}
};


/* octal -> string */
static const char *cvs_modestr[8] = {
	"", "x", "w", "wx", "r", "rx", "rw", "rwx"
};

/*
 * cvs_strtomode()
 *
 * Read the contents of the string <str> and generate a permission mode from
 * the contents of <str>, which is assumed to have the mode format of CVS.
 * The CVS protocol specification states that any modes or mode types that are
 * not recognized should be silently ignored.  This function does not return
 * an error in such cases, but will issue warnings.
 */
void
cvs_strtomode(const char *str, mode_t *mode)
{
	char type;
	size_t l;
	mode_t m;
	char buf[32], ms[4], *sp, *ep;

	m = 0;
	l = strlcpy(buf, str, sizeof(buf));
	if (l >= sizeof(buf))
		fatal("cvs_strtomode: string truncation");

	sp = buf;
	ep = sp;

	for (sp = buf; ep != NULL; sp = ep + 1) {
		ep = strchr(sp, ',');
		if (ep != NULL)
			*ep = '\0';

		memset(ms, 0, sizeof ms);
		if (sscanf(sp, "%c=%3s", &type, ms) != 2 &&
			sscanf(sp, "%c=", &type) != 1) {
			fatal("failed to scan mode string `%s'", sp);
		}

		if (type <= 'a' || type >= 'z' ||
		    cvs_modetypes[type - 'a'] == -1) {
			cvs_log(LP_ERR,
			    "invalid mode type `%c'"
			    " (`u', `g' or `o' expected), ignoring", type);
			continue;
		}

		/* make type contain the actual mode index */
		type = cvs_modetypes[type - 'a'];

		for (sp = ms; *sp != '\0'; sp++) {
			if (*sp <= 'a' || *sp >= 'z' ||
			    cvs_modes[(int)type][*sp - 'a'] == 0) {
				fatal("invalid permission bit `%c'", *sp);
			} else
				m |= cvs_modes[(int)type][*sp - 'a'];
		}
	}

	*mode = m;
}

/*
 * cvs_modetostr()
 *
 * Generate a CVS-format string to represent the permissions mask on a file
 * from the mode <mode> and store the result in <buf>, which can accept up to
 * <len> bytes (including the terminating NUL byte).  The result is guaranteed
 * to be NUL-terminated.
 */
void
cvs_modetostr(mode_t mode, char *buf, size_t len)
{
	char tmp[16], *bp;
	mode_t um, gm, om;

	um = (mode & S_IRWXU) >> 6;
	gm = (mode & S_IRWXG) >> 3;
	om = mode & S_IRWXO;

	bp = buf;
	*bp = '\0';

	if (um) {
		if (strlcpy(tmp, "u=", sizeof(tmp)) >= sizeof(tmp) ||
		    strlcat(tmp, cvs_modestr[um], sizeof(tmp)) >= sizeof(tmp))
			fatal("cvs_modetostr: overflow for user mode");

		if (strlcat(buf, tmp, len) >= len)
			fatal("cvs_modetostr: string truncation");
	}

	if (gm) {
		if (um) {
			if (strlcat(buf, ",", len) >= len)
				fatal("cvs_modetostr: string truncation");
		}

		if (strlcpy(tmp, "g=", sizeof(tmp)) >= sizeof(tmp) ||
		    strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp))
			fatal("cvs_modetostr: overflow for group mode");

		if (strlcat(buf, tmp, len) >= len)
			fatal("cvs_modetostr: string truncation");
	}

	if (om) {
		if (um || gm) {
			if (strlcat(buf, ",", len) >= len)
				fatal("cvs_modetostr: string truncation");
		}

		if (strlcpy(tmp, "o=", sizeof(tmp)) >= sizeof(tmp) ||
		    strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp))
			fatal("cvs_modetostr: overflow for others mode");

		if (strlcat(buf, tmp, len) >= len)
			fatal("cvs_modetostr: string truncation");
	}
}

/*
 * cvs_cksum()
 *
 * Calculate the MD5 checksum of the file whose path is <file> and generate
 * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
 * given in <len> and must be at least 33.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_cksum(const char *file, char *dst, size_t len)
{
	if (len < CVS_CKSUM_LEN) {
		cvs_log(LP_ERR, "buffer too small for checksum");
		return (-1);
	}
	if (MD5File(file, dst) == NULL) {
		cvs_log(LP_ERR, "failed to generate checksum for %s", file);
		return (-1);
	}

	return (0);
}

/*
 * cvs_getargv()
 *
 * Parse a line contained in <line> and generate an argument vector by
 * splitting the line on spaces and tabs.  The resulting vector is stored in
 * <argv>, which can accept up to <argvlen> entries.
 * Returns the number of arguments in the vector, or -1 if an error occurred.
 */
int
cvs_getargv(const char *line, char **argv, int argvlen)
{
	u_int i;
	int argc, error;
	char *linebuf, *lp, *cp;

	linebuf = xstrdup(line);

	memset(argv, 0, argvlen * sizeof(char *));
	argc = 0;

	/* build the argument vector */
	error = 0;
	for (lp = linebuf; lp != NULL;) {
		cp = strsep(&lp, " \t");
		if (cp == NULL)
			break;
		else if (*cp == '\0')
			continue;

		if (argc == argvlen) {
			error++;
			break;
		}

		argv[argc] = xstrdup(cp);
		argc++;
	}

	if (error != 0) {
		/* ditch the argument vector */
		for (i = 0; i < (u_int)argc; i++)
			xfree(argv[i]);
		argc = -1;
	}

	xfree(linebuf);
	return (argc);
}

/*
 * cvs_makeargv()
 *
 * Allocate an argument vector large enough to accommodate for all the
 * arguments found in <line> and return it.
 */
char **
cvs_makeargv(const char *line, int *argc)
{
	int i, ret;
	char *argv[1024], **copy;

	ret = cvs_getargv(line, argv, 1024);
	if (ret == -1)
		return (NULL);

	copy = xcalloc(ret + 1, sizeof(char *));

	for (i = 0; i < ret; i++)
		copy[i] = argv[i];
	copy[ret] = NULL;

	*argc = ret;
	return (copy);
}

/*
 * cvs_freeargv()
 *
 * Free an argument vector previously generated by cvs_getargv().
 */
void
cvs_freeargv(char **argv, int argc)
{
	int i;

	for (i = 0; i < argc; i++)
		if (argv[i] != NULL)
			xfree(argv[i]);
}

/*
 * cvs_chdir()
 *
 * Change to directory <path>.
 * If <rm> is equal to `1', <path> is removed if chdir() fails so we
 * do not have temporary directories leftovers.
 * Returns 0 on success.
 */
int
cvs_chdir(const char *path, int rm)
{
	if (chdir(path) == -1) {
		if (rm == 1)
			cvs_unlink(path);
		fatal("cvs_chdir: `%s': %s", path, strerror(errno));
	}

	return (0);
}

/*
 * cvs_rename()
 * Change the name of a file.
 * rename() wrapper with an error message.
 * Returns 0 on success.
 */
int
cvs_rename(const char *from, const char *to)
{
	if (cvs_server_active == 0)
		cvs_log(LP_TRACE, "cvs_rename(%s,%s)", from, to);

	if (cvs_noexec == 1)
		return (0);

	if (rename(from, to) == -1)
		fatal("cvs_rename: `%s'->`%s': %s", from, to, strerror(errno));

	return (0);
}

/*
 * cvs_unlink()
 *
 * Removes the link named by <path>.
 * unlink() wrapper with an error message.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_unlink(const char *path)
{
	if (cvs_server_active == 0)
		cvs_log(LP_TRACE, "cvs_unlink(%s)", path);

	if (cvs_noexec == 1 && disable_fast_checkout != 0)
		return (0);

	if (unlink(path) == -1 && errno != ENOENT) {
		cvs_log(LP_ERRNO, "%s", path);
		return (-1);
	}

	return (0);
}

/*
 * cvs_rmdir()
 *
 * Remove a directory tree from disk.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_rmdir(const char *path)
{
	int type, ret = -1;
	DIR *dirp;
	struct dirent *ent;
	struct stat st;
	char fpath[MAXPATHLEN];

	if (cvs_server_active == 0)
		cvs_log(LP_TRACE, "cvs_rmdir(%s)", path);

	if (cvs_noexec == 1 && disable_fast_checkout != 0)
		return (0);

	if ((dirp = opendir(path)) == NULL) {
		cvs_log(LP_ERR, "failed to open '%s'", path);
		return (-1);
	}

	while ((ent = readdir(dirp)) != NULL) {
		if (!strcmp(ent->d_name, ".") ||
		    !strcmp(ent->d_name, ".."))
			continue;

		(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
		    path, ent->d_name);

		if (ent->d_type == DT_UNKNOWN) {
			if (lstat(fpath, &st) == -1)
				fatal("'%s': %s", fpath, strerror(errno));

			switch (st.st_mode & S_IFMT) {
			case S_IFDIR:
				type = CVS_DIR;
				break;
			case S_IFREG:
				type = CVS_FILE;
				break;
			default:
				fatal("'%s': Unknown file type in copy",
				    fpath);
			}
		} else {
			switch (ent->d_type) {
			case DT_DIR:
				type = CVS_DIR;
				break;
			case DT_REG:
				type = CVS_FILE;
				break;
			default:
				fatal("'%s': Unknown file type in copy",
				    fpath);
			}
		}
		switch (type) {
		case CVS_DIR:
			if (cvs_rmdir(fpath) == -1)
				goto done;
			break;
		case CVS_FILE:
			if (cvs_unlink(fpath) == -1 && errno != ENOENT)
				goto done;
			break;
		default:
			fatal("type %d unknown, shouldn't happen", type);
		}
	}


	if (rmdir(path) == -1 && errno != ENOENT) {
		cvs_log(LP_ERRNO, "%s", path);
		goto done;
	}

	ret = 0;
done:
	closedir(dirp);
	return (ret);
}

void
cvs_get_repository_path(const char *dir, char *dst, size_t len)
{
	char buf[MAXPATHLEN];

	cvs_get_repository_name(dir, buf, sizeof(buf));
	(void)xsnprintf(dst, len, "%s/%s", current_cvsroot->cr_dir, buf);
	cvs_validate_directory(dst);
}

void
cvs_get_repository_name(const char *dir, char *dst, size_t len)
{
	FILE *fp;
	char fpath[MAXPATHLEN];

	dst[0] = '\0';

	if (!(cmdp->cmd_flags & CVS_USE_WDIR)) {
		if (strlcpy(dst, dir, len) >= len)
			fatal("cvs_get_repository_name: truncation");
		return;
	}

	switch (cvs_cmdop) {
	case CVS_OP_EXPORT:
		if (strcmp(dir, "."))
			if (strlcpy(dst, dir, len) >= len)
				fatal("cvs_get_repository_name: truncation");
		break;
	case CVS_OP_IMPORT:
		if (strlcpy(dst, import_repository, len) >= len)
			fatal("cvs_get_repository_name: truncation");
		if (strlcat(dst, "/", len) >= len)
			fatal("cvs_get_repository_name: truncation");

		if (strcmp(dir, "."))
			if (strlcat(dst, dir, len) >= len)
				fatal("cvs_get_repository_name: truncation");
		break;
	default:
		(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
		    dir, CVS_PATH_REPOSITORY);
		if ((fp = fopen(fpath, "r")) != NULL) {
			if ((fgets(dst, len, fp)) == NULL)
				fatal("%s: bad repository file", fpath);
			dst[strcspn(dst, "\n")] = '\0';
			(void)fclose(fp);
		} else if (cvs_cmdop != CVS_OP_CHECKOUT)
			fatal("%s is missing", fpath);
		break;
	}
}

void
cvs_mkadmin(const char *path, const char *root, const char *repo,
    char *tag, char *date)
{
	FILE *fp;
	int fd;
	char buf[MAXPATHLEN];
	struct hash_data *hdata, hd;

	hdata = hash_table_find(&created_cvs_directories, path, strlen(path));
	if (hdata != NULL)
		return;

	hd.h_key = xstrdup(path);
	hd.h_data = NULL;
	hash_table_enter(&created_cvs_directories, &hd);

	if (cvs_server_active == 0)
		cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s)",
		    path, root, repo, (tag != NULL) ? tag : "",
		    (date != NULL) ? date : "");

	(void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_CVSDIR);

	if (mkdir(buf, 0755) == -1 && errno != EEXIST)
		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));

	if (cvs_cmdop == CVS_OP_CHECKOUT || cvs_cmdop == CVS_OP_ADD ||
	    (cvs_cmdop == CVS_OP_UPDATE && build_dirs == 1)) {
		(void)xsnprintf(buf, sizeof(buf), "%s/%s",
		    path, CVS_PATH_ROOTSPEC);

		if ((fp = fopen(buf, "w")) == NULL)
			fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));

		fprintf(fp, "%s\n", root);
		(void)fclose(fp);
	}

	(void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_REPOSITORY);

	if ((fp = fopen(buf, "w")) == NULL)
		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));

	fprintf(fp, "%s\n", repo);
	(void)fclose(fp);

	cvs_write_tagfile(path, tag, date);

	(void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_ENTRIES);

	if ((fd = open(buf, O_WRONLY|O_CREAT|O_EXCL, 0666 & ~cvs_umask))
	    == -1) {
		if (errno == EEXIST)
			return;
		fatal("cvs_mkadmin: %s: %s", buf, strerror(errno));
	}

	if (atomicio(vwrite, fd, "D\n", 2) != 2)
		fatal("cvs_mkadmin: %s", strerror(errno));
	close(fd);
}

void
cvs_mkpath(const char *path, char *tag)
{
	CVSENTRIES *ent;
	FILE *fp;
	size_t len;
	struct hash_data *hdata, hd;
	char *entry, sticky[CVS_REV_BUFSZ];
	char *sp, *dp, *dir, *p, rpath[MAXPATHLEN], repo[MAXPATHLEN];

	hdata = hash_table_find(&created_directories, path, strlen(path));
	if (hdata != NULL)
		return;

	hd.h_key = xstrdup(path);
	hd.h_data = NULL;
	hash_table_enter(&created_directories, &hd);

	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL ||
	    cvs_server_active == 1)
		cvs_validate_directory(path);

	dir = xstrdup(path);

	STRIP_SLASH(dir);

	if (cvs_server_active == 0)
		cvs_log(LP_TRACE, "cvs_mkpath(%s)", dir);

	repo[0] = '\0';
	rpath[0] = '\0';

	if (cvs_cmdop == CVS_OP_UPDATE || cvs_cmdop == CVS_OP_COMMIT) {
		if ((fp = fopen(CVS_PATH_REPOSITORY, "r")) != NULL) {
			if ((fgets(repo, sizeof(repo), fp)) == NULL)
				fatal("cvs_mkpath: bad repository file");
			repo[strcspn(repo, "\n")] = '\0';
			(void)fclose(fp);
		}
	}

	for (sp = dir; sp != NULL; sp = dp) {
		dp = strchr(sp, '/');
		if (dp != NULL)
			*(dp++) = '\0';

		if (sp == dir && module_repo_root != NULL) {
			len = strlcpy(repo, module_repo_root, sizeof(repo));
			if (len >= (int)sizeof(repo))
				fatal("cvs_mkpath: overflow");
		} else if (strcmp(sp, ".")) {
			if (repo[0] != '\0') {
				len = strlcat(repo, "/", sizeof(repo));
				if (len >= (int)sizeof(repo))
					fatal("cvs_mkpath: overflow");
			}

			len = strlcat(repo, sp, sizeof(repo));
			if (len >= (int)sizeof(repo))
				fatal("cvs_mkpath: overflow");
		}

		if (rpath[0] != '\0') {
			len = strlcat(rpath, "/", sizeof(rpath));
			if (len >= (int)sizeof(rpath))
				fatal("cvs_mkpath: overflow");
		}

		len = strlcat(rpath, sp, sizeof(rpath));
		if (len >= (int)sizeof(rpath))
			fatal("cvs_mkpath: overflow");

		if (mkdir(rpath, 0755) == -1 && errno != EEXIST)
			fatal("cvs_mkpath: %s: %s", rpath, strerror(errno));

		if (cvs_cmdop == CVS_OP_EXPORT && !cvs_server_active)
			continue;

		cvs_mkadmin(rpath, current_cvsroot->cr_str, repo,
		    tag, NULL);

		if (dp != NULL) {
			if ((p = strchr(dp, '/')) != NULL)
				*p = '\0';

			entry = xmalloc(CVS_ENT_MAXLINELEN);
			cvs_ent_line_str(dp, NULL, NULL, NULL, NULL, 1, 0,
			    entry, CVS_ENT_MAXLINELEN);

			ent = cvs_ent_open(rpath);
			cvs_ent_add(ent, entry);
			xfree(entry);

			if (p != NULL)
				*p = '/';
		}

		if (cvs_server_active == 1 && strcmp(rpath, ".")) {
			if (tag != NULL) {
				(void)xsnprintf(sticky, sizeof(sticky),
				    "T%s", tag);
				cvs_server_set_sticky(rpath, sticky);
			}
		}
	}

	xfree(dir);
}

/*
 * Split the contents of a file into a list of lines.
 */
struct cvs_lines *
cvs_splitlines(u_char *data, size_t len)
{
	u_char *p, *c;
	size_t i, tlen;
	struct cvs_lines *lines;
	struct cvs_line *lp;

	lines = xcalloc(1, sizeof(*lines));
	TAILQ_INIT(&(lines->l_lines));

	lp = xcalloc(1, sizeof(*lp));
	TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);

	p = c = data;
	for (i = 0; i < len; i++) {
		if (*p == '\n' || (i == len - 1)) {
			tlen = p - c + 1;
			lp = xcalloc(1, sizeof(*lp));
			lp->l_line = c;
			lp->l_len = tlen;
			lp->l_lineno = ++(lines->l_nblines);
			TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
			c = p + 1;
		}
		p++;
	}

	return (lines);
}

void
cvs_freelines(struct cvs_lines *lines)
{
	struct cvs_line *lp;

	while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
		TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
		if (lp->l_needsfree == 1)
			xfree(lp->l_line);
		xfree(lp);
	}

	xfree(lines);
}

/*
 * cvs_strsplit()
 *
 * Split a string <str> of <sep>-separated values and allocate
 * an argument vector for the values found.
 */
struct cvs_argvector *
cvs_strsplit(char *str, const char *sep)
{
	struct cvs_argvector *av;
	size_t i = 0;
	char *cp, *p;

	cp = xstrdup(str);
	av = xmalloc(sizeof(*av));
	av->str = cp;
	av->argv = xmalloc(sizeof(*(av->argv)));

	while ((p = strsep(&cp, sep)) != NULL) {
		av->argv[i++] = p;
		av->argv = xrealloc(av->argv,
		    i + 1, sizeof(*(av->argv)));
	}
	av->argv[i] = NULL;

	return (av);
}

/*
 * cvs_argv_destroy()
 *
 * Free an argument vector previously allocated by cvs_strsplit().
 */
void
cvs_argv_destroy(struct cvs_argvector *av)
{
	xfree(av->str);
	xfree(av->argv);
	xfree(av);
}

u_int
cvs_revision_select(RCSFILE *file, char *range)
{
	int i;
	u_int nrev;
	char *lstr, *rstr;
	struct rcs_delta *rdp;
	struct cvs_argvector *revargv, *revrange;
	RCSNUM *lnum, *rnum;

	nrev = 0;
	lnum = rnum = NULL;

	revargv = cvs_strsplit(range, ",");
	for (i = 0; revargv->argv[i] != NULL; i++) {
		revrange = cvs_strsplit(revargv->argv[i], ":");
		if (revrange->argv[0] == NULL)
			fatal("invalid revision range: %s", revargv->argv[i]);
		else if (revrange->argv[1] == NULL)
			lstr = rstr = revrange->argv[0];
		else {
			if (revrange->argv[2] != NULL)
				fatal("invalid revision range: %s",
				    revargv->argv[i]);

			lstr = revrange->argv[0];
			rstr = revrange->argv[1];

			if (strcmp(lstr, "") == 0)
				lstr = NULL;
			if (strcmp(rstr, "") == 0)
				rstr = NULL;
		}

		if (lstr == NULL)
			lstr = RCS_HEAD_INIT;

		if ((lnum = rcs_translate_tag(lstr, file)) == NULL)
			fatal("cvs_revision_select: could not translate tag `%s'", lstr);

		if (rstr != NULL) {
			if ((rnum = rcs_translate_tag(rstr, file)) == NULL)
				fatal("cvs_revision_select: could not translate tag `%s'", rstr);
		} else {
			rnum = rcsnum_alloc();
			rcsnum_cpy(file->rf_head, rnum, 0);
		}

		cvs_argv_destroy(revrange);

		TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) {
			if (rcsnum_cmp(rdp->rd_num, lnum, 0) <= 0 &&
			    rcsnum_cmp(rdp->rd_num, rnum, 0) >= 0 &&
			    !(rdp->rd_flags & RCS_RD_SELECT)) {
				rdp->rd_flags |= RCS_RD_SELECT;
				nrev++;
			}
		}

		rcsnum_free(lnum);
		rcsnum_free(rnum);
	}

	cvs_argv_destroy(revargv);

	return (nrev);
}

int
cvs_yesno(void)
{
	int c, ret;

	ret = 0;

	fflush(stderr);
	fflush(stdout);

	if ((c = getchar()) != 'y' && c != 'Y')
		ret = -1;
	else
		while (c != EOF && c != '\n')
			c = getchar();

	return (ret);
}

/*
 * cvs_exec()
 *
 * Execute <prog> and send <in> to the STDIN if not NULL.
 * If <needwait> == 1, return the result of <prog>, 
 * else, 0 or -1 if an error occur.
 */
int
cvs_exec(char *prog, const char *in, int needwait)
{
	pid_t pid;
	int fds[2], size, st;
	char *argp[4] = { "sh", "-c", prog, NULL };

	if (in != NULL && pipe(fds) < 0) {
		cvs_log(LP_ERR, "cvs_exec: pipe failed");
		return (-1);
	}

	if ((pid = fork()) == -1) {
		cvs_log(LP_ERR, "cvs_exec: fork failed");
		return (-1);
	} else if (pid == 0) {
		if (in != NULL) {
			close(fds[1]);
			dup2(fds[0], STDIN_FILENO);
		}

		setenv("CVSROOT", current_cvsroot->cr_dir, 1);
		execv(_PATH_BSHELL, argp);
		cvs_log(LP_ERR, "cvs_exec: failed to run '%s'", prog);
		_exit(127);
	}

	if (in != NULL) {
		close(fds[0]);
		size = strlen(in);
		if (atomicio(vwrite, fds[1], in, size) != size)
			cvs_log(LP_ERR, "cvs_exec: failed to write on STDIN");
		close(fds[1]);
	}

	if (needwait == 1) {
		while (waitpid(pid, &st, 0) == -1)
			;
		if (!WIFEXITED(st)) {
			errno = EINTR;
			return (-1);
		}
		return (WEXITSTATUS(st));
	}

	return (0);
}