/*-
 * 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: releng/9.2/usr.bin/csup/misc.c 230743 2012-01-29 14:55:20Z marius $
 */

#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);
}
