summaryrefslogtreecommitdiffstats
path: root/usr.bin/csup/misc.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/csup/misc.c')
-rw-r--r--usr.bin/csup/misc.c645
1 files changed, 645 insertions, 0 deletions
diff --git a/usr.bin/csup/misc.c b/usr.bin/csup/misc.c
new file mode 100644
index 0000000..ae16b59
--- /dev/null
+++ b/usr.bin/csup/misc.c
@@ -0,0 +1,645 @@
+/*-
+ * 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 <openssl/md5.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);
+}
OpenPOWER on IntegriCloud