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

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

Revision 1.5, Thu Jul 7 14:27:57 2005 UTC (18 years, 11 months ago) by joris
Branch: MAIN
Changes since 1.4: +3 -3 lines



remove trailing whitespaces

from deraadt@

/*	$OpenBSD: repo.c,v 1.5 2005/07/07 14:27:57 joris Exp $	*/
/*
 * Copyright (c) 2005 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 <sys/param.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <sys/stat.h>

#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "log.h"
#include "repo.h"



static CVSRPENT*  cvs_repo_loadrec (CVSREPO *, const char *);


/*
 * cvs_repo_load()
 *
 * Load the information for a specific CVS repository whose base directory
 * is specified in <base>.
 */

CVSREPO*
cvs_repo_load(const char *base, int flags)
{
	struct stat st;
	CVSREPO *repo;

	cvs_log(LP_DEBUG, "loading repository %s", base);

	if (stat(base, &st) == -1) {
		cvs_log(LP_ERRNO, "failed to stat %s", base);
		return (NULL);
	}

	if (!S_ISDIR(st.st_mode)) {
		cvs_log(LP_ERR, "%s: repository path is not a directory", base);
		return (NULL);
	}

	repo = (struct cvs_repo *)malloc(sizeof(*repo));
	if (repo == NULL) {
		cvs_log(LP_ERRNO, "failed to allocate repository data");
		return (NULL);
	}
	memset(repo, 0, sizeof(*repo));

	TAILQ_INIT(&(repo->cr_modules));

	repo->cr_path = strdup(base);
	if (repo->cr_path == NULL) {
		cvs_log(LP_ERRNO, "failed to copy repository path");
		free(repo);
		return (NULL);
	}

	repo->cr_tree = cvs_repo_loadrec(repo, repo->cr_path);
	if (repo->cr_tree == NULL) {
		cvs_repo_free(repo);
		return (NULL);
	}

	return (repo);
}


/*
 * cvs_repo_free()
 *
 * Free the data associated to a repository.
 */

void
cvs_repo_free(CVSREPO *repo)
{
	CVSMODULE *mod;

	if (repo != NULL) {
		if (repo->cr_path != NULL)
			free(repo->cr_path);

		while ((mod = TAILQ_FIRST(&(repo->cr_modules))) != NULL) {
			TAILQ_REMOVE(&(repo->cr_modules), mod, cm_link);
			cvs_repo_modfree(mod);
		}

		if (repo->cr_tree != NULL)
			cvs_repo_entfree(repo->cr_tree);

		free(repo);
	}
}


/*
 * cvs_repo_lockdir()
 *
 * Obtain a lock on the directory <dir> which is relative to the root of
 * the repository <repo>.  The owner of the lock becomes <pid>.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_repo_lockdir(CVSREPO *repo, const char *dir, int type, pid_t owner)
{
	CVSRPENT *ent;

	if ((ent = cvs_repo_find(repo, dir)) == NULL) {
		return (-1);
	}

	return cvs_repo_lockent(ent, type, owner);
}


/*
 * cvs_repo_unlockdir()
 *
 * Attempt to unlock the directory <dir> in the repository <repo>.  The <owner>
 * argument is used to make sure that the caller really owns the lock it is
 * trying to release.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_repo_unlockdir(CVSREPO *repo, const char *dir, pid_t owner)
{
	CVSRPENT *ent;

	if ((ent = cvs_repo_find(repo, dir)) == NULL) {
		return (-1);
	}

	return cvs_repo_unlockent(ent, owner);
}


/*
 * cvs_repo_lockent()
 *
 * Obtain a lock on the entry <ent>.  The owner of the lock becomes <pid>.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_repo_lockent(CVSRPENT *ent, int type, pid_t owner)
{
	struct cvs_lock *lk;
	struct cvs_lklist *list;

	if ((type != CVS_LOCK_READ) && (type != CVS_LOCK_WRITE)) {
		cvs_log(LP_ERR, "invalid lock type (%d) requested");
		return (-1);
	}

	lk = (struct cvs_lock *)malloc(sizeof(*lk));
	if (lk == NULL) {
		cvs_log(LP_ERRNO, "failed to allocate repository lock");
		return (-1);
	}
	lk->lk_owner = owner;
	lk->lk_type = type;
	lk->lk_ent = ent;

	if ((ent->cr_wlock != NULL) && (ent->cr_wlock->lk_owner != 0)) {
		/*
		 * Another process has already locked the entry with a write
		 * lock, so regardless of the type of lock we are requesting,
		 * we'll have to wait in the pending requests queue.
		 */
		if (ent->cr_wlock->lk_owner == owner) {
			cvs_log(LP_WARN, "double-lock attempt");
			free(lk);
		} else
			TAILQ_INSERT_TAIL(&(ent->cr_lkreq), lk, lk_link);
	} else {
		if (type == CVS_LOCK_READ) {
			/*
			 * If there are any pending write lock requests,
			 * add the read lock request at the tail of the queue
			 * instead of assigning it right away.  Otherwise,
			 * we could end up with a write lock request never
			 * being obtained if other processes make overlapping
			 * read lock requests.
			 */
			if (TAILQ_EMPTY(&(ent->cr_lkreq)))
				list = &(ent->cr_rlocks);
			else
				list = &(ent->cr_lkreq);
			TAILQ_INSERT_TAIL(list, lk, lk_link);
		} else if (type == CVS_LOCK_WRITE) {
			if (TAILQ_EMPTY(&(ent->cr_rlocks)))
				ent->cr_wlock = lk;
			else
				TAILQ_INSERT_TAIL(&(ent->cr_lkreq), lk, lk_link);
		}
	}

	return (0);
}


/*
 * cvs_repo_unlockent()
 *
 * Attempt to unlock the entry <ent>.  The <owner> argument is used to make
 * sure that the caller really owns the lock it is trying to release.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_repo_unlockent(CVSRPENT *ent, pid_t owner)
{
	struct cvs_lock *lk;

	if ((ent->cr_wlock != NULL) && (ent->cr_wlock->lk_owner != 0)) {
		if (ent->cr_wlock->lk_owner != owner) {
			cvs_log(LP_ERR, "child %d attempted to unlock write "
			    "lock owned by %d", ent->cr_wlock->lk_owner);
			return (-1);
		}

		free(ent->cr_wlock);
		ent->cr_wlock = NULL;
	} else {
		TAILQ_FOREACH(lk, &(ent->cr_rlocks), lk_link) {
			if (lk->lk_owner == owner) {
				TAILQ_REMOVE(&(ent->cr_rlocks), lk, lk_link);
				free(lk);
				break;
			}
		}
	}

#ifdef notyet
	/* assign lock to any process with a pending request */
	while ((lk = TAILQ_FIRST(&(ent->cr_lkreq))) != NULL) {
		TAILQ_REMOVE(&(ent->cr_lkreq), lk, lk_link);
		/* XXX send message to process */
		child = cvsd_child_find(lk->lk_owner);
		if (child == NULL)
			continue;

		break;
	}
#endif

	return (0);
}


/*
 * cvs_repo_alias()
 *
 * Add a new module entry with name <alias> in the repository <repo>, which
 * points to the path <path> within the repository.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_repo_alias(CVSREPO *repo, const char *path, const char *alias)
{
	CVSMODULE *mod;

	mod = (CVSMODULE *)malloc(sizeof(*mod));
	if (mod == NULL) {
		cvs_log(LP_ERRNO, "failed to allocate module alias");
		return (-1);
	}
	memset(mod, 0, sizeof(*mod));

	mod->cm_name = strdup(alias);
	if (mod->cm_name == NULL) {
		cvs_log(LP_ERRNO, "failed to allocate module alias");
		free(mod);
		return (-1);
	}
	mod->cm_flags |= CVS_MODULE_ISALIAS;

	mod->cm_path = strdup(path);
	if (mod->cm_path == NULL) {
		cvs_log(LP_ERRNO, "failed to allocate module alias");
		free(mod->cm_name);
		free(mod);
		return (-1);
	}

	TAILQ_INSERT_TAIL(&(repo->cr_modules), mod, cm_link);

	return (0);
}


/*
 * cvs_repo_unalias()
 *
 * Remove the module alias <alias> from the repository <repo>.
 * Returns 0 on success, or -1 on failure.
 */
int
cvs_repo_unalias(CVSREPO *repo, const char *alias)
{
	CVSMODULE *mod;

	TAILQ_FOREACH(mod, &(repo->cr_modules), cm_link) {
		if (strcmp(mod->cm_name, alias) == 0) {
			if (!(mod->cm_flags & CVS_MODULE_ISALIAS)) {
				cvs_log(LP_ERR,
				    "attempt to remove non-aliased module `%s'",
				    mod->cm_name);
				return (-1);
			}

			break;
		}
	}
	if (mod == NULL)
		return (-1);

	TAILQ_REMOVE(&(repo->cr_modules), mod, cm_link);
	return (0);
}


/*
 * cvs_repo_find()
 *
 * Find the pointer to a CVS file entry within the file hierarchy <hier>.
 * The file's pathname <path> must be relative to the base of <hier>.
 * Returns the entry on success, or NULL on failure.
 */
CVSRPENT*
cvs_repo_find(CVSREPO *repo, const char *path)
{
	size_t len;
	char *pp, *sp, pbuf[MAXPATHLEN];
	CVSRPENT *sf, *cf;

	if ((len = strlcpy(pbuf, path, sizeof(pbuf))) >= sizeof(pbuf)) {
		errno = ENAMETOOLONG;
		cvs_log(LP_ERRNO, "%s", path);
		return (NULL);
	}

	/* remove any trailing slashes */
	while ((len > 0) && (pbuf[len - 1] == '/'))
		pbuf[--len] = '\0';

	cf = repo->cr_tree;
	pp = pbuf;
	do {
		if (cf->cr_type != CVS_RPENT_DIR) {
			errno = ENOTDIR;
			cvs_log(LP_ERRNO, "%s", path);
			return (NULL);
		}
		sp = strchr(pp, '/');
		if (sp != NULL)
			*(sp++) = '\0';

		/* special case */
		if (*pp == '.') {
			if ((*(pp + 1) == '.') && (*(pp + 2) == '\0')) {
				/* request to go back to parent */
				if (cf->cr_parent == NULL) {
					cvs_log(LP_NOTICE,
					    "path %s goes back too far", path);
					return (NULL);
				}
				cf = cf->cr_parent;
				continue;
			} else if (*(pp + 1) == '\0')
				continue;
		}

		TAILQ_FOREACH(sf, &(cf->cr_files), cr_link) {
			if (strcmp(pp, sf->cr_name) == 0)
				break;
		}
		if (sf == NULL)
			return (NULL);

		cf = sf;
		pp = sp;
	} while (sp != NULL);

	return (cf);
}


#if 0
/*
 * cvs_repo_getpath()
 *
 * Get the full 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_repo_getpath(CVSRPENT *file, char *buf, size_t len)
{
	u_int i;
	char *fp, *namevec[CVS_FILE_MAXDEPTH];
	CVSRPENT *top;

	buf[0] = '\0';
	i = CVS_FILE_MAXDEPTH;
	memset(namevec, 0, sizeof(namevec));

	/* find the top node */
	for (top = file; (top != NULL) && (i > 0); top = top->cr_parent) {
		fp = top->cr_name;

		/* skip self-references */
		if ((fp[0] == '.') && (fp[1] == '\0'))
			continue;
		namevec[--i] = fp;
	}

	if (i == 0)
		return (NULL);
	else if (i == CVS_FILE_MAXDEPTH) {
		strlcpy(buf, ".", len);
		return (buf);
	}

	while (i < CVS_FILE_MAXDEPTH - 1) {
		strlcat(buf, namevec[i++], len);
		strlcat(buf, "/", len);
	}
	strlcat(buf, namevec[i], len);

	return (buf);
}
#endif


/*
 * cvs_repo_loadrec()
 *
 * Recursively load the repository structure
 */
static CVSRPENT*
cvs_repo_loadrec(CVSREPO *repo, const char *path)
{
	int ret, fd, l;
	long base;
	u_char *dp, *ep;
	mode_t fmode;
	char fbuf[2048], pbuf[MAXPATHLEN];
	struct dirent *ent;
	CVSRPENT *cfp, *cr_ent;
	struct stat st;

	cvs_log(LP_NOTICE, "loading %s", path);
	if (stat(path, &st) == -1) {
		cvs_log(LP_ERRNO, "failed to stat %s", path);
		return (NULL);
	}

	cfp = (CVSRPENT *)malloc(sizeof(*cfp));
	if (cfp == NULL) {
		cvs_log(LP_ERRNO, "failed to allocate repository entry");
		return (NULL);
	}
	memset(cfp, 0, sizeof(*cfp));
	TAILQ_INIT(&(cfp->cr_rlocks));
	TAILQ_INIT(&(cfp->cr_lkreq));

	cfp->cr_name = strdup(basename(path));
	if (cfp->cr_name == NULL) {
		cvs_log(LP_ERRNO, "failed to copy entry name");
		free(cfp);
		return (NULL);
	}

	if (repo->cr_flags & CVS_REPO_CHKPERM) {
		if (S_ISDIR(st.st_mode))
			fmode = CVSD_DPERM;
		else
			fmode = CVSD_FPERM;
		/* perform permission checks on the file */
		if (st.st_uid != cvsd_uid) {
			cvs_log(LP_WARN, "owner of `%s' is not %s",
			    path, CVSD_USER);
		}

		if (st.st_gid != cvsd_gid) {
			cvs_log(LP_WARN, "group of `%s' is not %s",
			    path, CVSD_GROUP);
		}

		if (st.st_mode & S_IWGRP) {
			cvs_log(LP_WARN, "file `%s' is group-writable",
			    path, fmode);
		}

		if (st.st_mode & S_IWOTH) {
			cvs_log(LP_WARN, "file `%s' is world-writable",
			    path, fmode);
		}
	}

	if (S_ISREG(st.st_mode))
		cfp->cr_type = CVS_RPENT_RCSFILE;
	else if (S_ISDIR(st.st_mode)) {
		cfp->cr_type = CVS_RPENT_DIR;

		TAILQ_INIT(&(cfp->cr_files));

		if ((fd = open(path, O_RDONLY)) == -1) {
			cvs_log(LP_ERRNO, "failed to open `%s'", path);
			cvs_repo_entfree(cfp);
			return (NULL);
		}

		do {
			ret = getdirentries(fd, fbuf, sizeof(fbuf), &base);
			if (ret == -1) {
				cvs_log(LP_ERRNO,
				    "failed to get directory entries");
				cvs_repo_entfree(cfp);
				(void)close(fd);
				return (NULL);
			}

			dp = fbuf;
			ep = fbuf + (size_t)ret;
			while (dp < ep) {
				ent = (struct dirent *)dp;
				dp += ent->d_reclen;
				if (ent->d_fileno == 0)
					continue;

				if (((ent->d_namlen == 1) &&
				    (ent->d_name[0] == '.')) ||
				    ((ent->d_namlen == 2) &&
				    (ent->d_name[0] == '.') &&
				    (ent->d_name[1] == '.')))
					continue;

				l = snprintf(pbuf, sizeof(pbuf), "%s/%s", path,
				    ent->d_name);
				if (l == -1 || l >= (int)sizeof(pbuf)) {
					errno = ENAMETOOLONG;
					cvs_log(LP_ERRNO, "%s", pbuf);

					cvs_repo_entree(cfp);
					(void)close(fd);
					return (NULL);
				}

				if ((ent->d_type != DT_DIR) &&
				    (ent->d_type != DT_REG)) {
					cvs_log(LP_NOTICE, "skipping non-"
					    "regular file `%s'", pbuf);
					continue;
				}

				cr_ent = cvs_repo_loadrec(repo, pbuf);
				if (cr_ent == NULL) {
					cvs_repo_entfree(cfp);
					(void)close(fd);
					return (NULL);
				}

				cr_ent->cr_parent = cfp;
				TAILQ_INSERT_TAIL(&(cfp->cr_files), cr_ent, cr_link);
			}
		} while (ret > 0);

		(void)close(fd);
	}

	return (cfp);
}


/*
 * cvs_repo_entfree()
 *
 * Free a repository entry structure and all underlying data.  In the case of
 * directories, any child entries are also freed recursively.
 */
void
cvs_repo_entfree(CVSRPENT *ent)
{
	CVSRPENT *ch_ent;

	if (ent->cr_type == CVS_RPENT_DIR) {
		while ((ch_ent = TAILQ_FIRST(&(ent->cr_files))) != NULL) {
			TAILQ_REMOVE(&(ent->cr_files), ch_ent, cr_link);
			cvs_repo_entfree(ch_ent);
		}

	}

	if (ent->cr_name != NULL)
		free(ent->cr_name);
	free(ent);
}


/*
 * cvs_repo_modfree()
 *
 * Free a CVS module structure.
 */
void
cvs_repo_modfree(CVSMODULE *mod)
{
	if (mod->cm_name != NULL)
		free(mod->cm_name);
	if (mod->cm_path != NULL)
		free(mod->cm_path);
	free(mod);
}