c6aa3ac446
contributed, and main development is happening in the FreeBSD repo. Suggested by: joel
646 lines
12 KiB
C
646 lines
12 KiB
C
/*-
|
|
* 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);
|
|
}
|