diff --git a/contrib/csup/GNUmakefile b/contrib/csup/GNUmakefile index 441a6b85cab9..7aceccdbd37b 100644 --- a/contrib/csup/GNUmakefile +++ b/contrib/csup/GNUmakefile @@ -12,8 +12,8 @@ GROUP?= 0 UNAME= $(shell uname -s) SRCS= attrstack.c config.c detailer.c diff.c fattr.c fixups.c fnmatch.c \ - globtree.c keyword.c lister.c main.c misc.c mux.c pathcomp.c parse.c \ - proto.c status.c stream.c threads.c token.c updater.c + globtree.c idcache.c keyword.c lister.c main.c misc.c mux.c pathcomp.c \ + parse.c proto.c status.c stream.c threads.c token.c updater.c OBJS= $(SRCS:.c=.o) WARNS= -Wall -W -Wno-unused-parameter -Wmissing-prototypes -Wpointer-arith \ @@ -22,10 +22,10 @@ WARNS= -Wall -W -Wno-unused-parameter -Wmissing-prototypes -Wpointer-arith \ -Wnested-externs -Wredundant-decls -Wno-format-y2k CFLAGS+= -g -O -pipe -DNDEBUG -I$(PREFIX)/include -ifeq ($(UNAME), "Linux") +ifeq ($(UNAME), Linux) CFLAGS+= -D_XOPEN_SOURCE -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 endif -ifeq ($(UNAME), "Darwin") +ifeq ($(UNAME), Darwin) CFLAGS+= -DHAVE_FFLAGS endif CFLAGS+= $(WARNS) diff --git a/contrib/csup/Makefile b/contrib/csup/Makefile index 06f7b92c0321..b672c5ddefcb 100644 --- a/contrib/csup/Makefile +++ b/contrib/csup/Makefile @@ -7,27 +7,9 @@ MANDIR?= ${PREFIX}/man/man UNAME!= /usr/bin/uname -s PROG= csup -SRCS= attrstack.c attrstack.h \ - config.c config.h \ - detailer.c detailer.h \ - diff.c diff.h \ - fattr.c fattr.h fattr_bsd.h \ - fixups.c fixups.h \ - fnmatch.c fnmatch.h \ - globtree.c globtree.h \ - keyword.c keyword.h \ - lister.c lister.h \ - main.c main.h \ - misc.c misc.h \ - mux.c mux.h \ - parse.h parse.y \ - pathcomp.c pathcomp.h \ - proto.c proto.h \ - status.c status.h \ - stream.c stream.h \ - threads.c threads.h \ - token.h token.l \ - updater.c updater.h +SRCS= attrstack.c config.c detailer.c diff.c fattr.c fixups.c fnmatch.c \ + globtree.c idcache.c keyword.c lister.c main.c misc.c mux.c parse.y \ + pathcomp.c proto.c status.c stream.c threads.c token.l updater.c CFLAGS+= -I. -I${.CURDIR} -g -pthread -DHAVE_FFLAGS -DNDEBUG WARNS?= 6 diff --git a/contrib/csup/TODO b/contrib/csup/TODO index 5f67831ff768..66988e1780b0 100644 --- a/contrib/csup/TODO +++ b/contrib/csup/TODO @@ -9,9 +9,6 @@ BUGS: has global variables because of yacc, but I think it should be possible to do it better by using YYFUNC_PROTOTYPE or something. I think it should also be possible to completely get rid of the lex file. -- Some code uses getpwnam() while it should use the thread-safe variant, - getpwnam_r(). Same for getpwuid() and getgrgid(). We probably need a - UID/GID lookup cache here. - The $Log$ CVS keyword is not supported. - Add missing support for supfile keywords and add sanity checks for some of them. Also, we're not supposed to choke on unknown keywords @@ -22,9 +19,9 @@ MISSING FEATURES: - Add support for authentication. - Add support for shell commands sent by the server. -- Add missing support for various CVSup options : -k, -d, -D, - -a (requires authentication support), -e and -E (requires shell - commands support) and the destDir parameter. +- Add missing support for various CVSup options : -D, -a (requires + authentication support), -e and -E (requires shell commands support) + and the destDir parameter. - For now, this code should build fine on FreeBSD, NetBSD, OpenBSD, Linux and Darwin. Solaris support would also be nice at some point. - Implement some new useful options : the ability to generate CVS diff --git a/contrib/csup/config.c b/contrib/csup/config.c index 73be865b7999..5b71e7408522 100644 --- a/contrib/csup/config.c +++ b/contrib/csup/config.c @@ -53,6 +53,8 @@ extern FILE *yyin; static STAILQ_HEAD(, coll) colls; static struct coll *cur_coll; static struct coll *defaults; +static struct coll *ovcoll; +static int ovmask; static const char *cfgfile; /* @@ -78,13 +80,14 @@ config_init(const char *file, struct coll *override, int overridemask) mask = umask(0); umask(mask); defaults->co_umask = mask; + ovcoll = override; + ovmask = overridemask; /* Extract a list of collections from the configuration file. */ cur_coll = coll_new(defaults); yyin = fopen(file, "r"); if (yyin == NULL) { - lprintf(-1, "Cannot open \"%s\": %s\n", file, - strerror(errno)); + lprintf(-1, "Cannot open \"%s\": %s\n", file, strerror(errno)); goto bad; } cfgfile = file; @@ -101,7 +104,6 @@ config_init(const char *file, struct coll *override, int overridemask) /* Fixup the list of collections. */ STAILQ_FOREACH(coll, &config->colls, co_next) { - coll_override(coll, override, overridemask); if (coll->co_base == NULL) coll->co_base = xstrdup("/usr/local/etc/cvsup"); if (coll->co_colldir == NULL) @@ -229,13 +231,27 @@ static int config_parse_refusefile(struct coll *coll, char *path) { struct stream *rd; - char *line; + char *cp, *line, *pat; rd = stream_open_file(path, O_RDONLY); if (rd == NULL) return (0); - while ((line = stream_getln(rd, NULL)) != NULL) - pattlist_add(coll->co_refusals, line); + while ((line = stream_getln(rd, NULL)) != NULL) { + pat = line; + for (;;) { + /* Trim leading whitespace. */ + pat += strspn(pat, " \t"); + if (pat[0] == '\0') + break; + cp = strpbrk(pat, " \t"); + if (cp != NULL) + *cp = '\0'; + pattlist_add(coll->co_refusals, pat); + if (cp == NULL) + break; + pat = cp + 1; + } + } if (!stream_eof(rd)) { stream_close(rd); lprintf(-1, "Read failure from \"%s\": %s\n", path, @@ -417,6 +433,7 @@ coll_add(char *name) struct coll *coll; cur_coll->co_name = name; + coll_override(cur_coll, ovcoll, ovmask); if (cur_coll->co_release == NULL) { lprintf(-1, "Release not specified for collection " "\"%s\"\n", cur_coll->co_name); @@ -472,6 +489,8 @@ coll_free(struct coll *coll) globtree_free(coll->co_dirfilter); if (coll->co_dirfilter != NULL) globtree_free(coll->co_filefilter); + if (coll->co_norsync != NULL) + globtree_free(coll->co_norsync); if (coll->co_accepts != NULL) pattlist_free(coll->co_accepts); if (coll->co_refusals != NULL) @@ -483,6 +502,7 @@ void coll_setopt(int opt, char *value) { struct coll *coll; + int error, mask; coll = cur_coll; switch (opt) { @@ -529,14 +549,14 @@ coll_setopt(int opt, char *value) coll->co_listsuffix = value; break; case PT_UMASK: - errno = 0; - coll->co_umask = strtol(value, NULL, 8); + error = asciitoint(value, &mask, 8); free(value); - if (errno) { + if (error) { lprintf(-1, "Parse error in \"%s\": Invalid " "umask value\n", cfgfile); exit(1); } + coll->co_umask = mask; break; case PT_USE_REL_SUFFIX: coll->co_options |= CO_USERELSUFFIX; @@ -547,6 +567,9 @@ coll_setopt(int opt, char *value) case PT_COMPRESS: coll->co_options |= CO_COMPRESS; break; + case PT_NORSYNC: + coll->co_options |= CO_NORSYNC; + break; } } diff --git a/contrib/csup/config.h b/contrib/csup/config.h index aa12cb604c41..78b699547adb 100644 --- a/contrib/csup/config.h +++ b/contrib/csup/config.h @@ -86,6 +86,7 @@ struct coll { struct pattlist *co_refusals; struct globtree *co_dirfilter; struct globtree *co_filefilter; + struct globtree *co_norsync; const char *co_colldir; char *co_listsuffix; time_t co_scantime; /* Set by the detailer thread. */ @@ -101,6 +102,7 @@ struct config { char *host; struct sockaddr *laddr; socklen_t laddrlen; + int deletelim; int socket; struct chan *chan0; struct chan *chan1; diff --git a/contrib/csup/csup.1 b/contrib/csup/csup.1 index 7a80c01abef0..f8c7c13f135c 100644 --- a/contrib/csup/csup.1 +++ b/contrib/csup/csup.1 @@ -32,10 +32,11 @@ .Nd network distribution package for CVS repositories .Sh SYNOPSIS .Nm -.Op Fl 146svzZ +.Op Fl 146ksvzZ .Op Fl A Ar addr .Op Fl b Ar base .Op Fl c Ar collDir +.Op Fl d Ar delLimit .Op Fl h Ar host .Op Fl i Ar pattern .Op Fl l Ar lockfile @@ -123,6 +124,13 @@ Specifies the subdirectory of where the information about the collections is maintained. The default is .Pa sup . +.It Fl d Ar delLimit +Specifies the maximum number of files that may be deleted in a +single update run. +Any attempt to exceed the limit results in a fatal error. +This can provide some protection against temporary configuration +mistakes on the server. +The default limit is infinity. .It Fl h Ar host Specifies the server host to contact, overriding any .Cm host @@ -148,6 +156,17 @@ is a standard file name pattern. It is interpreted relative to the collection's prefix directory. Slash characters are matched only by explicit slashes in the pattern. Leading periods in file name are not treated specially. +.It Fl k +Causes +.Nm +to keep the temporary copies of any incorrectly edited files, in the +event of checksum mismatches. +This option is for debugging, to help determine why the files were +edited incorrectly. +Regardless of whether this option is specified, the permanent versions +of faulty files are replaced with correct versions obtained by +transferring the files in their entirety. +Such transfers are called fixups. .It Fl l Ar lockfile Creates and locks the .Ar lockfile diff --git a/contrib/csup/fattr.c b/contrib/csup/fattr.c index 59b66077c628..b29d73dcf64e 100644 --- a/contrib/csup/fattr.c +++ b/contrib/csup/fattr.c @@ -32,14 +32,13 @@ #include #include -#include -#include #include #include #include #include #include "fattr.h" +#include "idcache.h" #include "misc.h" /* @@ -126,6 +125,8 @@ fattr_init(void) fa->mask |= FA_MODE; defaults[i] = fa; } + /* Initialize the uid/gid lookup cache. */ + idcache_init(); } void @@ -133,6 +134,7 @@ fattr_fini(void) { int i; + idcache_fini(); for (i = 0; i < FT_NUMBER; i++) fattr_free(defaults[i]); } @@ -336,29 +338,26 @@ fattr_encode(const struct fattr *fa, fattr_support_t support, int ignore) int extval; char *ext; } pieces[FA_NUMBER], *piece; - struct passwd *pw; - struct group *gr; - char *cp, *s; + char *cp, *s, *username, *groupname; size_t len, vallen; mode_t mode, modemask; int mask, n, i; - pw = NULL; - gr = NULL; + username = NULL; + groupname = NULL; if (support == NULL) mask = fa->mask; else mask = fa->mask & support[fa->type]; mask &= ~ignore; - /* XXX - Use getpwuid_r() and getgrgid_r(). */ if (fa->mask & FA_OWNER) { - pw = getpwuid(fa->uid); - if (pw == NULL) + username = getuserbyid(fa->uid); + if (username == NULL) mask &= ~FA_OWNER; } if (fa->mask & FA_GROUP) { - gr = getgrgid(fa->gid); - if (gr == NULL) + groupname = getgroupbyid(fa->gid); + if (groupname == NULL) mask &= ~FA_GROUP; } if (fa->mask & FA_LINKCOUNT && fa->linkcount == 1) @@ -408,17 +407,17 @@ fattr_encode(const struct fattr *fa, fattr_support_t support, int ignore) piece++; } if (mask & FA_OWNER) { - vallen = strlen(pw->pw_name); + vallen = strlen(username); piece->extval = 1; - piece->ext = pw->pw_name; + piece->ext = username; len += snprintf(piece->len, sizeof(piece->len), "%lld", (long long)vallen) + vallen + 1; piece++; } if (mask & FA_GROUP) { - vallen = strlen(gr->gr_name); + vallen = strlen(groupname); piece->extval = 1; - piece->ext = gr->gr_name; + piece->ext = groupname; len += snprintf(piece->len, sizeof(piece->len), "%lld", (long long)vallen) + vallen + 1; piece++; @@ -551,11 +550,10 @@ fattr_getlinkcount(const struct fattr *fa) static char * fattr_scanattr(struct fattr *fa, int type, const char *attr) { - struct passwd *pw; - struct group *gr; char *attrend, *attrstart, *end; size_t len; unsigned long attrlen; + int error; mode_t modemask; char tmp; @@ -606,21 +604,13 @@ fattr_scanattr(struct fattr *fa, int type, const char *attr) goto bad; break; case FA_OWNER: - /* - * XXX - We need to use getpwnam_r() since getpwnam() - * is not thread-safe, and we also need to use a cache. - */ - pw = getpwnam(attrstart); - if (pw != NULL) - fa->uid = pw->pw_uid; - else + error = getuidbyname(attrstart, &fa->uid); + if (error) fa->mask &= ~FA_OWNER; break; case FA_GROUP: - gr = getgrnam(attrstart); - if (gr != NULL) - fa->gid = gr->gr_gid; - else + error = getgidbyname(attrstart, &fa->gid); + if (error) fa->mask &= ~FA_GROUP; break; case FA_MODE: @@ -770,11 +760,13 @@ fattr_delete(const char *path) return (-1); } +#ifdef HAVE_FFLAGS /* Clear flags. */ if (fa->mask & FA_FLAGS && fa->flags != 0) { fa->flags = 0; (void)chflags(path, fa->flags); } +#endif if (fa->type == FT_DIRECTORY) error = rmdir(path); @@ -812,35 +804,35 @@ fattr_install(struct fattr *fa, const char *topath, const char *frompath) inplace = 1; } old = fattr_frompath(topath, FATTR_NOFOLLOW); - if (old == NULL) - return (-1); - if (inplace && fattr_equal(fa, old)) { - fattr_free(old); - return (0); - } + if (old != NULL) { + if (inplace && fattr_equal(fa, old)) { + fattr_free(old); + return (0); + } #ifdef HAVE_FFLAGS - /* - * Determine whether we need to clear the flags of the target. - * This is bogus in that it assumes a value of 0 is safe and - * that non-zero is unsafe. I'm not really worried by that - * since as far as I know that's the way things are. - */ - if ((old->mask & FA_FLAGS) && old->flags > 0) { - (void)chflags(topath, 0); - old->flags = 0; - } + /* + * Determine whether we need to clear the flags of the target. + * This is bogus in that it assumes a value of 0 is safe and + * that non-zero is unsafe. I'm not really worried by that + * since as far as I know that's the way things are. + */ + if ((old->mask & FA_FLAGS) && old->flags > 0) { + (void)chflags(topath, 0); + old->flags = 0; + } #endif - /* Determine whether we need to remove the target first. */ - if (!inplace && (fa->type == FT_DIRECTORY) != - (old->type == FT_DIRECTORY)) { - if (old->type == FT_DIRECTORY) - error = rmdir(topath); - else - error = unlink(topath); - if (error) - goto bad; + /* Determine whether we need to remove the target first. */ + if (!inplace && (fa->type == FT_DIRECTORY) != + (old->type == FT_DIRECTORY)) { + if (old->type == FT_DIRECTORY) + error = rmdir(topath); + else + error = unlink(topath); + if (error) + goto bad; + } } /* Change those attributes that we can before moving the file @@ -866,8 +858,8 @@ fattr_install(struct fattr *fa, const char *topath, const char *frompath) } if (mask & FA_MODE) { newmode = fa->mode & modemask; - /* Merge in set*id bits from the old attribute. XXX - Why? */ - if (old->mask & FA_MODE) { + /* Merge in set*id bits from the old attribute. */ + if (old != NULL && old->mask & FA_MODE) { newmode |= (old->mode & ~modemask); newmode &= (FA_SETIDMASK | FA_PERMMASK); } diff --git a/contrib/csup/globtree.c b/contrib/csup/globtree.c index 520931afa136..74ac2c162ab1 100644 --- a/contrib/csup/globtree.c +++ b/contrib/csup/globtree.c @@ -29,10 +29,10 @@ #include #include -#include #include #include +#include "fnmatch.h" #include "globtree.h" #include "misc.h" diff --git a/contrib/csup/idcache.c b/contrib/csup/idcache.c new file mode 100644 index 000000000000..47a3e712c147 --- /dev/null +++ b/contrib/csup/idcache.c @@ -0,0 +1,421 @@ +/*- + * Copyright (c) 2006, Maxime Henrion + * 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. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "idcache.h" +#include "misc.h" + +/* + * Constants and data structures used to implement the thread-safe + * group and password file caches. Cache sizes must be prime. + */ +#define UIDTONAME_SZ 317 /* Size of uid -> user name cache */ +#define NAMETOUID_SZ 317 /* Size of user name -> uid cache */ +#define GIDTONAME_SZ 317 /* Size of gid -> group name cache */ +#define NAMETOGID_SZ 317 /* Size of group name -> gid cache */ + +/* Node structures used to cache lookups. */ +struct uidc { + char *name; /* user name */ + uid_t uid; /* cached uid */ + int valid; /* is this a valid or a miss entry */ + struct uidc *next; /* for collisions */ +}; + +struct gidc { + char *name; /* group name */ + gid_t gid; /* cached gid */ + int valid; /* is this a valid or a miss entry */ + struct gidc *next; /* for collisions */ +}; + +static struct uidc **uidtoname; /* uid to user name cache */ +static struct gidc **gidtoname; /* gid to group name cache */ +static struct uidc **nametouid; /* user name to uid cache */ +static struct gidc **nametogid; /* group name to gid cache */ + +static pthread_mutex_t uid_mtx; +static pthread_mutex_t gid_mtx; + +static void uid_lock(void); +static void uid_unlock(void); +static void gid_lock(void); +static void gid_unlock(void); + +static uint32_t hash(const char *); + +/* A 32-bit version of Peter Weinberger's (PJW) hash algorithm, + as used by ELF for hashing function names. */ +static uint32_t +hash(const char *name) +{ + uint32_t g, h; + + h = 0; + while(*name != '\0') { + h = (h << 4) + *name++; + if ((g = h & 0xF0000000)) { + h ^= g >> 24; + h &= 0x0FFFFFFF; + } + } + return (h); +} + +static void +uid_lock(void) +{ + int error; + + error = pthread_mutex_lock(&uid_mtx); + assert(!error); +} + +static void +uid_unlock(void) +{ + int error; + + error = pthread_mutex_unlock(&uid_mtx); + assert(!error); +} + +static void +gid_lock(void) +{ + int error; + + error = pthread_mutex_lock(&gid_mtx); + assert(!error); +} + +static void +gid_unlock(void) +{ + int error; + + error = pthread_mutex_unlock(&gid_mtx); + assert(!error); +} + +static void +uidc_insert(struct uidc **tbl, struct uidc *uidc, uint32_t key) +{ + + uidc->next = tbl[key]; + tbl[key] = uidc; +} + +static void +gidc_insert(struct gidc **tbl, struct gidc *gidc, uint32_t key) +{ + + gidc->next = tbl[key]; + tbl[key] = gidc; +} + +/* Return the user name for this uid, or NULL if it's not found. */ +char * +getuserbyid(uid_t uid) +{ + struct passwd *pw; + struct uidc *uidc, *uidc2; + uint32_t key, key2; + + key = uid % UIDTONAME_SZ; + uid_lock(); + uidc = uidtoname[key]; + while (uidc != NULL) { + if (uidc->uid == uid) + break; + uidc = uidc->next; + } + + if (uidc == NULL) { + /* We didn't find this uid, look it up and add it. */ + uidc = xmalloc(sizeof(struct uidc)); + uidc->uid = uid; + pw = getpwuid(uid); + if (pw != NULL) { + /* This uid is in the password file. */ + uidc->name = xstrdup(pw->pw_name); + uidc->valid = 1; + /* Also add it to the name -> gid table. */ + uidc2 = xmalloc(sizeof(struct uidc)); + uidc2->uid = uid; + uidc2->name = uidc->name; /* We reuse the pointer. */ + uidc2->valid = 1; + key2 = hash(uidc->name) % NAMETOUID_SZ; + uidc_insert(nametouid, uidc2, key2); + } else { + /* Add a miss entry for this uid. */ + uidc->name = NULL; + uidc->valid = 0; + } + uidc_insert(uidtoname, uidc, key); + } + /* It is safe to unlock here since the cache structure + is not going to get freed or changed. */ + uid_unlock(); + return (uidc->name); +} + +/* Return the group name for this gid, or NULL if it's not found. */ +char * +getgroupbyid(gid_t gid) +{ + struct group *gr; + struct gidc *gidc, *gidc2; + uint32_t key, key2; + + key = gid % GIDTONAME_SZ; + gid_lock(); + gidc = gidtoname[key]; + while (gidc != NULL) { + if (gidc->gid == gid) + break; + gidc = gidc->next; + } + + if (gidc == NULL) { + /* We didn't find this gid, look it up and add it. */ + gidc = xmalloc(sizeof(struct gidc)); + gidc->gid = gid; + gr = getgrgid(gid); + if (gr != NULL) { + /* This gid is in the group file. */ + gidc->name = xstrdup(gr->gr_name); + gidc->valid = 1; + /* Also add it to the name -> gid table. */ + gidc2 = xmalloc(sizeof(struct gidc)); + gidc2->gid = gid; + gidc2->name = gidc->name; /* We reuse the pointer. */ + gidc2->valid = 1; + key2 = hash(gidc->name) % NAMETOGID_SZ; + gidc_insert(nametogid, gidc2, key2); + } else { + /* Add a miss entry for this gid. */ + gidc->name = NULL; + gidc->valid = 0; + } + gidc_insert(gidtoname, gidc, key); + } + /* It is safe to unlock here since the cache structure + is not going to get freed or changed. */ + gid_unlock(); + return (gidc->name); +} + +/* Finds the uid for this user name. If it's found, the gid is stored + in *uid and 0 is returned. Otherwise, -1 is returned. */ +int +getuidbyname(const char *name, uid_t *uid) +{ + struct passwd *pw; + struct uidc *uidc, *uidc2; + uint32_t key, key2; + + uid_lock(); + key = hash(name) % NAMETOUID_SZ; + uidc = nametouid[key]; + while (uidc != NULL) { + if (strcmp(uidc->name, name) == 0) + break; + uidc = uidc->next; + } + + if (uidc == NULL) { + uidc = xmalloc(sizeof(struct uidc)); + uidc->name = xstrdup(name); + pw = getpwnam(name); + if (pw != NULL) { + /* This user name is in the password file. */ + uidc->valid = 1; + uidc->uid = pw->pw_uid; + /* Also add it to the uid -> name table. */ + uidc2 = xmalloc(sizeof(struct uidc)); + uidc2->name = uidc->name; /* We reuse the pointer. */ + uidc2->uid = uidc->uid; + uidc2->valid = 1; + key2 = uidc2->uid % UIDTONAME_SZ; + uidc_insert(uidtoname, uidc2, key2); + } else { + /* Add a miss entry for this user name. */ + uidc->valid = 0; + uidc->uid = (uid_t)-1; /* Should not be accessed. */ + } + uidc_insert(nametouid, uidc, key); + } + /* It is safe to unlock here since the cache structure + is not going to get freed or changed. */ + uid_unlock(); + if (!uidc->valid) + return (-1); + *uid = uidc->uid; + return (0); +} + +/* Finds the gid for this group name. If it's found, the gid is stored + in *gid and 0 is returned. Otherwise, -1 is returned. */ +int +getgidbyname(const char *name, gid_t *gid) +{ + struct group *gr; + struct gidc *gidc, *gidc2; + uint32_t key, key2; + + gid_lock(); + key = hash(name) % NAMETOGID_SZ; + gidc = nametogid[key]; + while (gidc != NULL) { + if (strcmp(gidc->name, name) == 0) + break; + gidc = gidc->next; + } + + if (gidc == NULL) { + gidc = xmalloc(sizeof(struct gidc)); + gidc->name = xstrdup(name); + gr = getgrnam(name); + if (gr != NULL) { + /* This group name is in the group file. */ + gidc->gid = gr->gr_gid; + gidc->valid = 1; + /* Also add it to the gid -> name table. */ + gidc2 = xmalloc(sizeof(struct gidc)); + gidc2->name = gidc->name; /* We reuse the pointer. */ + gidc2->gid = gidc->gid; + gidc2->valid = 1; + key2 = gidc2->gid % GIDTONAME_SZ; + gidc_insert(gidtoname, gidc2, key2); + } else { + /* Add a miss entry for this group name. */ + gidc->gid = (gid_t)-1; /* Should not be accessed. */ + gidc->valid = 0; + } + gidc_insert(nametogid, gidc, key); + } + /* It is safe to unlock here since the cache structure + is not going to get freed or changed. */ + gid_unlock(); + if (!gidc->valid) + return (-1); + *gid = gidc->gid; + return (0); +} + +/* Initialize the cache structures. */ +void +idcache_init(void) +{ + + pthread_mutex_init(&uid_mtx, NULL); + pthread_mutex_init(&gid_mtx, NULL); + uidtoname = xmalloc(UIDTONAME_SZ * sizeof(struct uidc *)); + gidtoname = xmalloc(GIDTONAME_SZ * sizeof(struct gidc *)); + nametouid = xmalloc(NAMETOUID_SZ * sizeof(struct uidc *)); + nametogid = xmalloc(NAMETOGID_SZ * sizeof(struct gidc *)); + memset(uidtoname, 0, UIDTONAME_SZ * sizeof(struct uidc *)); + memset(gidtoname, 0, GIDTONAME_SZ * sizeof(struct gidc *)); + memset(nametouid, 0, NAMETOUID_SZ * sizeof(struct uidc *)); + memset(nametogid, 0, NAMETOGID_SZ * sizeof(struct gidc *)); +} + +/* Cleanup the cache structures. */ +void +idcache_fini(void) +{ + struct uidc *uidc, *uidc2; + struct gidc *gidc, *gidc2; + size_t i; + + for (i = 0; i < UIDTONAME_SZ; i++) { + uidc = uidtoname[i]; + while (uidc != NULL) { + if (uidc->name != NULL) { + assert(uidc->valid); + free(uidc->name); + } + uidc2 = uidc->next; + free(uidc); + uidc = uidc2; + } + } + free(uidtoname); + for (i = 0; i < NAMETOUID_SZ; i++) { + uidc = nametouid[i]; + while (uidc != NULL) { + assert(uidc->name != NULL); + /* If it's a valid entry, it has been added to both the + uidtoname and nametouid tables, and the name pointer + has been reused for both entries. Thus, the name + pointer has already been freed in the loop above. */ + if (!uidc->valid) + free(uidc->name); + uidc2 = uidc->next; + free(uidc); + uidc = uidc2; + } + } + free(nametouid); + for (i = 0; i < GIDTONAME_SZ; i++) { + gidc = gidtoname[i]; + while (gidc != NULL) { + if (gidc->name != NULL) { + assert(gidc->valid); + free(gidc->name); + } + gidc2 = gidc->next; + free(gidc); + gidc = gidc2; + } + } + free(gidtoname); + for (i = 0; i < NAMETOGID_SZ; i++) { + gidc = nametogid[i]; + while (gidc != NULL) { + assert(gidc->name != NULL); + /* See above comment. */ + if (!gidc->valid) + free(gidc->name); + gidc2 = gidc->next; + free(gidc); + gidc = gidc2; + } + } + free(nametogid); + pthread_mutex_destroy(&uid_mtx); + pthread_mutex_destroy(&gid_mtx); +} diff --git a/contrib/csup/idcache.h b/contrib/csup/idcache.h new file mode 100644 index 000000000000..558af0ce115f --- /dev/null +++ b/contrib/csup/idcache.h @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2006, Maxime Henrion + * 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. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ +#ifndef _IDCACHE_H_ +#define _IDCACHE_H_ + +#include + +void idcache_init(void); +void idcache_fini(void); + +char *getuserbyid(uid_t); +char *getgroupbyid(gid_t); +int getuidbyname(const char *, uid_t *); +int getgidbyname(const char *, gid_t *); + +#endif /* !_IDCACHE_H_ */ diff --git a/contrib/csup/lister.c b/contrib/csup/lister.c index a048d25d888b..98a9c8368d85 100644 --- a/contrib/csup/lister.c +++ b/contrib/csup/lister.c @@ -145,6 +145,7 @@ lister_coll(struct lister *l, struct coll *coll, struct status *st) struct attrstack *as; struct statusrec *sr; struct fattr *fa; + size_t i; int depth, error, ret, prunedepth; wr = l->wr; @@ -203,7 +204,7 @@ lister_coll(struct lister *l, struct coll *coll, struct status *st) return (LISTER_ERR_WRITE); return (0); bad: - while (depth-- > 0) { + for (i = 0; i < attrstack_size(as); i++) { fa = attrstack_pop(as); fattr_free(fa); } diff --git a/contrib/csup/main.c b/contrib/csup/main.c index 33f591510270..74150393bc0b 100644 --- a/contrib/csup/main.c +++ b/contrib/csup/main.c @@ -66,6 +66,8 @@ usage(char *argv0) "Override supfile's \"base\" directory"); lprintf(-1, USAGE_OPTFMT, "-c collDir", "Subdirectory of \"base\" for collections (default \"sup\")"); + lprintf(-1, USAGE_OPTFMT, "-d delLimit", + "Allow at most \"delLimit\" file deletions (default unlimited)"); lprintf(-1, USAGE_OPTFMT, "-h host", "Override supfile's \"host\" name"); lprintf(-1, USAGE_OPTFMT, "-i pattern", @@ -73,6 +75,8 @@ usage(char *argv0) lprintf(-1, USAGE_OPTFMTSUB, "May be repeated for an OR operation. Default is"); lprintf(-1, USAGE_OPTFMTSUB, "to include each entire collection."); + lprintf(-1, USAGE_OPTFMT, "-k", + "Keep bad temporary files when fixups are required"); lprintf(-1, USAGE_OPTFMT, "-l lockfile", "Lock file during update; fail if already locked"); lprintf(-1, USAGE_OPTFMT, "-L n", @@ -102,13 +106,13 @@ main(int argc, char *argv[]) socklen_t laddrlen; struct stream *lock; char *argv0, *file, *lockfile; - uint16_t port; int family, error, lockfd, lflag, overridemask; - int c, i, retries, status; + int c, i, deletelim, port, retries, status; time_t nexttry; error = 0; family = PF_UNSPEC; + deletelim = -1; port = 0; lflag = 0; lockfd = 0; @@ -121,7 +125,8 @@ main(int argc, char *argv[]) override = coll_new(NULL); overridemask = 0; - while ((c = getopt(argc, argv, "146A:b:c:gh:i:l:L:p:P:r:svzZ")) != -1) { + while ((c = getopt(argc, argv, + "146A:b:c:d:gh:i:kl:L:p:P:r:svzZ")) != -1) { switch (c) { case '1': retries = 0; @@ -152,6 +157,14 @@ main(int argc, char *argv[]) case 'c': override->co_colldir = optarg; break; + case 'd': + error = asciitoint(optarg, &deletelim, 0); + if (error || deletelim < 0) { + lprintf(-1, "Invalid deletion limit\n"); + usage(argv0); + return (1); + } + break; case 'g': /* For compatibility. */ break; @@ -163,6 +176,10 @@ main(int argc, char *argv[]) case 'i': pattlist_add(override->co_accepts, optarg); break; + case 'k': + override->co_options |= CO_KEEPBADFILES; + overridemask |= CO_KEEPBADFILES; + break; case 'l': lockfile = optarg; lflag = 1; @@ -191,9 +208,8 @@ main(int argc, char *argv[]) stream_close(lock); break; case 'L': - errno = 0; - verbose = strtol(optarg, NULL, 0); - if (errno == EINVAL) { + error = asciitoint(optarg, &verbose, 0); + if (error) { lprintf(-1, "Invalid verbosity\n"); usage(argv0); return (1); @@ -201,13 +217,21 @@ main(int argc, char *argv[]) break; case 'p': /* Use specified server port. */ - errno = 0; - port = strtol(optarg, NULL, 0); - if (errno == EINVAL) { + error = asciitoint(optarg, &port, 0); + if (error) { lprintf(-1, "Invalid server port\n"); usage(argv0); return (1); } + if (port <= 0 || port >= 65536) { + lprintf(-1, "Invalid port %d\n", port); + return (1); + } + if (port < 1024) { + lprintf(-1, "Reserved port %d not permitted\n", + port); + return (1); + } break; case 'P': /* For compatibility. */ @@ -218,9 +242,8 @@ main(int argc, char *argv[]) } break; case 'r': - errno = 0; - retries = strtol(optarg, NULL, 0); - if (errno == EINVAL || retries < 0) { + error = asciitoint(optarg, &retries, 0); + if (error || retries < 0) { lprintf(-1, "Invalid retry limit\n"); usage(argv0); return (1); @@ -270,15 +293,16 @@ main(int argc, char *argv[]) if (config == NULL) return (1); - if (laddr != NULL) { - config->laddr = laddr; - config->laddrlen = laddrlen; - } if (config_checkcolls(config) == 0) { lprintf(-1, "No collections selected\n"); return (1); } + if (laddr != NULL) { + config->laddr = laddr; + config->laddrlen = laddrlen; + } + config->deletelim = deletelim; lprintf(2, "Connecting to %s\n", config->host); i = 0; diff --git a/contrib/csup/misc.c b/contrib/csup/misc.c index b97a54288036..97a02aba8368 100644 --- a/contrib/csup/misc.c +++ b/contrib/csup/misc.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,24 @@ struct backoff_timer { static void bt_update(struct backoff_timer *); static void bt_addjitter(struct backoff_timer *); +int +asciitoint(const char *s, int *val, int base) +{ + char *end; + long longval; + + errno = 0; + longval = strtol(s, &end, base); + if (errno || *end != '\0') + return (-1); + if (longval > INT_MAX || longval < INT_MIN) { + errno = ERANGE; + return (-1); + } + *val = longval; + return (0); +} + int lprintf(int level, const char *fmt, ...) { diff --git a/contrib/csup/misc.h b/contrib/csup/misc.h index 47f976750d2d..f0d03520d871 100644 --- a/contrib/csup/misc.h +++ b/contrib/csup/misc.h @@ -99,6 +99,7 @@ struct backoff_timer; struct pattlist; struct tm; +int asciitoint(const char *, int *, int); int lprintf(int, const char *, ...) __printflike(2, 3); int MD5_File(char *, char *); void MD5_End(char *, MD5_CTX *); @@ -125,4 +126,5 @@ struct backoff_timer *bt_new(time_t, time_t, float, float); time_t bt_get(struct backoff_timer *); void bt_pause(struct backoff_timer *); void bt_free(struct backoff_timer *); + #endif /* !_MISC_H_ */ diff --git a/contrib/csup/proto.c b/contrib/csup/proto.c index 04f5495095e2..0a8985856baa 100644 --- a/contrib/csup/proto.c +++ b/contrib/csup/proto.c @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -228,7 +227,7 @@ proto_negproto(struct config *config) stream_flush(s); line = stream_getln(s, NULL); cmd = proto_get_ascii(&line); - if (line == NULL) + if (cmd == NULL || line == NULL) goto bad; if (strcmp(cmd, "!") == 0) { msg = proto_get_rest(&line); @@ -256,13 +255,20 @@ static int proto_login(struct config *config) { struct stream *s; - char host[MAXHOSTNAMELEN]; - char *line, *cmd, *realm, *challenge, *msg; + char hostbuf[MAXHOSTNAMELEN]; + char *line, *login, *host, *cmd, *realm, *challenge, *msg; + int error; s = config->server; - gethostname(host, sizeof(host)); - host[sizeof(host) - 1] = '\0'; - proto_printf(s, "USER %s %s\n", getlogin(), host); + error = gethostname(hostbuf, sizeof(hostbuf)); + hostbuf[sizeof(hostbuf) - 1] = '\0'; + if (error) + host = NULL; + else + host = hostbuf; + login = getlogin(); + proto_printf(s, "USER %s %s\n", login != NULL ? login : "?", + host != NULL ? host : "?"); stream_flush(s); line = stream_getln(s, NULL); cmd = proto_get_ascii(&line); @@ -279,6 +285,8 @@ proto_login(struct config *config) stream_flush(s); line = stream_getln(s, NULL); cmd = proto_get_ascii(&line); + if (cmd == NULL || line == NULL) + goto bad; if (strcmp(cmd, "OK") == 0) return (STATUS_SUCCESS); if (strcmp(cmd, "!") == 0) { @@ -371,9 +379,11 @@ proto_xchgcoll(struct config *config) } proto_printf(s, ".\n"); stream_flush(s); + STAILQ_FOREACH(coll, &config->colls, co_next) { if (coll->co_options & CO_SKIP) continue; + coll->co_norsync = globtree_false(); line = stream_getln(s, NULL); if (line == NULL) goto bad; @@ -430,7 +440,21 @@ proto_xchgcoll(struct config *config) ident); if (error) goto bad; - } + } else if (strcmp(cmd, "NORS") == 0) { + pat = proto_get_ascii(&line); + if (pat == NULL || line != NULL) + goto bad; + coll->co_norsync = globtree_or(coll->co_norsync, + globtree_match(pat, FNM_PATHNAME)); + } else if (strcmp(cmd, "RNORS") == 0) { + pat = proto_get_ascii(&line); + if (pat == NULL || line != NULL) + goto bad; + coll->co_norsync = globtree_or(coll->co_norsync, + globtree_match(pat, FNM_PATHNAME | + FNM_LEADING_DIR)); + } else + goto bad; } if (line == NULL) goto bad; @@ -904,22 +928,14 @@ proto_get_rest(char **s) int proto_get_int(char **s, int *val, int base) { - char *cp, *end; - long longval; + char *cp; + int error; cp = proto_get_ascii(s); if (cp == NULL) return (-1); - errno = 0; - longval = strtol(cp, &end, base); - if (errno || *end != '\0') - return (-1); - if (longval > INT_MAX || longval < INT_MIN) { - errno = ERANGE; - return (-1); - } - *val = longval; - return (0); + error = asciitoint(cp, val, base); + return (error); } /* diff --git a/contrib/csup/token.h b/contrib/csup/token.h index 45d4ed780148..0dc3ec03fbef 100644 --- a/contrib/csup/token.h +++ b/contrib/csup/token.h @@ -44,5 +44,6 @@ int yyparse(void); #define PT_DELETE 8 #define PT_USE_REL_SUFFIX 9 #define PT_LIST 10 +#define PT_NORSYNC 11 #endif /* !_TOKEN_H_ */ diff --git a/contrib/csup/token.l b/contrib/csup/token.l index 0def82dedd18..267e61feff89 100644 --- a/contrib/csup/token.l +++ b/contrib/csup/token.l @@ -56,6 +56,7 @@ release { yylval.i = PT_RELEASE; return NAME; } tag { yylval.i = PT_TAG; return NAME; } umask { yylval.i = PT_UMASK; return NAME; } list { yylval.i = PT_LIST; return NAME; } +norsync { yylval.i = PT_NORSYNC; return NAME; } = { return EQUAL; } compress { yylval.i = PT_COMPRESS; return BOOLEAN; } delete { yylval.i = PT_DELETE; return BOOLEAN; } diff --git a/contrib/csup/updater.c b/contrib/csup/updater.c index 235bfcaf7b52..df74d5296365 100644 --- a/contrib/csup/updater.c +++ b/contrib/csup/updater.c @@ -54,11 +54,13 @@ #define UPDATER_ERR_PROTO (-1) /* Protocol error. */ #define UPDATER_ERR_MSG (-2) /* Error is in updater->errmsg. */ #define UPDATER_ERR_READ (-3) /* Error reading from server. */ +#define UPDATER_ERR_DELETELIM (-4) /* File deletion limit exceeded. */ /* Everything needed to update a file. */ struct file_update { struct statusrec srbuf; char *destpath; + char *temppath; char *coname; /* Points somewhere in destpath. */ char *wantmd5; struct coll *coll; @@ -74,6 +76,7 @@ struct updater { struct config *config; struct stream *rd; char *errmsg; + int deletecount; }; static struct file_update *fup_new(struct coll *, struct status *); @@ -84,14 +87,13 @@ static void fup_free(struct file_update *); static void updater_prunedirs(char *, char *); static int updater_batch(struct updater *, int); static int updater_docoll(struct updater *, struct file_update *, int); -static void updater_delete(struct file_update *); +static int updater_delete(struct updater *, struct file_update *); +static void updater_deletefile(const char *); static int updater_checkout(struct updater *, struct file_update *, int); static int updater_setattrs(struct updater *, struct file_update *, char *, char *, char *, char *, char *, struct fattr *); -static void updater_checkmd5(struct updater *, struct file_update *, - const char *, int); static int updater_updatefile(struct updater *, struct file_update *fup, - const char *, const char *); + const char *, int); static int updater_diff(struct updater *, struct file_update *); static int updater_diff_batch(struct updater *, struct file_update *); static int updater_diff_apply(struct updater *, struct file_update *, @@ -134,6 +136,10 @@ fup_cleanup(struct file_update *fup) free(fup->destpath); fup->destpath = NULL; } + if (fup->temppath != NULL) { + free(fup->temppath); + fup->temppath = NULL; + } fup->coname = NULL; if (fup->author != NULL) { free(fup->author); @@ -188,6 +194,7 @@ updater(void *arg) up->config = args->config; up->rd = args->rd; up->errmsg = NULL; + up->deletecount = 0; error = updater_batch(up, 0); @@ -218,6 +225,11 @@ updater(void *arg) } args->status = STATUS_TRANSIENTFAILURE; break; + case UPDATER_ERR_DELETELIM: + xasprintf(&args->errmsg, "Updater failed: " + "File deletion limit exceeded"); + args->status = STATUS_FAILURE; + break; default: assert(error == 0); args->status = STATUS_SUCCESS; @@ -261,12 +273,11 @@ updater_batch(struct updater *up, int isfixups) stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); st = status_open(coll, coll->co_scantime, &errmsg); - fup = fup_new(coll, st); if (st == NULL) { - fup_free(fup); up->errmsg = errmsg; return (UPDATER_ERR_MSG); } + fup = fup_new(coll, st); error = updater_docoll(up, fup, isfixups); status_close(st, &errmsg); fup_free(fup); @@ -360,8 +371,11 @@ updater_docoll(struct updater *up, struct file_update *fup, int isfixups) /* Theoritically, the file does not exist on the client. Just to make sure, we'll delete it here, if it exists. */ - if (access(fup->destpath, F_OK) == 0) - updater_delete(fup); + if (access(fup->destpath, F_OK) == 0) { + error = updater_delete(up, fup); + if (error) + return (error); + } sr = &srbuf; sr->sr_type = SR_CHECKOUTDEAD; @@ -410,6 +424,7 @@ updater_docoll(struct updater *up, struct file_update *fup, int isfixups) return (UPDATER_ERR_PROTO); fup->wantmd5 = xstrdup(wantmd5); + fup->temppath = tempname(fup->destpath); error = updater_diff(up, fup); if (error) return (error); @@ -426,7 +441,9 @@ updater_docoll(struct updater *up, struct file_update *fup, int isfixups) error = fup_prepare(fup, name); if (error) return (UPDATER_ERR_PROTO); - updater_delete(fup); + error = updater_delete(up, fup); + if (error) + return (error); sr = &srbuf; sr->sr_type = SR_CHECKOUTDEAD; sr->sr_file = name; @@ -478,6 +495,7 @@ updater_docoll(struct updater *up, struct file_update *fup, int isfixups) error = fup_prepare(fup, name); if (error) return (UPDATER_ERR_PROTO); + fup->temppath = tempname(fup->destpath); if (*cmd == 'Y') error = updater_checkout(up, fup, 1); else @@ -493,7 +511,9 @@ updater_docoll(struct updater *up, struct file_update *fup, int isfixups) error = fup_prepare(fup, name); if (error) return (UPDATER_ERR_PROTO); - updater_delete(fup); + error = updater_delete(up, fup); + if (error) + return (error); error = status_delete(fup->st, name, 0); if (error) { up->errmsg = status_errmsg(fup->st); @@ -518,27 +538,39 @@ updater_docoll(struct updater *up, struct file_update *fup, int isfixups) } /* Delete file. */ -static void -updater_delete(struct file_update *fup) +static int +updater_delete(struct updater *up, struct file_update *fup) { + struct config *config; struct coll *coll; - int error; - /* XXX - delete limit handling */ + config = up->config; coll = fup->coll; if (coll->co_options & CO_DELETE) { lprintf(1, " Delete %s\n", fup->coname); - error = fattr_delete(fup->destpath); - if (error) { - lprintf(-1, "Cannot delete \"%s\": %s\n", - fup->destpath, strerror(errno)); - return; - } + if (config->deletelim >= 0 && + up->deletecount >= config->deletelim) + return (UPDATER_ERR_DELETELIM); + up->deletecount++; + updater_deletefile(fup->destpath); if (coll->co_options & CO_CHECKOUTMODE) updater_prunedirs(coll->co_prefix, fup->destpath); } else { lprintf(1," NoDelete %s\n", fup->coname); } + return (0); +} + +static void +updater_deletefile(const char *path) +{ + int error; + + error = fattr_delete(path); + if (error && errno != ENOENT) { + lprintf(-1, "Cannot delete \"%s\": %s\n", + path, strerror(errno)); + } } static int @@ -613,34 +645,9 @@ updater_setattrs(struct updater *up, struct file_update *fup, char *name, return (0); } -/* - * Check that the file we created/updated has a correct MD5 checksum. - * If it doesn't and that this is not a fixup update, add a fixup - * request to checkout the whole file. If it's already a fixup update, - * we just fail. - */ -static void -updater_checkmd5(struct updater *up, struct file_update *fup, const char *md5, - int isfixup) -{ - struct statusrec *sr; - - sr = &fup->srbuf; - if (strcmp(fup->wantmd5, md5) == 0) - return; - if (isfixup) { - lprintf(-1, "%s: Checksum mismatch -- file not updated\n", - fup->destpath); - return; - } - lprintf(-1, "%s: Checksum mismatch -- will transfer entire file\n", - fup->destpath); - fixups_put(up->config->fixups, fup->coll, sr->sr_file); -} - static int -updater_updatefile(struct updater *up, struct file_update *fup, const char *to, - const char *from) +updater_updatefile(struct updater *up, struct file_update *fup, + const char *md5, int isfixup) { struct coll *coll; struct status *st; @@ -652,16 +659,27 @@ updater_updatefile(struct updater *up, struct file_update *fup, const char *to, sr = &fup->srbuf; st = fup->st; - fattr_umask(sr->sr_clientattr, coll->co_umask); - rv = fattr_install(sr->sr_clientattr, to, from); - if (rv == -1) { - if (from == NULL) - xasprintf(&up->errmsg, "Cannot install \"%s\": %s", - to, strerror(errno)); + if (strcmp(fup->wantmd5, md5) != 0) { + if (isfixup) { + lprintf(-1, "%s: Checksum mismatch -- " + "file not updated\n", fup->destpath); + } else { + lprintf(-1, "%s: Checksum mismatch -- " + "will transfer entire file\n", fup->destpath); + fixups_put(up->config->fixups, fup->coll, sr->sr_file); + } + if (coll->co_options & CO_KEEPBADFILES) + lprintf(-1, "Bad version saved in %s\n", fup->temppath); else - xasprintf(&up->errmsg, - "Cannot install \"%s\" to \"%s\": %s", - from, to, strerror(errno)); + updater_deletefile(fup->temppath); + return (0); + } + + fattr_umask(sr->sr_clientattr, coll->co_umask); + rv = fattr_install(sr->sr_clientattr, fup->destpath, fup->temppath); + if (rv == -1) { + xasprintf(&up->errmsg, "Cannot install \"%s\" to \"%s\": %s", + fup->temppath, fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } @@ -676,9 +694,9 @@ updater_updatefile(struct updater *up, struct file_update *fup, const char *to, * server. This is important for preserving hard links in mirror * mode. */ - fileattr = fattr_frompath(to, FATTR_NOFOLLOW); + fileattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW); if (fileattr == NULL) { - xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", to, + xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } @@ -715,10 +733,9 @@ updater_diff(struct updater *up, struct file_update *fup) struct statusrec *sr; struct fattr *fa, *tmp; char *author, *path, *revnum, *revdate; - char *line, *cmd, *temppath; + char *line, *cmd; int error; - temppath = NULL; coll = fup->coll; sr = &fup->srbuf; path = fup->destpath; @@ -728,18 +745,14 @@ updater_diff(struct updater *up, struct file_update *fup) if (strcmp(line, ".") == 0) break; cmd = proto_get_ascii(&line); - if (cmd == NULL || strcmp(cmd, "D") != 0) { - error = UPDATER_ERR_PROTO; - goto bad; - } + if (cmd == NULL || strcmp(cmd, "D") != 0) + return (UPDATER_ERR_PROTO); revnum = proto_get_ascii(&line); proto_get_ascii(&line); /* XXX - diffbase */ revdate = proto_get_ascii(&line); author = proto_get_ascii(&line); - if (author == NULL || line != NULL) { - error = UPDATER_ERR_PROTO; - goto bad; - } + if (author == NULL || line != NULL) + return (UPDATER_ERR_PROTO); if (sr->sr_revnum != NULL) free(sr->sr_revnum); if (sr->sr_revdate != NULL) @@ -755,36 +768,32 @@ updater_diff(struct updater *up, struct file_update *fup) if (fup->orig == NULL) { xasprintf(&up->errmsg, "%s: Cannot open: %s", path, strerror(errno)); - error = UPDATER_ERR_MSG; - goto bad; + return (UPDATER_ERR_MSG); } } else { /* Subsequent patches. */ stream_close(fup->orig); fup->orig = fup->to; stream_rewind(fup->orig); - unlink(temppath); - free(temppath); + unlink(fup->temppath); + free(fup->temppath); + fup->temppath = tempname(path); } - temppath = tempname(path); - fup->to = stream_open_file(temppath, - O_RDWR | O_CREAT | O_EXCL, 0600); + fup->to = stream_open_file(fup->temppath, + O_RDWR | O_CREAT | O_TRUNC, 0600); if (fup->to == NULL) { xasprintf(&up->errmsg, "%s: Cannot open: %s", - temppath, strerror(errno)); - error = UPDATER_ERR_MSG; - goto bad; + fup->temppath, strerror(errno)); + return (UPDATER_ERR_MSG); } lprintf(2, " Add delta %s %s %s\n", sr->sr_revnum, sr->sr_revdate, fup->author); error = updater_diff_batch(up, fup); if (error) - goto bad; - } - if (line == NULL) { - error = UPDATER_ERR_READ; - goto bad; + return (error); } + if (line == NULL) + return (UPDATER_ERR_READ); fa = fattr_frompath(path, FATTR_FOLLOW); tmp = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); @@ -793,24 +802,13 @@ updater_diff(struct updater *up, struct file_update *fup) fattr_maskout(fa, FA_MODTIME); sr->sr_clientattr = fa; - error = updater_updatefile(up, fup, path, temppath); - if (error) - goto bad; - - if (MD5_File(path, md5) == -1) { + if (MD5_File(fup->temppath, md5) == -1) { xasprintf(&up->errmsg, "Cannot calculate checksum for \"%s\": %s", path, strerror(errno)); - error = UPDATER_ERR_MSG; - goto bad; + return (UPDATER_ERR_MSG); } - updater_checkmd5(up, fup, md5, 0); - free(temppath); - return (0); -bad: - assert(error); - if (temppath != NULL) - free(temppath); + error = updater_updatefile(up, fup, md5, 0); return (error); } @@ -934,10 +932,11 @@ updater_checkout(struct updater *up, struct file_update *fup, int isfixup) return (UPDATER_ERR_MSG); } - to = stream_open_file(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + to = stream_open_file(fup->temppath, + O_WRONLY | O_CREAT | O_TRUNC, 0600); if (to == NULL) { xasprintf(&up->errmsg, "%s: Cannot create: %s", - path, strerror(errno)); + fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } stream_filter_start(to, STREAM_FILTER_MD5, md5); @@ -980,14 +979,14 @@ updater_checkout(struct updater *up, struct file_update *fup, int isfixup) fup->wantmd5 = proto_get_ascii(&line); if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0) return (UPDATER_ERR_PROTO); - updater_checkmd5(up, fup, md5, isfixup); + error = updater_updatefile(up, fup, md5, isfixup); fup->wantmd5 = NULL; /* So that it doesn't get freed. */ - error = updater_updatefile(up, fup, path, NULL); if (error) return (error); return (0); bad: - xasprintf(&up->errmsg, "%s: Cannot write: %s", path, strerror(errno)); + xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, + strerror(errno)); return (UPDATER_ERR_MSG); }