/*- * Copyright (c) 2003-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 #include #include #include #include #include #include #include "fattr.h" #include "main.h" #include "misc.h" struct pattlist { char **patterns; size_t size; size_t in; }; struct backoff_timer { time_t min; time_t max; time_t interval; float backoff; float jitter; }; 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, ...) { FILE *to; va_list ap; int ret; if (level > verbose) return (0); if (level == -1) to = stderr; else to = stdout; va_start(ap, fmt); ret = vfprintf(to, fmt, ap); va_end(ap); fflush(to); return (ret); } /* * Compute the MD5 checksum of a file. The md parameter must * point to a buffer containing at least MD5_DIGEST_SIZE bytes. * * Do not confuse OpenSSL's MD5_DIGEST_LENGTH with our own * MD5_DIGEST_SIZE macro. */ int MD5_File(char *path, char *md) { char buf[1024]; MD5_CTX ctx; ssize_t n; int fd; fd = open(path, O_RDONLY); if (fd == -1) return (-1); MD5_Init(&ctx); while ((n = read(fd, buf, sizeof(buf))) > 0) MD5_Update(&ctx, buf, n); close(fd); if (n == -1) return (-1); MD5_End(md, &ctx); return (0); } /* * Wrapper around MD5_Final() that converts the 128 bits MD5 hash * to an ASCII string representing this value in hexadecimal. */ void MD5_End(char *md, MD5_CTX *c) { unsigned char md5[MD5_DIGEST_LENGTH]; const char hex[] = "0123456789abcdef"; int i, j; MD5_Final(md5, c); j = 0; for (i = 0; i < MD5_DIGEST_LENGTH; i++) { md[j++] = hex[md5[i] >> 4]; md[j++] = hex[md5[i] & 0xf]; } md[j] = '\0'; } int pathcmp(const char *s1, const char *s2) { char c1, c2; do { c1 = *s1++; if (c1 == '/') c1 = 1; c2 = *s2++; if (c2 == '/') c2 = 1; } while (c1 == c2 && c1 != '\0'); return (c1 - c2); } size_t commonpathlength(const char *a, size_t alen, const char *b, size_t blen) { size_t i, minlen, lastslash; minlen = min(alen, blen); lastslash = 0; for (i = 0; i < minlen; i++) { if (a[i] != b[i]) return (lastslash); if (a[i] == '/') { if (i == 0) /* Include the leading slash. */ lastslash = 1; else lastslash = i; } } /* One path is a prefix of the other/ */ if (alen > minlen) { /* Path "b" is a prefix of "a". */ if (a[minlen] == '/') return (minlen); else return (lastslash); } else if (blen > minlen) { /* Path "a" is a prefix of "b". */ if (b[minlen] == '/') return (minlen); else return (lastslash); } /* The paths are identical. */ return (minlen); } const char * pathlast(const char *path) { const char *s; s = strrchr(path, '/'); if (s == NULL) return (path); return (++s); } int rcsdatetotm(const char *revdate, struct tm *tm) { char *cp; size_t len; cp = strchr(revdate, '.'); if (cp == NULL) return (-1); len = cp - revdate; if (len >= 4) cp = strptime(revdate, "%Y.%m.%d.%H.%M.%S", tm); else if (len == 2) cp = strptime(revdate, "%y.%m.%d.%H.%M.%S", tm); else return (-1); if (cp == NULL || *cp != '\0') return (-1); return (0); } time_t rcsdatetotime(const char *revdate) { struct tm tm; time_t t; int error; error = rcsdatetotm(revdate, &tm); if (error) return (error); t = timegm(&tm); return (t); } /* * Checks if a file is an RCS file. */ int isrcs(const char *file, size_t *len) { const char *cp; if (file[0] == '/') return (0); cp = file; while ((cp = strstr(cp, "..")) != NULL) { if (cp == file || cp[2] == '\0' || (cp[-1] == '/' && cp[2] == '/')) return (0); cp += 2; } *len = strlen(file); if (*len < 2 || file[*len - 1] != 'v' || file[*len - 2] != ',') { return (0); } return (1); } /* * Returns a buffer allocated with malloc() containing the absolute * pathname to the checkout file made from the prefix and the path * of the corresponding RCS file relatively to the prefix. If the * filename is not an RCS filename, NULL will be returned. */ char * checkoutpath(const char *prefix, const char *file) { char *path; size_t len; if (!isrcs(file, &len)) return (NULL); xasprintf(&path, "%s/%.*s", prefix, (int)len - 2, file); return (path); } /* * Returns a cvs path allocated with malloc() containing absolute pathname to a * file in cvs mode which can reside in the attic. XXX: filename has really no * restrictions. */ char * cvspath(const char *prefix, const char *file, int attic) { const char *last; char *path; last = pathlast(file); if (attic) xasprintf(&path, "%s/%.*sAttic/%s", prefix, (int)(last - file), file, last); else xasprintf(&path, "%s/%s", prefix, file); return (path); } /* * Regular or attic path if regular fails. * XXX: This should perhaps also check if the Attic file exists too, and return * NULL if not. */ char * atticpath(const char *prefix, const char *file) { char *path; path = cvspath(prefix, file, 0); if (access(path, F_OK) != 0) { free(path); path = cvspath(prefix, file, 1); } return (path); } int mkdirhier(char *path, mode_t mask) { struct fattr *fa; size_t i, last, len; int error, finish, rv; finish = 0; last = 0; len = strlen(path); for (i = len - 1; i > 0; i--) { if (path[i] == '/') { path[i] = '\0'; if (access(path, F_OK) == 0) { path[i] = '/'; break; } if (errno != ENOENT) { path[i] = '/'; if (last == 0) return (-1); finish = 1; break; } last = i; } } if (last == 0) return (0); i = strlen(path); fa = fattr_new(FT_DIRECTORY, -1); fattr_mergedefault(fa); fattr_umask(fa, mask); while (i < len) { if (!finish) { rv = 0; error = fattr_makenode(fa, path); if (!error) rv = fattr_install(fa, path, NULL); if (error || rv == -1) finish = 1; } path[i] = '/'; i += strlen(path + i); } assert(i == len); if (finish) return (-1); return (0); } /* * Compute temporary pathnames. * This can look a bit like overkill but we mimic CVSup's behaviour. */ #define TEMPNAME_PREFIX "#cvs.csup" static pthread_mutex_t tempname_mtx = PTHREAD_MUTEX_INITIALIZER; static pid_t tempname_pid = -1; static int tempname_count; char * tempname(const char *path) { char *cp, *temp; int count, error; error = pthread_mutex_lock(&tempname_mtx); assert(!error); if (tempname_pid == -1) { tempname_pid = getpid(); tempname_count = 0; } count = tempname_count++; error = pthread_mutex_unlock(&tempname_mtx); assert(!error); cp = strrchr(path, '/'); if (cp == NULL) xasprintf(&temp, "%s-%ld.%d", TEMPNAME_PREFIX, (long)tempname_pid, count); else xasprintf(&temp, "%.*s%s-%ld.%d", (int)(cp - path + 1), path, TEMPNAME_PREFIX, (long)tempname_pid, count); return (temp); } void * xmalloc(size_t size) { void *buf; buf = malloc(size); if (buf == NULL) err(1, "malloc"); return (buf); } void * xrealloc(void *buf, size_t size) { buf = realloc(buf, size); if (buf == NULL) err(1, "realloc"); return (buf); } char * xstrdup(const char *str) { char *buf; buf = strdup(str); if (buf == NULL) err(1, "strdup"); return (buf); } int xasprintf(char **ret, const char *format, ...) { va_list ap; int rv; va_start(ap, format); rv = vasprintf(ret, format, ap); va_end(ap); if (*ret == NULL) err(1, "asprintf"); return (rv); } struct pattlist * pattlist_new(void) { struct pattlist *p; p = xmalloc(sizeof(struct pattlist)); p->size = 4; /* Initial size. */ p->patterns = xmalloc(p->size * sizeof(char *)); p->in = 0; return (p); } void pattlist_add(struct pattlist *p, const char *pattern) { if (p->in == p->size) { p->size *= 2; p->patterns = xrealloc(p->patterns, p->size * sizeof(char *)); } assert(p->in < p->size); p->patterns[p->in++] = xstrdup(pattern); } char * pattlist_get(struct pattlist *p, size_t i) { assert(i < p->in); return (p->patterns[i]); } size_t pattlist_size(struct pattlist *p) { return (p->in); } void pattlist_free(struct pattlist *p) { size_t i; for (i = 0; i < p->in; i++) free(p->patterns[i]); free(p->patterns); free(p); } /* Creates a backoff timer. */ struct backoff_timer * bt_new(time_t min, time_t max, float backoff, float jitter) { struct backoff_timer *bt; bt = xmalloc(sizeof(struct backoff_timer)); bt->min = min; bt->max = max; bt->backoff = backoff; bt->jitter = jitter; bt->interval = min; bt_addjitter(bt); srandom(time(0)); return (bt); } /* Updates the backoff timer. */ static void bt_update(struct backoff_timer *bt) { bt->interval = (time_t)min(bt->interval * bt->backoff, bt->max); bt_addjitter(bt); } /* Adds some jitter. */ static void bt_addjitter(struct backoff_timer *bt) { long mag; mag = (long)(bt->jitter * bt->interval); /* We want a random number between -mag and mag. */ bt->interval += (time_t)(random() % (2 * mag) - mag); } /* Returns the current timer value. */ time_t bt_get(struct backoff_timer *bt) { return (bt->interval); } /* Times out for bt->interval seconds. */ void bt_pause(struct backoff_timer *bt) { sleep(bt->interval); bt_update(bt); } void bt_free(struct backoff_timer *bt) { free(bt); } /* Compare two revisions. */ int rcsnum_cmp(char *revision1, char *revision2) { char *ptr1, *ptr2, *dot1, *dot2; int num1len, num2len, ret; ptr1 = revision1; ptr2 = revision2; while (*ptr1 != '\0' && *ptr2 != '\0') { dot1 = strchr(ptr1, '.'); dot2 = strchr(ptr2, '.'); if (dot1 == NULL) dot1 = strchr(ptr1, '\0'); if (dot2 == NULL) dot2 = strchr(ptr2, '\0'); num1len = dot1 - ptr1; num2len = dot2 - ptr2; /* Check the distance between each, showing how many digits */ if (num1len > num2len) return (1); else if (num1len < num2len) return (-1); /* Equal distance means we must check each character. */ ret = strncmp(ptr1, ptr2, num1len); if (ret != 0) return (ret); ptr1 = (*dot1 == '.') ? (dot1 + 1) : dot1; ptr2 = (*dot2 == '.') ? (dot2 + 1) : dot2; } if (*ptr1 != '\0' && *ptr2 == '\0') return (1); if (*ptr1 == '\0' && *ptr2 != '\0') return (-1); return (0); } /* Returns 0 if a rcsrev is not a trunk revision number. */ int rcsrev_istrunk(char *revnum) { char *tmp; tmp = strchr(revnum, '.'); tmp++; if (strchr(tmp, '.') != NULL) return (0); return (1); } /* Return prefix of rcsfile. */ char * rcsrev_prefix(char *revnum) { char *modrev, *pos; modrev = xstrdup(revnum); pos = strrchr(modrev, '.'); if (pos == NULL) { free(modrev); return (NULL); } *pos = '\0'; return (modrev); }