version 1.137, 2006/04/14 02:45:35 |
version 1.138, 2006/05/27 03:30:30 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
/* |
/* |
|
* Copyright (c) 2006 Joris Vink <joris@openbsd.org> |
* Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> |
* Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> |
* All rights reserved. |
* All rights reserved. |
* |
* |
|
|
|
|
#define CVS_CHAR_ISMETA(c) ((c == '*') || (c == '?') || (c == '[')) |
#define CVS_CHAR_ISMETA(c) ((c == '*') || (c == '?') || (c == '[')) |
|
|
/* ignore pattern */ |
|
struct cvs_ignpat { |
|
char ip_pat[MAXNAMLEN]; |
|
int ip_flags; |
|
TAILQ_ENTRY(cvs_ignpat) ip_list; |
|
}; |
|
|
|
/* |
/* |
* Standard patterns to ignore. |
* Standard patterns to ignore. |
*/ |
*/ |
|
|
".", |
".", |
"..", |
"..", |
"*.o", |
"*.o", |
"*.so", |
|
"*.a", |
"*.a", |
"*.bak", |
"*.bak", |
"*.orig", |
"*.orig", |
|
|
"*~", |
"*~", |
"_$*", |
"_$*", |
"*$", |
"*$", |
#ifdef OLD_SMELLY_CRUFT |
|
"RCSLOG", |
|
"tags", |
|
"TAGS", |
|
"RCS", |
|
"SCCS", |
|
"cvslog.*", /* to ignore CVS_CLIENT_LOG output */ |
|
"#*", |
|
",*", |
|
#endif |
|
}; |
}; |
|
|
/* |
struct ignore_head cvs_ign_pats; |
* Entries in the CVS/Entries file with a revision of '0' have only been |
struct ignore_head dir_ign_pats; |
* added. Compare against this revision to see if this is the case |
|
*/ |
|
static RCSNUM *cvs_addedrev; |
|
|
|
TAILQ_HEAD(, cvs_ignpat) cvs_ign_pats; |
static int cvs_file_cmpname(const char *, const char *); |
|
|
static int cvs_file_getdir(CVSFILE *, int, int (*)(CVSFILE *, void *), |
void |
void *, int); |
|
|
|
static int cvs_load_dirinfo(CVSFILE *, int); |
|
static int cvs_file_sort(struct cvs_flist *, u_int); |
|
static int cvs_file_cmp(const void *, const void *); |
|
static int cvs_file_cmpname(const char *, const char *); |
|
static CVSFILE *cvs_file_alloc(const char *, u_int); |
|
static CVSFILE *cvs_file_lget(const char *, int, CVSFILE *, CVSENTRIES *, |
|
struct cvs_ent *); |
|
|
|
/* |
|
* cvs_file_init() |
|
* |
|
*/ |
|
int |
|
cvs_file_init(void) |
cvs_file_init(void) |
{ |
{ |
int i, l; |
int i, l; |
size_t len; |
|
char path[MAXPATHLEN], buf[MAXNAMLEN]; |
|
FILE *ifp; |
FILE *ifp; |
|
size_t len; |
|
char *path, *buf; |
|
|
|
path = xmalloc(MAXPATHLEN); |
|
buf = xmalloc(MAXNAMLEN); |
|
|
TAILQ_INIT(&cvs_ign_pats); |
TAILQ_INIT(&cvs_ign_pats); |
|
TAILQ_INIT(&dir_ign_pats); |
|
|
if ((cvs_addedrev = rcsnum_parse("0")) == NULL) |
|
return (-1); |
|
|
|
/* standard patterns to ignore */ |
/* standard patterns to ignore */ |
for (i = 0; i < (int)(sizeof(cvs_ign_std)/sizeof(char *)); i++) |
for (i = 0; i < (int)(sizeof(cvs_ign_std)/sizeof(char *)); i++) |
cvs_file_ignore(cvs_ign_std[i]); |
cvs_file_ignore(cvs_ign_std[i], &cvs_ign_pats); |
|
|
/* read the cvsignore file in the user's home directory, if any */ |
/* read the cvsignore file in the user's home directory, if any */ |
l = snprintf(path, sizeof(path), "%s/.cvsignore", cvs_homedir); |
l = snprintf(path, MAXPATHLEN, "%s/.cvsignore", cvs_homedir); |
if (l == -1 || l >= (int)sizeof(path)) { |
if (l == -1 || l >= MAXPATHLEN) |
errno = ENAMETOOLONG; |
fatal("overflow in cvs_file_init"); |
cvs_log(LP_ERRNO, "%s", path); |
|
return (-1); |
|
} |
|
|
|
ifp = fopen(path, "r"); |
ifp = fopen(path, "r"); |
if (ifp == NULL) { |
if (ifp == NULL) { |
|
|
cvs_log(LP_ERRNO, |
cvs_log(LP_ERRNO, |
"failed to open user's cvsignore file `%s'", path); |
"failed to open user's cvsignore file `%s'", path); |
} else { |
} else { |
while (fgets(buf, (int)sizeof(buf), ifp) != NULL) { |
while (fgets(buf, MAXNAMLEN, ifp) != NULL) { |
len = strlen(buf); |
len = strlen(buf); |
if (len == 0) |
if (len == 0) |
continue; |
continue; |
if (buf[len - 1] != '\n') { |
|
|
if (buf[len - 1] != '\n') |
cvs_log(LP_ERR, "line too long in `%s'", path); |
cvs_log(LP_ERR, "line too long in `%s'", path); |
} |
|
buf[--len] = '\0'; |
buf[--len] = '\0'; |
cvs_file_ignore(buf); |
cvs_file_ignore(buf, &cvs_ign_pats); |
} |
} |
|
|
(void)fclose(ifp); |
(void)fclose(ifp); |
} |
} |
|
|
return (0); |
xfree(path); |
|
xfree(buf); |
} |
} |
|
|
/* |
void |
* cvs_file_ignore() |
cvs_file_ignore(const char *pat, struct ignore_head *list) |
* |
|
* Add the pattern <pat> to the list of patterns for files to ignore. |
|
* Returns 0 on success, or -1 on failure. |
|
*/ |
|
int |
|
cvs_file_ignore(const char *pat) |
|
{ |
{ |
char *cp; |
char *cp; |
|
size_t len; |
struct cvs_ignpat *ip; |
struct cvs_ignpat *ip; |
|
|
ip = xmalloc(sizeof(*ip)); |
ip = xmalloc(sizeof(*ip)); |
strlcpy(ip->ip_pat, pat, sizeof(ip->ip_pat)); |
len = strlcpy(ip->ip_pat, pat, sizeof(ip->ip_pat)); |
|
if (len >= sizeof(ip->ip_pat)) |
|
fatal("cvs_file_ignore: truncation of pattern '%s'", pat); |
|
|
/* check if we will need globbing for that pattern */ |
/* check if we will need globbing for that pattern */ |
ip->ip_flags = CVS_IGN_STATIC; |
ip->ip_flags = CVS_IGN_STATIC; |
|
|
} |
} |
} |
} |
|
|
TAILQ_INSERT_TAIL(&cvs_ign_pats, ip, ip_list); |
TAILQ_INSERT_TAIL(list, ip, ip_list); |
|
|
return (0); |
|
} |
} |
|
|
/* |
|
* cvs_file_chkign() |
|
* |
|
* Returns 1 if the filename <file> is matched by one of the ignore |
|
* patterns, or 0 otherwise. |
|
*/ |
|
int |
int |
cvs_file_chkign(const char *file) |
cvs_file_chkign(const char *file) |
{ |
{ |
|
|
return (1); |
return (1); |
} |
} |
|
|
|
TAILQ_FOREACH(ip, &dir_ign_pats, ip_list) { |
|
if (ip->ip_flags & CVS_IGN_STATIC) { |
|
if (cvs_file_cmpname(file, ip->ip_pat) == 0) |
|
return (1); |
|
} else if (fnmatch(ip->ip_pat, file, flags) == 0) |
|
return (1); |
|
} |
|
|
return (0); |
return (0); |
} |
} |
|
|
/* |
void |
* cvs_file_create() |
cvs_file_run(int argc, char **argv, struct cvs_recursion *cr) |
* |
|
* Create a new file whose path is specified in <path> and of type <type>. |
|
* If the type is DT_DIR, the CVS administrative repository and files will be |
|
* created. |
|
* Returns the created file on success, or NULL on failure. |
|
*/ |
|
CVSFILE * |
|
cvs_file_create(CVSFILE *parent, const char *path, u_int type, mode_t mode) |
|
{ |
{ |
int fd, l; |
int i; |
char fp[MAXPATHLEN], repo[MAXPATHLEN]; |
struct cvs_flisthead fl; |
CVSFILE *cfp; |
|
|
|
cfp = cvs_file_alloc(path, type); |
TAILQ_INIT(&fl); |
if (cfp == NULL) |
|
return (NULL); |
|
|
|
l = 0; |
for (i = 0; i < argc; i++) |
cfp->cf_mode = mode; |
cvs_file_get(argv[i], &fl); |
cfp->cf_parent = parent; |
|
|
|
if (type == DT_DIR) { |
cvs_file_walklist(&fl, cr); |
cfp->cf_root = cvsroot_get(path); |
cvs_file_freelist(&fl); |
if (cfp->cf_root == NULL) { |
|
cvs_file_free(cfp); |
|
return (NULL); |
|
} |
|
|
|
if (cvs_repo_base != NULL) { |
|
cvs_file_getpath(cfp, fp, sizeof(fp)); |
|
l = snprintf(repo, sizeof(repo), "%s/%s", cvs_repo_base, |
|
fp); |
|
} else { |
|
cvs_file_getpath(cfp, repo, sizeof(repo)); |
|
l = 0; |
|
} |
|
|
|
if (l == -1 || l >= (int)sizeof(repo)) { |
|
errno = ENAMETOOLONG; |
|
cvs_log(LP_ERRNO, "%s", repo); |
|
cvs_file_free(cfp); |
|
return (NULL); |
|
} |
|
|
|
cfp->cf_repo = xstrdup(repo); |
|
if ((mkdir(path, mode) == -1 && errno != EEXIST) || |
|
cvs_mkadmin(path, cfp->cf_root->cr_str, cfp->cf_repo, |
|
NULL, NULL, 0) < 0) { |
|
cvs_file_free(cfp); |
|
return (NULL); |
|
} |
|
} else { |
|
fd = open(path, O_WRONLY|O_CREAT|O_EXCL, mode); |
|
if (fd == -1) { |
|
cvs_file_free(cfp); |
|
return (NULL); |
|
} |
|
(void)close(fd); |
|
} |
|
|
|
return (cfp); |
|
} |
} |
|
|
|
struct cvs_filelist * |
/* |
cvs_file_get(const char *name, struct cvs_flisthead *fl) |
* cvs_file_copy() |
|
* |
|
* Allocate space to create a copy of the file <orig>. The copy inherits all |
|
* of the original's attributes, but does not inherit its children if the |
|
* original file is a directory. Note that files copied using this mechanism |
|
* are linked to their parent, but the parent has no link to the file. This |
|
* is so cvs_file_getpath() works. |
|
* Returns the copied file on success, or NULL on failure. The returned |
|
* structure should be freed using cvs_file_free(). |
|
*/ |
|
CVSFILE * |
|
cvs_file_copy(CVSFILE *orig) |
|
{ |
{ |
char path[MAXPATHLEN]; |
const char *p; |
CVSFILE *cfp; |
struct cvs_filelist *l; |
|
|
cvs_file_getpath(orig, path, sizeof(path)); |
for (p = name; p[0] == '.' && p[1] == '/';) |
|
p += 2; |
|
|
cfp = cvs_file_alloc(path, orig->cf_type); |
TAILQ_FOREACH(l, fl, flist) |
if (cfp == NULL) |
if (!strcmp(l->file_path, p)) |
return (NULL); |
return (l); |
|
|
cfp->cf_parent = orig->cf_parent; |
l = (struct cvs_filelist *)xmalloc(sizeof(*l)); |
cfp->cf_mode = orig->cf_mode; |
l->file_path = xstrdup(p); |
cfp->cf_cvstat = orig->cf_cvstat; |
|
|
|
if (orig->cf_type == DT_REG) { |
TAILQ_INSERT_TAIL(fl, l, flist); |
cfp->cf_etime = orig->cf_etime; |
return (l); |
cfp->cf_mtime = orig->cf_mtime; |
|
} else if (orig->cf_type == DT_DIR) { |
|
/* XXX copy CVS directory attributes */ |
|
} |
|
|
|
return (cfp); |
|
} |
} |
|
|
|
struct cvs_file * |
/* |
cvs_file_get_cf(const char *d, const char *f, int fd, int type) |
* cvs_file_get() |
|
* |
|
* Load a cvs_file structure with all the information pertaining to the file |
|
* <path>. |
|
* The <flags> parameter specifies various flags that alter the behaviour of |
|
* the function. The CF_RECURSE flag causes the function to recursively load |
|
* subdirectories when <path> is a directory. |
|
* The CF_SORT flag causes the files to be sorted in alphabetical order upon |
|
* loading. The special case of "." as a path specification generates |
|
* recursion for a single level and is equivalent to calling cvs_file_get() on |
|
* all files of that directory. |
|
* Returns a pointer to the cvs file structure, which must later be freed |
|
* with cvs_file_free(). |
|
*/ |
|
|
|
int |
|
cvs_file_get(const char *path, int flags, int (*cb)(CVSFILE *, void *), |
|
void *arg, struct cvs_flist *list) |
|
{ |
{ |
char *files[1]; |
int l; |
|
struct cvs_file *cf; |
|
char *p, *rpath; |
|
|
*(const char **)files = path; |
rpath = xmalloc(MAXPATHLEN); |
return cvs_file_getspec(files, 1, flags, cb, arg, list); |
|
} |
|
|
|
|
l = snprintf(rpath, MAXPATHLEN, "%s/%s", d, f); |
|
if (l == -1 || l >= MAXPATHLEN) |
|
fatal("cvs_file_get_cf: overflow"); |
|
|
/* |
for (p = rpath; p[0] == '.' && p[1] == '/';) |
* cvs_file_getspec() |
p += 2; |
* |
|
* Obtain the info about the supplied files or directories. |
|
*/ |
|
int |
|
cvs_file_getspec(char **fspec, int fsn, int flags, int (*cb)(CVSFILE *, void *), |
|
void *arg, struct cvs_flist *list) |
|
{ |
|
int i, freecf; |
|
char pcopy[MAXPATHLEN]; |
|
CVSFILE *cf; |
|
extern char *cvs_rootstr; |
|
|
|
freecf = (list == NULL); |
cf = (struct cvs_file *)xmalloc(sizeof(*cf)); |
cvs_error = CVS_EX_DATA; |
memset(cf, 0, sizeof(*cf)); |
|
|
/* init the list */ |
cf->file_name = xstrdup(f); |
if (list != NULL) |
cf->file_wd = xstrdup(d); |
SIMPLEQ_INIT(list); |
cf->file_path = xstrdup(p); |
|
cf->fd = fd; |
|
cf->repo_fd = -1; |
|
cf->file_type = type; |
|
cf->file_status = cf->file_flags = 0; |
|
cf->file_ent = NULL; |
|
|
/* |
xfree(rpath); |
* Fetch the needed information about ".", so we can setup a few |
return (cf); |
* things to get ourselfs going. |
|
*/ |
|
cf = cvs_file_lget(".", 0, NULL, NULL, NULL); |
|
if (cf == NULL) { |
|
cvs_log(LP_ERR, "failed to obtain '.' information"); |
|
return (-1); |
|
} |
|
|
|
/* |
|
* save the base repository path so we can use it to create |
|
* the correct full repopath later on. |
|
*/ |
|
if (cf->cf_repo != NULL) { |
|
if (cvs_repo_base != NULL) |
|
xfree(cvs_repo_base); |
|
cvs_repo_base = xstrdup(cf->cf_repo); |
|
} |
|
|
|
/* |
|
* This will go away when we have support for multiple Roots. |
|
*/ |
|
if (cvs_rootstr == NULL && cf->cf_root != NULL) { |
|
cvs_rootstr = xstrdup(cf->cf_root->cr_str); |
|
} |
|
|
|
cvs_error = CVS_EX_OK; |
|
|
|
/* |
|
* Since some commands don't require any files to operate |
|
* we can stop right here for those. |
|
*/ |
|
if (cf->cf_root != NULL) { |
|
if (cf->cf_root->cr_method != CVS_METHOD_LOCAL && |
|
cvs_cmdop == CVS_OP_CHECKOUT) { |
|
cvs_file_free(cf); |
|
return (0); |
|
} |
|
} |
|
|
|
cvs_file_free(cf); |
|
|
|
if (cvs_cmdop == CVS_OP_VERSION) |
|
return (0); |
|
|
|
for (i = 0; i < fsn; i++) { |
|
strlcpy(pcopy, fspec[i], sizeof(pcopy)); |
|
|
|
/* |
|
* get rid of any trailing slashes. |
|
*/ |
|
STRIP_SLASH(pcopy); |
|
|
|
/* |
|
* Load the information. |
|
*/ |
|
cf = cvs_file_loadinfo(pcopy, flags, cb, arg, freecf); |
|
if (cf == NULL) { |
|
if (cvs_error != CVS_EX_OK) |
|
return (-1); |
|
continue; |
|
} |
|
|
|
/* |
|
* If extra actions are needed, do them now. |
|
*/ |
|
if (cf->cf_type == DT_DIR) { |
|
/* do possible extra actions .. */ |
|
} else { |
|
/* do possible extra actions .. */ |
|
} |
|
|
|
/* |
|
* Attach it to a list if requested, otherwise |
|
* just free it again. |
|
*/ |
|
if (list != NULL) |
|
SIMPLEQ_INSERT_TAIL(list, cf, cf_list); |
|
else |
|
cvs_file_free(cf); |
|
} |
|
|
|
return (0); |
|
} |
} |
|
|
/* |
void |
* Load the neccesary information about file or directory <path>. |
cvs_file_walklist(struct cvs_flisthead *fl, struct cvs_recursion *cr) |
* Returns a pointer to the loaded information on success, or NULL |
|
* on failure. |
|
* |
|
* If cb is not NULL, the requested path will be passed to that callback |
|
* with <arg> as an argument. |
|
* |
|
* the <freecf> argument is passed to cvs_file_getdir, if this is 1 |
|
* CVSFILE * structs will be free'd once we are done with them. |
|
*/ |
|
CVSFILE * |
|
cvs_file_loadinfo(char *path, int flags, int (*cb)(CVSFILE *, void *), |
|
void *arg, int freecf) |
|
{ |
{ |
CVSFILE *cf, *base; |
int len, fd, type; |
CVSENTRIES *entf; |
|
struct cvs_ent *ent; |
|
char *p; |
|
char parent[MAXPATHLEN], item[MAXPATHLEN]; |
|
int type, callit; |
|
struct stat st; |
struct stat st; |
struct cvsroot *root; |
struct cvs_file *cf; |
|
struct cvs_filelist *l, *nxt; |
|
char *d, *f, *repo, *fpath; |
|
|
type = 0; |
fpath = xmalloc(MAXPATHLEN); |
base = cf = NULL; |
repo = xmalloc(MAXPATHLEN); |
entf = NULL; |
|
ent = NULL; |
|
|
|
/* |
for (l = TAILQ_FIRST(fl); l != NULL; l = nxt) { |
* We first have to find out what type of item we are |
if (cvs_quit) |
* dealing with. A file or a directory. |
fatal("received signal %d", sig_received); |
* |
|
* We can do this by stat(2)'ing the item, but since it |
|
* might be gone we also check the Entries file in the |
|
* parent directory. |
|
*/ |
|
|
|
/* get parent directory */ |
cvs_log(LP_TRACE, "cvs_file_walklist: element '%s'", |
if ((p = strrchr(path, '/')) != NULL) { |
l->file_path); |
*p++ = '\0'; |
|
strlcpy(parent, path, sizeof(parent)); |
|
strlcpy(item, p, sizeof(item)); |
|
*--p = '/'; |
|
} else { |
|
strlcpy(parent, ".", sizeof(parent)); |
|
strlcpy(item, path, sizeof(item)); |
|
} |
|
|
|
/* |
if ((f = basename(l->file_path)) == NULL) |
* There might not be an Entries file, so do not fail if there |
fatal("cvs_file_walklist: basename failed"); |
* is none available to get the info from. |
if ((d = dirname(l->file_path)) == NULL) |
*/ |
fatal("cvs_file_walklist: dirname failed"); |
entf = cvs_ent_open(parent, O_RDWR); |
|
|
|
/* |
if ((fd = open(l->file_path, O_RDONLY)) != -1) { |
* Load the Entry if we successfully opened the Entries file. |
if (fstat(fd, &st) == -1) { |
*/ |
cvs_log(LP_ERRNO, "%s", l->file_path); |
if (entf != NULL) |
(void)close(fd); |
ent = cvs_ent_get(entf, item); |
goto next; |
|
} |
|
|
/* |
if (S_ISDIR(st.st_mode)) |
* No Entry available? fall back to stat(2)'ing the item, if |
type = CVS_DIR; |
* that fails, assume a normal file. |
else if (S_ISREG(st.st_mode)) |
*/ |
type = CVS_FILE; |
if (ent == NULL) { |
else { |
if (stat(path, &st) == -1) |
cvs_log(LP_ERR, |
type = DT_REG; |
"ignoring bad file type for %s", |
else |
l->file_path); |
type = IFTODT(st.st_mode); |
(void)close(fd); |
} else { |
goto next; |
if (ent->ce_type == CVS_ENT_DIR) |
} |
type = DT_DIR; |
} else { |
else |
if (stat(d, &st) == -1) { |
type = DT_REG; |
cvs_log(LP_ERRNO, "%s", d); |
} |
goto next; |
|
} |
|
|
/* |
cvs_get_repo(d, repo, MAXPATHLEN); |
* Get the base, which is <parent> for a normal file or |
len = snprintf(fpath, MAXPATHLEN, "%s/%s", |
* <path> for a directory. |
repo, f); |
*/ |
if (len == -1 || len >= MAXPATHLEN) |
if (type == DT_DIR) |
fatal("cvs_file_walklist: overflow"); |
base = cvs_file_lget(path, flags, NULL, entf, ent); |
|
else |
|
base = cvs_file_lget(parent, flags, NULL, entf, NULL); |
|
|
|
if (base == NULL) { |
if ((fd = open(fpath, O_RDONLY)) == -1) { |
cvs_log(LP_ERR, "failed to obtain directory info for '%s'", |
strlcat(fpath, RCS_FILE_EXT, MAXPATHLEN); |
parent); |
fd = open(fpath, O_RDONLY); |
cvs_error = CVS_EX_FILE; |
} |
goto fail; |
|
} |
|
|
|
/* |
if (fd != -1) { |
* Sanity. |
if (fstat(fd, &st) == -1) |
*/ |
fatal("cvs_file_walklist: %s: %s", |
if (base->cf_type != DT_DIR) { |
fpath, strerror(errno)); |
cvs_log(LP_ERR, "base directory isn't a directory at all"); |
|
goto fail; |
|
} |
|
|
|
root = CVS_DIR_ROOT(base); |
if (S_ISDIR(st.st_mode)) |
if (root == NULL) { |
type = CVS_DIR; |
cvs_error = CVS_EX_BADROOT; |
else if (S_ISREG(st.st_mode)) |
goto fail; |
type = CVS_FILE; |
} |
else { |
|
cvs_log(LP_ERR, |
/* |
"ignoring bad file type for %s", |
* If we have a normal file, get the info and link it |
l->file_path); |
* to the base. |
(void)close(fd); |
*/ |
goto next; |
if (type != DT_DIR) { |
} |
cf = cvs_file_lget(path, flags, base, entf, ent); |
|
if (cf == NULL) { |
/* this file is not in our working copy yet */ |
cvs_error = CVS_EX_DATA; |
(void)close(fd); |
goto fail; |
fd = -1; |
|
} |
} |
} |
|
|
cvs_file_attach(base, cf); |
cf = cvs_file_get_cf(d, f, fd, type); |
} |
if (cf->file_type == CVS_DIR) { |
|
cvs_file_walkdir(cf, cr); |
/* |
} else { |
* Always pass the base directory, unless: |
if (cr->local != NULL) |
* - we are running in server or local mode and the path is not "." |
cr->local(cf); |
* - the directory does not exist on disk. |
|
* - the callback is NULL. |
|
*/ |
|
callit = 1; |
|
if (cb == NULL) |
|
callit = 0; |
|
|
|
if (cvs_cmdop == CVS_OP_SERVER && type != DT_DIR) |
|
callit = 0; |
|
|
|
if (root->cr_method == CVS_METHOD_LOCAL && type != DT_DIR) |
|
callit = 0; |
|
|
|
if (!(base->cf_flags & CVS_FILE_ONDISK)) |
|
callit = 0; |
|
|
|
if (callit != 0) { |
|
if ((cvs_error = cb(base,arg)) != CVS_EX_OK) |
|
goto fail; |
|
} |
|
|
|
/* |
|
* If we have a normal file, pass it as well. |
|
*/ |
|
if (type != DT_DIR) { |
|
if (cb != NULL && (cvs_error = cb(cf, arg)) != CVS_EX_OK) |
|
goto fail; |
|
} else { |
|
/* |
|
* If the directory exists, recurse through it. |
|
*/ |
|
if ((base->cf_flags & CVS_FILE_ONDISK) && |
|
cvs_file_getdir(base, flags, cb, arg, freecf) < 0) { |
|
cvs_error = CVS_EX_FILE; |
|
goto fail; |
|
} |
} |
} |
|
|
|
if (entf != NULL) { |
cvs_file_free(cf); |
cvs_ent_close(entf); |
|
entf = NULL; |
|
} |
|
|
|
return (base); |
next: |
|
nxt = TAILQ_NEXT(l, flist); |
|
TAILQ_REMOVE(fl, l, flist); |
|
|
fail: |
xfree(l->file_path); |
if (entf != NULL) |
xfree(l); |
cvs_ent_close(entf); |
|
if (base != NULL) |
|
cvs_file_free(base); |
|
return (NULL); |
|
} |
|
|
|
/* |
|
* cvs_file_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. |
|
*/ |
|
CVSFILE * |
|
cvs_file_find(CVSFILE *hier, const char *path) |
|
{ |
|
char *pp, *sp, pbuf[MAXPATHLEN]; |
|
CVSFILE *sf, *cf; |
|
|
|
strlcpy(pbuf, path, sizeof(pbuf)); |
|
|
|
cf = hier; |
|
pp = pbuf; |
|
do { |
|
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->cf_parent == NULL) { |
|
cvs_log(LP_NOTICE, |
|
"path %s goes back too far", path); |
|
return (NULL); |
|
} |
|
cf = cf->cf_parent; |
|
continue; |
|
} else if (*(pp + 1) == '\0') |
|
continue; |
|
} |
|
|
|
SIMPLEQ_FOREACH(sf, &(cf->cf_files), cf_list) |
|
if (cvs_file_cmpname(pp, sf->cf_name) == 0) |
|
break; |
|
if (sf == NULL) |
|
return (NULL); |
|
|
|
cf = sf; |
|
pp = sp; |
|
} while (sp != NULL); |
|
|
|
return (cf); |
|
} |
|
|
|
|
|
/* |
|
* cvs_file_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. |
|
*/ |
|
char * |
|
cvs_file_getpath(CVSFILE *file, char *buf, size_t len) |
|
{ |
|
memset(buf, '\0', len); |
|
if (file->cf_dir != NULL) { |
|
strlcat(buf, file->cf_dir, len); |
|
strlcat(buf, "/", len); |
|
} |
} |
|
|
strlcat(buf, file->cf_name, len); |
xfree(fpath); |
return (buf); |
xfree(repo); |
} |
} |
|
|
/* |
void |
* cvs_file_attach() |
cvs_file_walkdir(struct cvs_file *cf, struct cvs_recursion *cr) |
* |
|
* Attach the file <file> as one of the children of parent <parent>, which |
|
* has to be a file of type DT_DIR. |
|
* Returns 0 on success, or -1 on failure. |
|
*/ |
|
int |
|
cvs_file_attach(CVSFILE *parent, CVSFILE *file) |
|
{ |
{ |
if (parent->cf_type != DT_DIR) |
int l; |
return (-1); |
FILE *fp; |
|
int nbytes; |
|
size_t len; |
|
long base; |
|
size_t bufsize; |
|
struct stat st; |
|
struct dirent *dp; |
|
struct cvs_ent *ent; |
|
struct cvs_ignpat *ip; |
|
struct cvs_ent_line *line; |
|
struct cvs_flisthead fl, dl; |
|
CVSENTRIES *entlist; |
|
char *buf, *ebuf, *cp, *repo, *fpath; |
|
|
SIMPLEQ_INSERT_TAIL(&(parent->cf_files), file, cf_list); |
cvs_log(LP_TRACE, "cvs_file_walkdir(%s)", cf->file_path); |
file->cf_parent = parent; |
|
|
|
return (0); |
if (cr->enterdir != NULL) |
} |
cr->enterdir(cf); |
|
|
|
if (cr->local != NULL) |
|
cr->local(cf); |
|
|
/* |
repo = xmalloc(MAXPATHLEN); |
* Load directory information |
fpath = xmalloc(MAXPATHLEN); |
*/ |
|
static int |
|
cvs_load_dirinfo(CVSFILE *cf, int flags) |
|
{ |
|
char fpath[MAXPATHLEN]; |
|
char pbuf[MAXPATHLEN]; |
|
struct stat st; |
|
int l; |
|
|
|
cvs_file_getpath(cf, fpath, sizeof(fpath)); |
|
|
|
/* |
/* |
* Try to obtain the Root for this given directory, if we cannot |
* If we do not have a admin directory inside here, dont bother. |
* get it, fail, unless we are dealing with a directory that is |
|
* unknown or not on disk. |
|
*/ |
*/ |
cf->cf_root = cvsroot_get(fpath); |
l = snprintf(fpath, MAXPATHLEN, "%s/%s", cf->file_path, |
if (cf->cf_root == NULL) { |
CVS_PATH_CVSDIR); |
if (cf->cf_cvstat == CVS_FST_UNKNOWN || |
if (l == -1 || l >= MAXPATHLEN) |
!(cf->cf_flags & CVS_FILE_ONDISK)) |
fatal("cvs_file_walkdir: overflow"); |
return (0); |
|
return (-1); |
|
} |
|
|
|
/* if the CVS administrative directory exists, load the info */ |
if (stat(fpath, &st) == -1) { |
l = snprintf(pbuf, sizeof(pbuf), "%s/" CVS_PATH_CVSDIR, fpath); |
xfree(repo); |
if (l == -1 || l >= (int)sizeof(pbuf)) { |
xfree(fpath); |
errno = ENAMETOOLONG; |
return; |
cvs_log(LP_ERRNO, "%s", pbuf); |
|
return (-1); |
|
} |
} |
|
|
if (stat(pbuf, &st) == 0 && S_ISDIR(st.st_mode)) { |
|
if (cvs_readrepo(fpath, pbuf, sizeof(pbuf)) == 0) |
|
cf->cf_repo = xstrdup(pbuf); |
|
} else { |
|
/* |
|
* Fill in the repo path ourselfs. |
|
*/ |
|
if (cvs_repo_base != NULL) { |
|
l = snprintf(pbuf, sizeof(pbuf), "%s/%s", |
|
cvs_repo_base, fpath); |
|
if (l == -1 || l >= (int)sizeof(pbuf)) |
|
return (-1); |
|
|
|
cf->cf_repo = xstrdup(pbuf); |
|
} else |
|
cf->cf_repo = NULL; |
|
} |
|
|
|
return (0); |
|
} |
|
|
|
/* |
|
* cvs_file_getdir() |
|
* |
|
* Get a cvs directory structure for the directory whose path is <dir>. |
|
* This function should not free the directory information on error, as this |
|
* is performed by cvs_file_free(). |
|
*/ |
|
static int |
|
cvs_file_getdir(CVSFILE *cf, int flags, int (*cb)(CVSFILE *, void *), |
|
void *arg, int freecf) |
|
{ |
|
int ret; |
|
size_t len; |
|
DIR *dp; |
|
struct dirent *de; |
|
char fpath[MAXPATHLEN], pbuf[MAXPATHLEN]; |
|
CVSENTRIES *entf; |
|
CVSFILE *cfp; |
|
struct cvs_ent *ent; |
|
struct cvs_flist dirs; |
|
int nfiles, ndirs; |
|
|
|
if ((flags & CF_KNOWN) && cf->cf_cvstat == CVS_FST_UNKNOWN) |
|
return (0); |
|
|
|
/* |
/* |
* if we are working with a repository, fiddle with |
* check for a local .cvsignore file |
* the pathname again. |
|
*/ |
*/ |
if (flags & CF_REPO) { |
l = snprintf(fpath, MAXPATHLEN, "%s/.cvsignore", cf->file_path); |
ret = snprintf(fpath, sizeof(fpath), "%s%s%s", |
if (l == -1 || l >= MAXPATHLEN) |
cf->cf_root->cr_dir, |
fatal("cvs_file_walkdir: overflow"); |
(cf->cf_dir != NULL) ? "/" : "", |
|
(cf->cf_dir != NULL) ? cf->cf_dir : ""); |
|
if (ret == -1 || ret >= (int)sizeof(fpath)) |
|
return (-1); |
|
|
|
if (cf->cf_dir != NULL) |
if ((fp = fopen(fpath, "r")) != NULL) { |
xfree(cf->cf_dir); |
while (fgets(fpath, MAXPATHLEN, fp)) { |
cf->cf_dir = xstrdup(fpath); |
len = strlen(fpath); |
} |
if (fpath[len - 1] == '\n') |
|
fpath[len - 1] = '\0'; |
|
|
nfiles = ndirs = 0; |
cvs_file_ignore(fpath, &dir_ign_pats); |
SIMPLEQ_INIT(&dirs); |
} |
cvs_file_getpath(cf, fpath, sizeof(fpath)); |
|
|
|
if ((dp = opendir(fpath)) == NULL) { |
(void)fclose(fp); |
cvs_log(LP_ERRNO, "failed to open directory '%s'", fpath); |
|
return (-1); |
|
} |
} |
|
|
ret = -1; |
if (fstat(cf->fd, &st) == -1) |
entf = cvs_ent_open(fpath, O_RDWR); |
fatal("cvs_file_walkdir: %s %s", cf->file_path, |
while ((de = readdir(dp)) != NULL) { |
strerror(errno)); |
if (!strcmp(de->d_name, ".") || |
|
!strcmp(de->d_name, "..")) |
|
continue; |
|
|
|
len = cvs_path_cat(fpath, de->d_name, pbuf, sizeof(pbuf)); |
bufsize = st.st_size; |
if (len >= sizeof(pbuf)) |
if (bufsize < st.st_blksize) |
goto done; |
bufsize = st.st_blksize; |
|
|
if (entf != NULL) |
buf = xmalloc(bufsize); |
ent = cvs_ent_get(entf, de->d_name); |
TAILQ_INIT(&fl); |
else |
TAILQ_INIT(&dl); |
ent = NULL; |
|
|
|
/* |
while ((nbytes = getdirentries(cf->fd, buf, bufsize, &base)) > 0) { |
* Do some filtering on the current directory item. |
ebuf = buf + nbytes; |
*/ |
cp = buf; |
if ((flags & CF_IGNORE) && cvs_file_chkign(de->d_name)) |
|
continue; |
|
|
|
if (!(flags & CF_RECURSE) && (de->d_type == DT_DIR)) { |
while (cp < ebuf) { |
if (ent != NULL) |
dp = (struct dirent *)cp; |
ent->processed = 1; |
if (!strcmp(dp->d_name, ".") || |
continue; |
!strcmp(dp->d_name, "..") || |
} |
!strcmp(dp->d_name, CVS_PATH_CVSDIR) || |
|
dp->d_reclen == 0) { |
|
cp += dp->d_reclen; |
|
continue; |
|
} |
|
|
if (de->d_type != DT_DIR && (flags & CF_NOFILES)) |
if (cvs_file_chkign(dp->d_name)) { |
continue; |
cp += dp->d_reclen; |
|
continue; |
|
} |
|
|
cfp = cvs_file_lget(pbuf, flags, cf, entf, ent); |
l = snprintf(fpath, MAXPATHLEN, "%s/%s", |
if (cfp == NULL) { |
cf->file_path, dp->d_name); |
cvs_log(LP_ERR, "failed to get '%s'", pbuf); |
if (l == -1 || l >= MAXPATHLEN) |
goto done; |
fatal("cvs_file_walkdir: overflow"); |
} |
|
|
|
/* |
/* |
* A file is linked to the parent <cf>, a directory |
* Anticipate the file type to sort them, |
* is added to the dirs SIMPLEQ list for later use. |
* note that we do not determine the final |
*/ |
* type until we actually have the fd floating |
if (cfp->cf_type != DT_DIR && !freecf) { |
* around. |
SIMPLEQ_INSERT_TAIL(&(cf->cf_files), cfp, cf_list); |
*/ |
nfiles++; |
if (dp->d_type == DT_DIR) |
} else if (cfp->cf_type == DT_DIR) { |
cvs_file_get(fpath, &dl); |
SIMPLEQ_INSERT_TAIL(&dirs, cfp, cf_list); |
else if (dp->d_type == DT_REG) |
ndirs++; |
cvs_file_get(fpath, &fl); |
} |
|
|
|
/* |
cp += dp->d_reclen; |
* Now, for a file, pass it to the callback if it was |
|
* supplied to us. |
|
*/ |
|
if (cfp->cf_type != DT_DIR && cb != NULL) { |
|
if ((cvs_error = cb(cfp, arg)) != CVS_EX_OK) |
|
goto done; |
|
} |
} |
|
|
/* |
|
* Mark the entry as processed. |
|
*/ |
|
if (ent != NULL) |
|
ent->processed = 1; |
|
|
|
/* |
|
* If we don't want to keep it, free it |
|
*/ |
|
if (cfp->cf_type != DT_DIR && freecf) |
|
cvs_file_free(cfp); |
|
} |
} |
|
|
closedir(dp); |
if (nbytes == -1) |
dp = NULL; |
fatal("cvs_file_walkdir: %s %s", cf->file_path, |
|
strerror(errno)); |
|
|
/* |
xfree(buf); |
* Pass over all of the entries now, so we pickup any files |
|
* that might have been lost, or are for some reason not on disk. |
|
* |
|
* (Follows the same procedure as above ... can we merge them?) |
|
*/ |
|
while (entf != NULL && (ent = cvs_ent_next(entf)) != NULL) { |
|
if (ent->processed == 1) |
|
continue; |
|
if (!(flags & CF_RECURSE) && ent->ce_type == CVS_ENT_DIR) |
|
continue; |
|
if ((flags & CF_NOFILES) && ent->ce_type != CVS_ENT_DIR) |
|
continue; |
|
|
|
len = cvs_path_cat(fpath, ent->ce_name, pbuf, sizeof(pbuf)); |
while ((ip = TAILQ_FIRST(&dir_ign_pats)) != NULL) { |
if (len >= sizeof(pbuf)) |
TAILQ_REMOVE(&dir_ign_pats, ip, ip_list); |
goto done; |
xfree(ip); |
|
} |
|
|
cfp = cvs_file_lget(pbuf, flags, cf, entf, ent); |
entlist = cvs_ent_open(cf->file_path); |
if (cfp == NULL) { |
TAILQ_FOREACH(line, &(entlist->cef_ent), entries_list) { |
cvs_log(LP_ERR, "failed to fetch '%s'", pbuf); |
ent = cvs_ent_parse(line->buf); |
goto done; |
|
} |
|
|
|
if (cfp->cf_type != DT_DIR && !freecf) { |
l = snprintf(fpath, MAXPATHLEN, "%s/%s", cf->file_path, |
SIMPLEQ_INSERT_TAIL(&(cf->cf_files), cfp, cf_list); |
ent->ce_name); |
nfiles++; |
if (l == -1 || l >= MAXPATHLEN) |
} else if (cfp->cf_type == DT_DIR) { |
fatal("cvs_file_walkdir: overflow"); |
SIMPLEQ_INSERT_TAIL(&dirs, cfp, cf_list); |
|
ndirs++; |
|
} |
|
|
|
if (cfp->cf_type != DT_DIR && cb != NULL) { |
if (ent->ce_type == CVS_ENT_DIR) |
if ((cvs_error = cb(cfp, arg)) != CVS_EX_OK) |
cvs_file_get(fpath, &dl); |
goto done; |
else if (ent->ce_type == CVS_ENT_FILE) |
} |
cvs_file_get(fpath, &fl); |
|
|
if (cfp->cf_type != DT_DIR && freecf) |
cvs_ent_free(ent); |
cvs_file_free(cfp); |
|
} |
} |
|
|
/* |
cvs_ent_close(entlist, ENT_NOSYNC); |
* Sort files and dirs if requested. |
|
*/ |
|
if (flags & CF_SORT) { |
|
if (nfiles > 0) |
|
cvs_file_sort(&(cf->cf_files), nfiles); |
|
if (ndirs > 0) |
|
cvs_file_sort(&dirs, ndirs); |
|
} |
|
|
|
/* |
cvs_get_repo(cf->file_path, repo, MAXPATHLEN); |
* Finally, run over the directories we have encountered. |
cvs_repository_lock(repo); |
* Before calling cvs_file_getdir() on them, we pass them |
|
* to the callback first. |
|
*/ |
|
while (!SIMPLEQ_EMPTY(&dirs)) { |
|
cfp = SIMPLEQ_FIRST(&dirs); |
|
SIMPLEQ_REMOVE_HEAD(&dirs, cf_list); |
|
|
|
if (!freecf) |
cvs_repository_getdir(repo, cf->file_path, &fl, &dl); |
SIMPLEQ_INSERT_TAIL(&(cf->cf_files), cfp, cf_list); |
|
|
|
if (cb != NULL) { |
cvs_file_walklist(&fl, cr); |
if ((cvs_error = cb(cfp, arg)) != CVS_EX_OK) |
cvs_file_freelist(&fl); |
goto done; |
|
} |
|
|
|
if ((cfp->cf_flags & CVS_FILE_ONDISK) && |
cvs_repository_unlock(repo); |
cvs_file_getdir(cfp, flags, cb, arg, freecf) < 0) |
|
goto done; |
|
|
|
if (freecf) |
cvs_file_walklist(&dl, cr); |
cvs_file_free(cfp); |
cvs_file_freelist(&dl); |
} |
|
|
|
ret = 0; |
xfree(repo); |
cfp = NULL; |
xfree(fpath); |
done: |
|
if (cfp != NULL && freecf) |
|
cvs_file_free(cfp); |
|
|
|
while (!SIMPLEQ_EMPTY(&dirs)) { |
if (cr->leavedir != NULL) |
cfp = SIMPLEQ_FIRST(&dirs); |
cr->leavedir(cf); |
SIMPLEQ_REMOVE_HEAD(&dirs, cf_list); |
|
|
|
cvs_file_free(cfp); |
|
} |
|
|
|
if (entf != NULL) |
|
cvs_ent_close(entf); |
|
if (dp != NULL) |
|
closedir(dp); |
|
|
|
return (ret); |
|
} |
} |
|
|
|
|
/* |
|
* cvs_file_free() |
|
* |
|
* Free a cvs_file structure and its contents. |
|
*/ |
|
void |
void |
cvs_file_free(CVSFILE *cf) |
cvs_file_freelist(struct cvs_flisthead *fl) |
{ |
{ |
CVSFILE *child; |
struct cvs_filelist *f; |
|
|
if (cf->cf_name != NULL) |
while ((f = TAILQ_FIRST(fl)) != NULL) { |
xfree(cf->cf_name); |
TAILQ_REMOVE(fl, f, flist); |
|
xfree(f->file_path); |
if (cf->cf_dir != NULL) |
xfree(f); |
xfree(cf->cf_dir); |
|
|
|
if (cf->cf_type == DT_DIR) { |
|
if (cf->cf_root != NULL) |
|
cvsroot_remove(cf->cf_root); |
|
if (cf->cf_repo != NULL) |
|
xfree(cf->cf_repo); |
|
while (!SIMPLEQ_EMPTY(&(cf->cf_files))) { |
|
child = SIMPLEQ_FIRST(&(cf->cf_files)); |
|
SIMPLEQ_REMOVE_HEAD(&(cf->cf_files), cf_list); |
|
cvs_file_free(child); |
|
} |
|
} else { |
|
if (cf->cf_tag != NULL) |
|
xfree(cf->cf_tag); |
|
if (cf->cf_opts != NULL) |
|
xfree(cf->cf_opts); |
|
} |
} |
|
|
xfree(cf); |
|
} |
} |
|
|
|
void |
/* |
cvs_file_classify(struct cvs_file *cf) |
* cvs_file_sort() |
|
* |
|
* Sort a list of cvs file structures according to their filename. The list |
|
* <flp> is modified according to the sorting algorithm. The number of files |
|
* in the list must be given by <nfiles>. |
|
* Returns 0 on success, or -1 on failure. |
|
*/ |
|
static int |
|
cvs_file_sort(struct cvs_flist *flp, u_int nfiles) |
|
{ |
{ |
int i; |
size_t len; |
size_t nb; |
time_t mtime; |
CVSFILE *cf, **cfvec; |
struct stat st; |
|
int rflags, l, ismodified, rcsdead; |
|
CVSENTRIES *entlist = NULL; |
|
const char *state; |
|
char *repo, *rcsfile, r1[16], r2[16]; |
|
|
cfvec = xcalloc((size_t)nfiles, sizeof(*cfvec)); |
cvs_log(LP_TRACE, "cvs_file_classify(%s)", cf->file_path); |
|
|
i = 0; |
if (!strcmp(cf->file_path, ".")) { |
SIMPLEQ_FOREACH(cf, flp, cf_list) { |
cf->file_status = FILE_UPTODATE; |
if (i == (int)nfiles) { |
return; |
cvs_log(LP_WARN, "too many files to sort"); |
|
/* rebuild the list and abort sorting */ |
|
while (--i >= 0) |
|
SIMPLEQ_INSERT_HEAD(flp, cfvec[i], cf_list); |
|
xfree(cfvec); |
|
return (-1); |
|
} |
|
cfvec[i++] = cf; |
|
|
|
/* now unlink it from the list, |
|
* we'll put it back in order later |
|
*/ |
|
SIMPLEQ_REMOVE_HEAD(flp, cf_list); |
|
} |
} |
|
|
/* clear the list just in case */ |
entlist = cvs_ent_open(cf->file_wd); |
SIMPLEQ_INIT(flp); |
|
nb = (size_t)i; |
|
|
|
heapsort(cfvec, nb, sizeof(cf), cvs_file_cmp); |
repo = xmalloc(MAXPATHLEN); |
|
rcsfile = xmalloc(MAXPATHLEN); |
|
|
/* rebuild the list from the bottom up */ |
cvs_get_repo(cf->file_wd, repo, MAXPATHLEN); |
for (i = (int)nb - 1; i >= 0; i--) |
l = snprintf(rcsfile, MAXPATHLEN, "%s/%s", |
SIMPLEQ_INSERT_HEAD(flp, cfvec[i], cf_list); |
repo, cf->file_name); |
|
if (l == -1 || l >= MAXPATHLEN) |
|
fatal("cvs_file_classify: overflow"); |
|
|
xfree(cfvec); |
if (cf->file_type == CVS_FILE) { |
return (0); |
len = strlcat(rcsfile, RCS_FILE_EXT, MAXPATHLEN); |
} |
if (len >= MAXPATHLEN) |
|
fatal("cvs_file_classify: truncation"); |
|
} |
|
|
|
cf->file_rpath = xstrdup(rcsfile); |
|
cf->file_ent = cvs_ent_get(entlist, cf->file_name); |
|
|
static int |
if (cf->file_type == CVS_DIR) { |
cvs_file_cmp(const void *f1, const void *f2) |
if (cf->fd == -1 && stat(rcsfile, &st) != -1) |
{ |
cf->file_status = DIR_CREATE; |
const CVSFILE *cf1, *cf2; |
else if (cf->file_ent != NULL) |
cf1 = *(CVSFILE * const *)f1; |
cf->file_status = FILE_UPTODATE; |
cf2 = *(CVSFILE * const *)f2; |
xfree(repo); |
return cvs_file_cmpname(cf1->cf_name, cf2->cf_name); |
xfree(rcsfile); |
} |
cvs_ent_close(entlist, ENT_NOSYNC); |
|
return; |
|
} |
|
|
|
rflags = 0; |
|
cf->repo_fd = open(cf->file_rpath, O_RDONLY); |
|
if (cf->repo_fd != -1) { |
|
cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd, rflags); |
|
if (cf->file_rcs == NULL) |
|
fatal("cvs_file_classify: rcs_open failed while it " |
|
"shouldn't"); |
|
} else { |
|
cf->file_rcs = NULL; |
|
} |
|
|
/* |
if (cf->file_ent != NULL) |
* cvs_file_alloc() |
rcsnum_tostr(cf->file_ent->ce_rev, r1, sizeof(r1)); |
* |
if (cf->file_rcs != NULL) |
* Allocate a CVSFILE structure and initialize its internals. |
rcsnum_tostr(cf->file_rcs->rf_head, r2, sizeof(r2)); |
*/ |
|
CVSFILE * |
|
cvs_file_alloc(const char *path, u_int type) |
|
{ |
|
CVSFILE *cfp; |
|
char *p; |
|
|
|
cfp = xcalloc(1, sizeof(*cfp)); |
ismodified = rcsdead = 0; |
|
if (cf->fd != -1 && cf->file_ent != NULL) { |
|
if (fstat(cf->fd, &st) == -1) |
|
fatal("cvs_file_classify: %s", strerror(errno)); |
|
|
cfp->cf_type = type; |
mtime = cvs_hack_time(st.st_mtime, 1); |
cfp->cf_cvstat = CVS_FST_UNKNOWN; |
if (mtime == 0) |
|
fatal("to gmt failed"); |
|
|
if (type == DT_DIR) { |
if (mtime != cf->file_ent->ce_mtime) |
SIMPLEQ_INIT(&(cfp->cf_files)); |
ismodified = 1; |
} |
} |
|
|
cfp->cf_name = xstrdup(basename(path)); |
if (cf->file_rcs != NULL) { |
if ((p = strrchr(path, '/')) != NULL) { |
state = rcs_state_get(cf->file_rcs, cf->file_rcs->rf_head); |
*p = '\0'; |
if (state == NULL) |
if (strcmp(path, ".")) |
fatal("failed to get state for HEAD for %s", |
cfp->cf_dir = xstrdup(path); |
cf->file_path); |
else |
if (!strcmp(state, "dead")) |
cfp->cf_dir = NULL; |
rcsdead = 1; |
*p = '/'; |
|
} else |
|
cfp->cf_dir = NULL; |
|
|
|
return (cfp); |
|
} |
|
|
|
|
|
/* |
|
* cvs_file_lget() |
|
* |
|
* Get the file and link it with the parent right away. |
|
* Returns a pointer to the created file structure on success, or NULL on |
|
* failure. |
|
*/ |
|
static CVSFILE * |
|
cvs_file_lget(const char *path, int flags, CVSFILE *parent, CVSENTRIES *pent, |
|
struct cvs_ent *ent) |
|
{ |
|
char *c; |
|
int ret; |
|
u_int type; |
|
struct stat st; |
|
CVSFILE *cfp; |
|
struct cvsroot *root; |
|
|
|
type = DT_UNKNOWN; |
|
ret = stat(path, &st); |
|
if (ret == 0) |
|
type = IFTODT(st.st_mode); |
|
|
|
if ((flags & CF_REPO) && type != DT_DIR) { |
|
if ((c = strrchr(path, ',')) == NULL) |
|
return (NULL); |
|
*c = '\0'; |
|
} |
} |
|
|
if ((cfp = cvs_file_alloc(path, type)) == NULL) |
/* |
return (NULL); |
* 10 Sin |
cfp->cf_parent = parent; |
* 20 Goto hell |
cfp->cf_entry = pent; |
* (I welcome you if-else hell) |
|
*/ |
if (cfp->cf_type == DT_DIR && cfp->cf_parent == NULL) |
if (cf->file_ent == NULL) { |
cfp->cf_flags |= CVS_DIRF_BASE; |
if (cf->file_rcs == NULL) { |
|
if (cf->fd == -1) { |
if (ret == 0) { |
if (verbosity > 1) |
cfp->cf_mode = st.st_mode & ACCESSPERMS; |
cvs_log(LP_NOTICE, |
if (cfp->cf_type == DT_REG) |
"nothing known about '%s'", |
cfp->cf_mtime = st.st_mtime; |
cf->file_path); |
cfp->cf_flags |= CVS_FILE_ONDISK; |
} else { |
|
if (verbosity > 1) |
if (ent == NULL) |
cvs_log(LP_NOTICE, |
if (cfp->cf_flags & CVS_DIRF_BASE) |
"use add to create an entry for %s", |
cfp->cf_cvstat = CVS_FST_UPTODATE; |
cf->file_path); |
else |
|
cfp->cf_cvstat = CVS_FST_UNKNOWN; |
|
else { |
|
/* always show directories as up-to-date */ |
|
if (ent->ce_type == CVS_ENT_DIR) |
|
cfp->cf_cvstat = CVS_FST_UPTODATE; |
|
else if (rcsnum_cmp(ent->ce_rev, cvs_addedrev, 2) == 0) |
|
cfp->cf_cvstat = CVS_FST_ADDED; |
|
else { |
|
/* |
|
* correct st.st_mtime first |
|
*/ |
|
if ((st.st_mtime = |
|
cvs_hack_time(st.st_mtime, 1)) == 0) { |
|
cvs_file_free(cfp); |
|
return (NULL); |
|
} |
|
|
|
/* check last modified time */ |
|
if (ent->ce_mtime == (time_t)st.st_mtime) { |
|
cfp->cf_cvstat = CVS_FST_UPTODATE; |
|
} else { |
|
cfp->cf_cvstat = CVS_FST_MODIFIED; |
|
} |
|
} |
} |
|
|
cfp->cf_etime = ent->ce_mtime; |
cf->file_status = FILE_UNKNOWN; |
|
} else if (rcsdead == 1) { |
|
if (cf->fd == -1) { |
|
cf->file_status = FILE_UPTODATE; |
|
} else { |
|
cvs_log(LP_NOTICE, |
|
"use add to create an entry for %s", |
|
cf->file_path); |
|
cf->file_status = FILE_UNKNOWN; |
|
} |
|
} else { |
|
cf->file_status = FILE_CHECKOUT; |
} |
} |
} else { |
} else if (cf->file_ent->ce_status == CVS_ENT_ADDED) { |
if (ent == NULL) { |
if (cf->fd == -1) { |
/* assume it is a file and unknown */ |
if (verbosity > 1) |
cfp->cf_cvstat = CVS_FST_UNKNOWN; |
cvs_log(LP_NOTICE, |
cfp->cf_type = DT_REG; |
"warning: new-born %s has dissapeared", |
|
cf->file_path); |
|
cf->file_status = FILE_REMOVE_ENTRY; |
|
} else if (cf->file_rcs == NULL || rcsdead == 1) { |
|
cf->file_status = FILE_ADDED; |
} else { |
} else { |
if (ent->ce_type == CVS_ENT_FILE) |
if (verbosity > 1) |
cfp->cf_type = DT_REG; |
cvs_log(LP_NOTICE, |
else if (ent->ce_type == CVS_ENT_DIR) |
"conflict: %s already created by others", |
cfp->cf_type = DT_DIR; |
cf->file_path); |
else |
cf->file_status = FILE_CONFLICT; |
cvs_log(LP_WARN, "unknown ce_type %d", |
|
ent->ce_type); |
|
|
|
if (ent->ce_status == CVS_ENT_REMOVED) |
|
cfp->cf_cvstat = CVS_FST_REMOVED; |
|
else if (ent->ce_status == CVS_ENT_UPTODATE) |
|
cfp->cf_cvstat = CVS_FST_UPTODATE; |
|
else if (ent->ce_status == CVS_ENT_ADDED) |
|
cfp->cf_cvstat = CVS_FST_ADDED; |
|
else |
|
cfp->cf_cvstat = CVS_FST_LOST; |
|
} |
} |
|
} else if (cf->file_ent->ce_status == CVS_ENT_REMOVED) { |
/* XXX assume 0644 ? */ |
if (cf->fd != -1) { |
cfp->cf_mode = 0644; |
if (verbosity > 1) |
} |
cvs_log(LP_NOTICE, |
|
"%s should be removed but is still there", |
if (ent != NULL) { |
cf->file_path); |
/* steal the RCSNUM */ |
cf->file_status = FILE_REMOVED; |
cfp->cf_lrev = ent->ce_rev; |
} else if (cf->file_rcs == NULL || rcsdead == 1) { |
|
cf->file_status = FILE_REMOVE_ENTRY; |
if (ent->ce_type == CVS_ENT_FILE) { |
} else { |
if (ent->ce_tag[0] != '\0') |
if (strcmp(r1, r2)) { |
cfp->cf_tag = xstrdup(ent->ce_tag); |
if (verbosity > 1) |
|
cvs_log(LP_NOTICE, |
if (ent->ce_opts[0] != '\0') |
"conflict: removed %s was modified" |
cfp->cf_opts = xstrdup(ent->ce_opts); |
" by a second party", |
|
cf->file_path); |
|
cf->file_status = FILE_CONFLICT; |
|
} else { |
|
cf->file_status = FILE_REMOVED; |
|
} |
} |
} |
} |
} else if (cf->file_ent->ce_status == CVS_ENT_REG) { |
|
if (cf->file_rcs == NULL || rcsdead == 1) { |
|
if (cf->fd == -1) { |
|
if (verbosity > 1) |
|
cvs_log(LP_NOTICE, |
|
"warning: %s's entry exists but" |
|
" there is no longer a file" |
|
" in the repository," |
|
" removing entry", |
|
cf->file_path); |
|
cf->file_status = FILE_REMOVE_ENTRY; |
|
} else { |
|
if (ismodified) { |
|
if (verbosity > 1) |
|
cvs_log(LP_NOTICE, |
|
"conflict: %s is no longer " |
|
"in the repository but is " |
|
"locally modified", |
|
cf->file_path); |
|
cf->file_status = FILE_CONFLICT; |
|
} else { |
|
if (verbosity > 1) |
|
cvs_log(LP_NOTICE, |
|
"%s is no longer in the " |
|
"repository", |
|
cf->file_path); |
|
|
if (cfp->cf_type == DT_DIR) { |
cf->file_status = FILE_UNLINK; |
if (cvs_load_dirinfo(cfp, flags) < 0) { |
} |
cvs_file_free(cfp); |
} |
return (NULL); |
|
} |
|
} |
|
|
|
if (flags & CF_REPO) { |
|
root = CVS_DIR_ROOT(cfp); |
|
|
|
cfp->cf_mode = 0644; |
|
cfp->cf_cvstat = CVS_FST_LOST; |
|
|
|
c = xstrdup(cfp->cf_dir); |
|
xfree(cfp->cf_dir); |
|
|
|
if (strcmp(c, root->cr_dir)) { |
|
c += strlen(root->cr_dir) + 1; |
|
cfp->cf_dir = xstrdup(c); |
|
c -= strlen(root->cr_dir) + 1; |
|
} else { |
} else { |
cfp->cf_dir = NULL; |
if (cf->fd == -1) { |
} |
if (verbosity > 1) |
|
cvs_log(LP_NOTICE, |
|
"warning: %s was lost", |
|
cf->file_path); |
|
cf->file_status = FILE_LOST; |
|
} else { |
|
if (ismodified == 1) |
|
cf->file_status = FILE_MODIFIED; |
|
else |
|
cf->file_status = FILE_UPTODATE; |
|
|
xfree(c); |
if (strcmp(r1, r2)) { |
|
if (cf->file_status == FILE_MODIFIED) |
|
cf->file_status = FILE_MERGE; |
|
else |
|
cf->file_status = FILE_PATCH; |
|
} |
|
} |
|
} |
} |
} |
|
|
if (cfp->cf_repo != NULL && cfp->cf_type == DT_DIR && |
xfree(repo); |
!strcmp(cfp->cf_repo, path)) |
xfree(rcsfile); |
cfp->cf_cvstat = CVS_FST_UPTODATE; |
cvs_ent_close(entlist, ENT_NOSYNC); |
|
} |
|
|
/* |
void |
* In server mode, we do a few extra checks. |
cvs_file_free(struct cvs_file *cf) |
*/ |
{ |
if (cvs_cmdop == CVS_OP_SERVER) { |
xfree(cf->file_name); |
/* |
xfree(cf->file_wd); |
* If for some reason a file was added, |
xfree(cf->file_path); |
* but does not exist anymore, start complaining. |
|
*/ |
|
if (!(cfp->cf_flags & CVS_FILE_ONDISK) && |
|
(cfp->cf_cvstat == CVS_FST_ADDED) && |
|
(cfp->cf_type != DT_DIR)) |
|
cvs_log(LP_WARN, "new-born %s has disappeared", path); |
|
|
|
/* |
if (cf->file_rpath != NULL) |
* Any other needed checks? |
xfree(cf->file_rpath); |
*/ |
if (cf->file_ent != NULL) |
} |
cvs_ent_free(cf->file_ent); |
|
if (cf->file_rcs != NULL) |
return (cfp); |
rcs_close(cf->file_rcs); |
|
if (cf->fd != -1) |
|
(void)close(cf->fd); |
|
if (cf->repo_fd != -1) |
|
(void)close(cf->repo_fd); |
|
xfree(cf); |
} |
} |
|
|
|
|
static int |
static int |
cvs_file_cmpname(const char *name1, const char *name2) |
cvs_file_cmpname(const char *name1, const char *name2) |
{ |
{ |
return (cvs_nocase == 0) ? (strcmp(name1, name2)) : |
return (cvs_nocase == 0) ? (strcmp(name1, name2)) : |
(strcasecmp(name1, name2)); |
(strcasecmp(name1, name2)); |
} |
|
|
|
/* |
|
* remove any empty directories. |
|
*/ |
|
int |
|
cvs_file_prune(char *path) |
|
{ |
|
DIR *dirp; |
|
int l, pwd, empty; |
|
struct dirent *dp; |
|
char fpath[MAXPATHLEN]; |
|
CVSENTRIES *entf; |
|
CVSFILE *cfp; |
|
|
|
pwd = (!strcmp(path, ".")); |
|
|
|
if ((dirp = opendir(path)) == NULL) { |
|
cvs_log(LP_ERRNO, "failed to open `%s'", fpath); |
|
return (-1); |
|
} |
|
|
|
empty = 0; |
|
entf = cvs_ent_open(path, O_RDWR); |
|
|
|
while ((dp = readdir(dirp)) != NULL) { |
|
if (!strcmp(dp->d_name, ".") || |
|
!strcmp(dp->d_name, "..") || |
|
!strcmp(dp->d_name, CVS_PATH_CVSDIR)) |
|
continue; |
|
|
|
empty++; |
|
if (dp->d_type == DT_DIR) { |
|
l = snprintf(fpath, sizeof(fpath), "%s%s%s", |
|
(pwd) ? "" : path, (pwd) ? "" : "/", dp->d_name); |
|
if (l == -1 || l >= (int)sizeof(fpath)) { |
|
errno = ENAMETOOLONG; |
|
cvs_log(LP_ERRNO, "%s", fpath); |
|
continue; |
|
} |
|
|
|
cfp = cvs_file_find(cvs_files, fpath); |
|
if (cfp == NULL) |
|
continue; |
|
|
|
/* ignore unknown directories */ |
|
if (cfp->cf_cvstat == CVS_FST_UNKNOWN) |
|
continue; |
|
|
|
if (cvs_file_prune(fpath)) { |
|
empty--; |
|
if (entf) |
|
cvs_ent_remove(entf, fpath, 0); |
|
} else { |
|
empty++; |
|
} |
|
} |
|
} |
|
|
|
closedir(dirp); |
|
if (entf) |
|
cvs_ent_close(entf); |
|
|
|
empty = (empty == 0); |
|
if (empty) { |
|
if (cvs_rmdir(path) < 0) { |
|
cvs_log(LP_ERR, "failed to prune `%s'", path); |
|
empty = 0; |
|
} |
|
} |
|
|
|
return (empty); |
|
} |
} |