diff options
Diffstat (limited to 'usr.bin/csup/misc.c')
-rw-r--r-- | usr.bin/csup/misc.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/usr.bin/csup/misc.c b/usr.bin/csup/misc.c new file mode 100644 index 0000000..f4170df --- /dev/null +++ b/usr.bin/csup/misc.c @@ -0,0 +1,644 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#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); +} |