[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.69, Fri Jan 27 12:56:28 2006 UTC (18 years, 4 months ago) by xsa
Branch: MAIN
CVS Tags: OPENBSD_3_9_BASE, OPENBSD_3_9
Changes since 1.68: +2 -5 lines

cvs_mkadmin() cannot return < 0 anymore;

/*	$OpenBSD: util.c,v 1.69 2006/01/27 12:56:28 xsa Exp $	*/
/*
 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@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 "includes.h"

#include "cvs.h"
#include "log.h"

#if !defined(RCSPROG)

/* 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_readrepo()
 *
 * Read the path stored in the `Repository' CVS file for a given directory
 * <dir>, and store that path into the buffer pointed to by <dst>, whose size
 * is <len>.
 */
int
cvs_readrepo(const char *dir, char *dst, size_t len)
{
	size_t dlen, l;
	FILE *fp;
	char repo_path[MAXPATHLEN];

	l = cvs_path_cat(dir, "CVS/Repository", repo_path, sizeof(repo_path));
	if (l >= sizeof(repo_path))
		return (-1);

	fp = fopen(repo_path, "r");
	if (fp == NULL)
		return (-1);

	if (fgets(dst, (int)len, fp) == NULL) {
		if (ferror(fp)) {
			cvs_log(LP_ERRNO, "failed to read from `%s'",
			    repo_path);
		}
		(void)fclose(fp);
		return (-1);
	}
	dlen = strlen(dst);
	if ((dlen > 0) && (dst[dlen - 1] == '\n'))
		dst[--dlen] = '\0';

	(void)fclose(fp);
	return (0);
}


/*
 * 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) {
			cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
			continue;
		}

		if ((type <= 'a') || (type >= 'z') ||
		    (cvs_modetypes[type - 'a'] == -1)) {
			cvs_log(LP_WARN,
			    "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)) {
				cvs_log(LP_WARN,
				    "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_WARN, "buffer too small for checksum");
		return (-1);
	}
	if (MD5File(file, dst) == NULL) {
		cvs_log(LP_ERRNO, "failed to generate checksum for %s", file);
		return (-1);
	}

	return (0);
}

/*
 * cvs_splitpath()
 *
 * Split a path <path> into the base portion and the filename portion.
 * The path is copied in <base> and the last delimiter is replaced by a NUL
 * byte.  The <file> pointer is set to point to the first character after
 * that delimiter.
 * Returns 0 on success, or -1 on failure.
 */
void
cvs_splitpath(const char *path, char *base, size_t blen, char **file)
{
	size_t rlen;
	char *sp;

	if ((rlen = strlcpy(base, path, blen)) >= blen)
		fatal("cvs_splitpath: path truncation");

	while ((rlen > 0) && (base[rlen - 1] == '/'))
		base[--rlen] = '\0';

	sp = strrchr(base, '/');
	if (sp == NULL) {
		rlen = strlcpy(base, "./", blen);
		if (rlen >= blen)
			fatal("cvs_splitpath: path truncation");

		rlen = strlcat(base, path, blen);
		if (rlen >= blen)
			fatal("cvs_splitpath: path truncation");

		sp = base + 1;
	}

	*sp = '\0';
	if (file != NULL)
		*file = sp + 1;
}

/*
 * 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)
{
	size_t l;
	u_int i;
	int argc, error;
	char linebuf[256], qbuf[128], *lp, *cp, *arg;

	l = strlcpy(linebuf, line, sizeof(linebuf));
	if (l >= sizeof(linebuf))
		fatal("cvs_getargv: string truncation");

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

	/* build the argument vector */
	error = 0;
	for (lp = linebuf; lp != NULL;) {
		if (*lp == '"') {
			/* double-quoted string */
			lp++;
			i = 0;
			memset(qbuf, 0, sizeof(qbuf));
			while (*lp != '"') {
				if (*lp == '\\')
					lp++;
				if (*lp == '\0') {
					cvs_log(LP_ERR, "no terminating quote");
					error++;
					break;
				}

				qbuf[i++] = *lp++;
				if (i == sizeof(qbuf)) {
					error++;
					break;
				}
			}

			arg = qbuf;
		} else {
			cp = strsep(&lp, " \t");
			if (cp == NULL)
				break;
			else if (*cp == '\0')
				continue;

			arg = cp;
		}

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

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

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

	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;
	size_t size;

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

	size = (ret + 1) * sizeof(char *);
	copy = (char **)xmalloc(size);
	memset(copy, 0, size);

	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_mkadmin()
 *
 * Create the CVS administrative files within the directory <cdir>.  If the
 * files already exist, they are kept as is.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_mkadmin(const char *dpath, const char *rootpath, const char *repopath,
    char *tag, char *date, int nb)
{
	size_t l;
	char path[MAXPATHLEN];
	FILE *fp;
	CVSENTRIES *ef;
	struct stat st;

	cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s, %d)",
	    dpath, rootpath, repopath, tag ? tag : "", date ? date : "", nb);

	l = cvs_path_cat(dpath, CVS_PATH_CVSDIR, path, sizeof(path));
	if (l >= sizeof(path))
		fatal("cvs_mkadmin: path truncation");

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

	/* just create an empty Entries file */
	ef = cvs_ent_open(dpath, O_WRONLY);
	if (ef != NULL)
		cvs_ent_close(ef);

	l = cvs_path_cat(dpath, CVS_PATH_ROOTSPEC, path, sizeof(path));
	if (l >= sizeof(path))
		fatal("cvs_mkadmin: path truncation");

	if ((stat(path, &st) == -1) && (errno == ENOENT)) {
		if ((fp = fopen(path, "w")) == NULL)
			fatal("cvs_mkadmin: fopen: `%s': %s",
			    path, strerror(errno));

		if (rootpath != NULL)
			fprintf(fp, "%s\n", rootpath);
		(void)fclose(fp);
	}

	l = cvs_path_cat(dpath, CVS_PATH_REPOSITORY, path, sizeof(path));
	if (l >= sizeof(path))
		fatal("cvs_mkadmin: path truncation");

	if ((stat(path, &st) == -1) && (errno == ENOENT)) {
		if ((fp = fopen(path, "w")) == NULL)
			fatal("cvs_mkadmin: fopen: `%s': %s",
			    path, strerror(errno));

		if (repopath != NULL)
			fprintf(fp, "%s\n", repopath);
		(void)fclose(fp);
	}

	/* create CVS/Tag file (if needed) */
	/* XXX correct? */
	if (tag != NULL || date != NULL)
		(void)cvs_write_tagfile(tag, date, nb);

	return (0);
}


/*
 * cvs_exec()
 */
int
cvs_exec(int argc, char **argv, int fds[3])
{
	int ret;
	pid_t pid;

	if ((pid = fork()) == -1) {
		cvs_log(LP_ERRNO, "failed to fork");
		return (-1);
	} else if (pid == 0) {
		execvp(argv[0], argv);
		cvs_log(LP_ERRNO, "failed to exec %s", argv[0]);
		exit(1);
	}

	if (waitpid(pid, &ret, 0) == -1)
		cvs_log(LP_ERRNO, "failed to waitpid");

	return (ret);
}

/*
 * 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)
{
	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)
{
	cvs_log(LP_TRACE, "cvs_unlink(%s)", path);

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

	if ((unlink(path) == -1) && (errno != ENOENT)) {
		cvs_log(LP_ERRNO, "cannot remove `%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 ret = -1;
	size_t len;
	DIR *dirp;
	struct dirent *ent;
	char fpath[MAXPATHLEN];

	cvs_log(LP_TRACE, "cvs_rmdir(%s)", path);

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

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

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

		len = cvs_path_cat(path, ent->d_name, fpath, sizeof(fpath));
		if (len >= sizeof(fpath))
			fatal("cvs_rmdir: path truncation");

		if (ent->d_type == DT_DIR) {
			if (cvs_rmdir(fpath) == -1)
				goto done;
		} else if ((cvs_unlink(fpath) == -1) && (errno != ENOENT))
			goto done;
	}


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

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

/*
 * Create a directory, and the parent directories if needed.
 * based upon mkpath() from mkdir.c
 */
int
cvs_create_dir(const char *path, int create_adm, char *root, char *repo)
{
	int ret;
	char *d, *s;
	struct stat sb;
	char rpath[MAXPATHLEN], entry[MAXPATHLEN];
	CVSENTRIES *entf;
	struct cvs_ent *ent;

	if ((create_adm == 1) && (root == NULL))
		fatal("cvs_create_dir failed");

	s = xstrdup(path);
	rpath[0] = '\0';
	if (repo != NULL) {
		if (strlcpy(rpath, repo, sizeof(rpath)) >= sizeof(rpath))
			fatal("cvs_create_dir: path truncation");

		if (strlcat(rpath, "/", sizeof(rpath)) >= sizeof(rpath))
			fatal("cvs_create_dir: path truncation");
	}

	ret = -1;
	entf = NULL;
	d = strtok(s, "/");
	while (d != NULL) {
		if (stat(d, &sb)) {
			/* try to create the directory */
			if ((errno != ENOENT) || (mkdir(d, 0755) &&
			    errno != EEXIST)) {
				cvs_log(LP_ERRNO, "failed to create `%s'", d);
				goto done;
			}
		} else if (!S_ISDIR(sb.st_mode)) {
			cvs_log(LP_ERR, "`%s' not a directory", d);
			goto done;
		}

		/*
		 * Create administrative files if requested.
		 */
		if (create_adm == 1) {
			if (strlcat(rpath, d, sizeof(rpath)) >= sizeof(rpath))
				fatal("cvs_create_dir: path truncation");

			if (strlcat(rpath, "/", sizeof(rpath)) >= sizeof(rpath))
				fatal("cvs_create_dir: path truncation");

			cvs_mkadmin(d, root, rpath, NULL, NULL, 0);
		}

		/*
		 * Add it to the parent directory entry file.
		 * (if any).
		 */
		entf = cvs_ent_open(".", O_RDWR);
		if (entf != NULL && strcmp(d, ".")) {
			if (strlcpy(entry, "D/", sizeof(entry)) >=
			    sizeof(entry) ||
			    strlcat(entry, d, sizeof(entry)) >= sizeof(entry) ||
			    strlcat(entry, "////", sizeof(entry)) >=
			    sizeof(entry))
				fatal("cvs_create_dir: overflow in entry buf");

			if ((ent = cvs_ent_parse(entry)) == NULL) {
				cvs_log(LP_ERR, "failed to parse entry");
				goto done;
			}

			cvs_ent_remove(entf, d, 0);

			if (cvs_ent_add(entf, ent) < 0) {
				cvs_log(LP_ERR, "failed to add entry");
				goto done;
			}
		}

		if (entf != NULL) {
			cvs_ent_close(entf);
			entf = NULL;
		}

		/* All went ok, switch to the newly created directory. */
		cvs_chdir(d, 0);

		d = strtok(NULL, "/");
	}

	ret = 0;
done:
	if (entf != NULL)
		cvs_ent_close(entf);
	xfree(s);
	return (ret);
}

/*
 * cvs_path_cat()
 *
 * Concatenate the two paths <base> and <end> and store the generated path
 * into the buffer <dst>, which can accept up to <dlen> bytes, including the
 * NUL byte.  The result is guaranteed to be NUL-terminated.
 * Returns the number of bytes necessary to store the full resulting path,
 * not including the NUL byte (a value equal to or larger than <dlen>
 * indicates truncation).
 */
size_t
cvs_path_cat(const char *base, const char *end, char *dst, size_t dlen)
{
	size_t len;

	len = strlcpy(dst, base, dlen);
	if (len >= dlen - 1) {
		errno = ENAMETOOLONG;
		cvs_log(LP_ERRNO, "%s", dst);
	} else {
		dst[len] = '/';
		dst[len + 1] = '\0';
		len = strlcat(dst, end, dlen);
		if (len >= dlen) {
			errno = ENAMETOOLONG;
			cvs_log(LP_ERRNO, "%s", dst);
		}
	}

	return (len);
}

/*
 * cvs_rcs_getpath()
 *
 * Get the RCS path of the file <file> and store it in <buf>, which is
 * of size <len>. For portability, it is recommended that <buf> always be
 * at least MAXPATHLEN bytes long.
 * Returns a pointer to the start of the path on success, or NULL on failure.
 */
char *
cvs_rcs_getpath(CVSFILE *file, char *buf, size_t len)
{
	char *repo;
	struct cvsroot *root;

	root = CVS_DIR_ROOT(file);
	repo = CVS_DIR_REPO(file);

	if (strlcpy(buf, root->cr_dir, len) >= len ||
	    strlcat(buf, "/", len) >= len ||
	    strlcat(buf, repo, len) >= len ||
	    strlcat(buf, "/", len) >= len ||
	    strlcat(buf, file->cf_name, len) >= len ||
	    strlcat(buf, RCS_FILE_EXT, len) >= len)
		fatal("cvs_rcs_getpath: path truncation");

	return (buf);
}

/*
 * cvs_write_tagfile()
 *
 * Write the CVS/Tag file for current directory.
 */
void
cvs_write_tagfile(char *tag, char *date, int nb)
{
	FILE *fp;
	char tagpath[MAXPATHLEN];

	if (cvs_noexec == 1)
		return;

	if (strlcpy(tagpath, CVS_PATH_TAG, sizeof(tagpath)) >= sizeof(tagpath))
		return;

	if ((tag != NULL) || (date != NULL)) {
		fp = fopen(tagpath, "w+");
		if (fp == NULL) {
			if (errno != ENOENT)
				cvs_log(LP_NOTICE,
				    "failed to open `%s' : %s", tagpath,
				    strerror(errno));
			return;
		}
		if (tag != NULL) {
			if (nb != 0)
				fprintf(fp, "N%s\n", tag);
			else
				fprintf(fp, "T%s\n", tag);
		} else {
			fprintf(fp, "D%s\n", date);
		}
		(void)fclose(fp);
	} else {
		cvs_unlink(tagpath);
		return;
	}
}

/*
 * cvs_parse_tagfile()
 *
 * Parse the CVS/Tag file for current directory.
 *
 * If it contains a branch tag, sets <tagp>.
 * If it contains a date, sets <datep>.
 * If it contains a non-branch tag, sets <nbp>.
 *
 * Returns nothing but an error message, and sets <tagp>, <datep> to NULL
 * and <nbp> to 0.
 */
void
cvs_parse_tagfile(char **tagp, char **datep, int *nbp)
{
	FILE *fp;
	int linenum;
	size_t len;
	char linebuf[128], tagpath[MAXPATHLEN];

	if (tagp != NULL)
		*tagp = (char *)NULL;

	if (datep != NULL)
		*datep = (char *)NULL;

	if (nbp != NULL)
		*nbp = 0;

	if (strlcpy(tagpath, CVS_PATH_TAG, sizeof(tagpath)) >= sizeof(tagpath))
		return;

	fp = fopen(tagpath, "r");
	if (fp == NULL) {
		if (errno != ENOENT)
			cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath,
			    strerror(errno));
		return;
	}

	linenum = 0;

	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
		linenum++;
		if ((len = strlen(linebuf)) == 0)
			continue;
		if (linebuf[len -1] != '\n') {
			cvs_log(LP_WARN, "line too long in `%s:%d'", tagpath,
			    linenum);
			break;
		}
		linebuf[--len] = '\0';

		switch (*linebuf) {
		case 'T':
			if (tagp != NULL)
				*tagp = xstrdup(linebuf);
			break;
		case 'D':
			if (datep != NULL)
				*datep = xstrdup(linebuf);
			break;
		case 'N':
			if (tagp != NULL)
				*tagp = xstrdup(linebuf);
			if (nbp != NULL)
				*nbp = 1;
			break;
		default:
			break;
		}
	}
	if (ferror(fp))
		cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath);

	(void)fclose(fp);
}

#endif	/* !RCSPROG */

/*
 * Split the contents of a file into a list of lines.
 */
struct cvs_lines *
cvs_splitlines(const char *fcont)
{
	char *dcp;
	struct cvs_lines *lines;
	struct cvs_line *lp;

	lines = (struct cvs_lines *)xmalloc(sizeof(*lines));
	TAILQ_INIT(&(lines->l_lines));
	lines->l_nblines = 0;
	lines->l_data = xstrdup(fcont);

	lp = (struct cvs_line *)xmalloc(sizeof(*lp));
	lp->l_line = NULL;
	lp->l_lineno = 0;
	TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);

	for (dcp = lines->l_data; *dcp != '\0';) {
		lp = (struct cvs_line *)xmalloc(sizeof(*lp));
		lp->l_line = dcp;
		lp->l_lineno = ++(lines->l_nblines);
		TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);

		dcp = strchr(dcp, '\n');
		if (dcp == NULL)
			break;
		*(dcp++) = '\0';
	}

	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);
		xfree(lp);
	}

	xfree(lines->l_data);
	xfree(lines);
}

BUF *
cvs_patchfile(const char *data, const char *patch,
    int (*p)(struct cvs_lines *, struct cvs_lines *))
{
	struct cvs_lines *dlines, *plines;
	struct cvs_line *lp;
	size_t len;
	int lineno;
	BUF *res;

	len = strlen(data);

	if ((dlines = cvs_splitlines(data)) == NULL)
		return (NULL);

	if ((plines = cvs_splitlines(patch)) == NULL)
		return (NULL);

	if (p(dlines, plines) < 0) {
		cvs_freelines(dlines);
		cvs_freelines(plines);
		return (NULL);
	}

	lineno = 0;
	res = cvs_buf_alloc(len, BUF_AUTOEXT);
	TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
		if (lineno != 0)
			cvs_buf_fappend(res, "%s\n", lp->l_line);
		lineno++;
	}

	cvs_freelines(dlines);
	cvs_freelines(plines);
	return (res);
}

/*
 * a hack to mimic and thus match gnu cvs behaviour.
 */
time_t
cvs_hack_time(time_t oldtime, int togmt)
{
	int l;
	struct tm *t;
	char tbuf[32];

	if (togmt == 1) {
		t = gmtime(&oldtime);
		if (t == NULL)
			return (0);

		return (mktime(t));
	}

	t = localtime(&oldtime);

	l = snprintf(tbuf, sizeof(tbuf), "%d/%d/%d GMT %d:%d:%d",
	    t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour,
	    t->tm_min, t->tm_sec);
	if (l == -1 || l >= (int)sizeof(tbuf))
		return (0);

	return (cvs_date_parse(tbuf));
}