/* $Id: util.c,v 1.10 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 1999 Atsushi Onoe
 * 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <limits.h>

#include "cue.h"

char *
copy_kbuf(struct kbuf **kbufp, const char *buf, int len)
{
	struct kbuf *kbuf;
	char *ret;
	int size;

	for (kbuf = *kbufp; ; kbuf = kbuf->next) {
		if (kbuf == NULL) {
			size = sizeof(*kbuf);
			if (len > CHARBLOCK)
				size += (len - CHARBLOCK);
			kbuf = malloc(size);
			if (kbuf == NULL)
				return NULL;
			kbuf->inuse = 0;
			kbuf->next = *kbufp;
			*kbufp = kbuf;
			break;
		}
		if (kbuf->inuse + len <= CHARBLOCK)
			break;
	}
	ret = &kbuf->buf[kbuf->inuse];
	memcpy(ret, buf, len);
	kbuf->inuse += len;
	return ret;
}

char *
copy_abuf(struct abuf *abuf, const char *buf, int len)
{
	char *ret;
	int l;
	char *p;

	if (abuf->buf.len + len > abuf->size) {
		l = 0;
		while (abuf->buf.len + len > abuf->size + l)
			l += CHARBLOCK;
		p = realloc(abuf->buf.ptr, abuf->size + l);
		if (!p)
			abort();
		abuf->buf.ptr = p;
		abuf->size += l;
	}
	ret = abuf->buf.ptr + abuf->buf.len;
	if (len > 0)
		memcpy(ret, buf, len);
	abuf->buf.len += len;
	return ret;
}

int
csubst(char *str1, char *str2, cbuf_t *buf)
{
	int len1 = strlen(str1);
	int len2 = str2 ? strlen(str2) : 0;
	char *p, *ep, *np;
	int count = 0;

	/* XXX do not support growing in length */
	if (len1 <= 0 || len1 < len2)
		return -1;

	for (p = buf->ptr, ep = p + buf->len; p + len1 <= ep; p++) {
		if (strncasecmp(str1, p, len1) == 0) {
			count++;
			np = p + len1;
			if (len2 > 0) {
				memcpy(p, str2, len2);
				p += len2;
			}
			if (len1 != len2) {
				memmove(p, np, ep - np);
				ep -= np - p;
			}
		}
	}
	buf->len = ep - buf->ptr;
	return count;
}

void
fname_expand(char *name, char *buf)
{
	char *p, *q;
	int len, hlen;
	struct passwd *pw;
	char uname[9];

	/* in place expansion: ..//.. -> /.. , ../~/.. -> ~/.. */
	len = strlen(name);
	for (p = name + len - 2; p >= name; p--) {
		if (*p != '/')
			continue;
		if (p[1] == '/' || (p[1] == '~' && p[2] == '/')) {
			memmove(name, p + 1, len - (p - name));
			break;
		}
	}
	if (buf == NULL)
		return;

	/* name and buf can be same buffer */
	len = strlen(name);
	if (name[0] == '~' && name[1] == '/') {
		p = getenv("HOME");
		if (p == NULL)
			p = "";
		hlen = strlen(p);
		memmove(buf + hlen, name + 1, len);	/* with '\0' */
		memcpy(buf, p, hlen);			/* without '\0' */
		return;
	} else if (name[0] == '~' && (q = strchr(name + 1, '/')) != NULL &&
	    q - (name + 1) < sizeof(uname)) {
		strncpy(uname, name + 1, q - (name + 1));
		uname[q - (name + 1)] = '\0';
		pw = getpwnam(uname);
		if (!pw) {
			memmove(buf, name, len);
			return;
		}
		hlen = strlen(pw->pw_dir);
		memmove(buf + hlen, q, len);	/* with '\0' */
		memcpy(buf, pw->pw_dir, hlen);	/* without '\0' */
		return;
	} else {
		memmove(buf, name, len);
		return;
	}
}

int
fname_completion(char *candidate, void (*callback)(void *arg, const char *entry), void *arg)
{
	int nmatch = 0;
	int matchlen, len, nlen;
	int i, l;
	char *fname, *ep;
	char dname[MAXPATH], mname[MAXPATH];
	DIR *dirp;
	struct dirent *dp;
	struct stat stbuf;

	if ((fname = strrchr(candidate, '/')) != NULL) {
		fname_expand(candidate, dname);
		candidate = fname + 1;
		fname = strrchr(dname, '/');
		/* assert(fname != NULL); */
		*fname = '\0';
	} else {
		strlcpy(dname, ".", sizeof(dname));
		fname = dname + 1;
	}
	ep = dname + sizeof(dname);
	dirp = opendir(dname);
	if (dirp == NULL)
		return 0;
	*fname++ = '/';
	len = strlen(candidate);
	matchlen = 0;
	mname[0] = '\0';
	l = 0;
	while ((dp = readdir(dirp)) != NULL) {
		if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
			continue;
		strlcpy(fname, dp->d_name, ep - fname);
#ifdef DT_REG
		if (dp->d_type != DT_REG)
#endif /* DT_REG */
		if (stat(dname, &stbuf) == 0 && S_ISDIR(stbuf.st_mode))
			strlcat(fname, "/", ep - fname);
		nlen = strlen(fname);
		if (nlen >= len &&
		    memcmp(fname, candidate, len) == 0) {
			if (nmatch && mname[0]) {
				for (i = len; i < matchlen; i++) {
					if (i >= nlen
					||  fname[i] != mname[i]) {
						matchlen = i;
						break;
					}
				}
			} else {
				matchlen = nlen;
			}
			nmatch++;
			if (callback)
				callback(arg, fname);
			strlcpy(mname, fname, sizeof(mname));
		}
	}
	closedir(dirp);
	if (nmatch && matchlen > len) {
		memcpy(candidate + len, mname + len, matchlen - len);
		candidate[matchlen] = '\0';
	}
	return nmatch;
}

/*
 * Derived from bin/mkdir.c on FreeBSD2.2.8.
 */
int
builddir(char *path, mode_t omode)
{
	struct stat sb;
	mode_t numask, oumask;
	int first, last, retval;
	char *p;

	p = path;
	oumask = 0;
	retval = 0;
	if (p[0] == '/')		/* Skip leading '/'. */
		++p;
	for (first = 1, last = 0; !last ; ++p) {
		if (p[0] == '\0')
			last = 1;
		else if (p[0] != '/')
			continue;
		*p = '\0';
		if (p[1] == '\0')
			last = 1;
		if (first) {
			/*
			 * POSIX 1003.2:
			 * For each dir operand that does not name an existing
			 * directory, effects equivalent to those cased by the
			 * following command shall occcur:
			 *
			 * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
			 *    mkdir [-m mode] dir
			 *
			 * We change the user's umask and then restore it,
			 * instead of doing chmod's.
			 */
			oumask = umask(0);
			numask = oumask & ~(S_IWUSR | S_IXUSR);
			(void)umask(numask);
			first = 0;
		}
		if (last)
			(void)umask(oumask);
		if (stat(path, &sb)) {
			if (errno != ENOENT ||
			    mkdir(path, last ? omode : 
				  S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
				retval = -1;
				break;
			}
		}
		else if ((sb.st_mode & S_IFMT) != S_IFDIR) {
			if (last)
				errno = EEXIST;
			else
				errno = ENOTDIR;
			retval = -1;
			break;
		}
		*p = '/';
	}
	if (!first && !last)
		(void)umask(oumask);
	return (retval);
}
