/*
 *  Copyright (C) 2006-2019, Thomas Maier-Komor
 *
 *  This is the source code of xjobs.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>

#include "settings.h"
#include "support.h"
#include "tokens.h"
#include "log.h"

typedef struct cache_s {
	struct cache_s *next;
	char *file, *exe;
} cache_t;

static cache_t *Cache = 0;
static char Token[PATH_MAX+1] = "", PWD[PATH_MAX];
static size_t PwdLen = 0;


void *Malloc(size_t s)
{
	int tries = 0, time = 5;
	for (;;) {
		void *r = malloc(s);
		if (r != 0)
			return r;
		warn("out of memory - retrying in %d seconds\n",time);
		(void) sleep(time);
		time += 5;
		++tries;
		if (tries == 10) {
			warn("Persisiting low memory situation: stopping.\n"
				"Please send SIGCONT to contiue.\n");
			if (-1 == raise(SIGSTOP))
				abort();
			time = 5;
			tries = 0;
		}
	}
}


char *makestr(const char *f, ...)
{
       va_list val;
       va_start(val,f);
       size_t l = vsnprintf(0,0,f,val);
       va_end(val);
       ++l;
       char *r = Malloc(l);
       va_start(val,f);
       vsnprintf(r,l,f,val);
       va_end(val);
       return r;
}


char *concatstr(const char *s0, const char *s1)
{
	size_t l0 = strlen(s0), l1 = strlen(s1);
	char *ret = Malloc(l0 + l1 + 1);
	memcpy(ret,s0,l0);
	memcpy(ret+l0,s1,l1+1);
	return ret;
}


char *replace_string(char *in, const char *param, const char *valuestr)
{
	if (in == 0)
		return 0;
	char *p = strstr(in,param);
	if (p == 0)
		return in;
	if (valuestr == 0)
		valuestr = "";
	size_t vl = strlen(valuestr);
	size_t pl = strlen(param);
	size_t il = strlen(in) + 1;
	do {
		if (vl > pl) {
			in = realloc(in,il + (vl-pl));
			p = strstr(in,param);
		}
		if (pl != vl)
			memmove(p+vl,p+pl,il-(p-in)-pl);
		memcpy(p,valuestr,vl);
		p = strstr(in,param);
	} while (p);
	return in;
}


int search_param(const char *in, size_t *off, size_t *len)
{
	const char *b = in;
	const char *d;
	do
		d = strchr(b,'$');
	while (d && d[1] != '(');
	if (d == 0)
		return 0;
	const char *p = strchr(d+1,')');
	const char *d2 = strchr(d+1,'$');
	if (p == 0)
		return 0;
	if (d2 == 0) {
		*off = d-in;
		*len = p-d+1;
		return 1;
	}
	if (p < d2) {
		*off = d-in;
		*len = p-d+1;
		return 1;
	}
	size_t o,l;
	if ((d2[1] == '(') && search_param(d2,&o,&l)) {
		*off = o + (d2-in);
		*len = l;
		return 1;
	}
	*off = d-in;
	*len = p-d+1;
	return 1;
}


char *resolve_env(const char *in)
{
	char *buf = Strdup(in);
	size_t off,len;
	while (search_param(buf,&off,&len)) {
		char param[len+1];
		memcpy(param,buf+off,len);
		param[len] = 0;
		assert(param[len-1] == ')');
		param[len-1] = 0;
		const char *ev = getenv(param+2);
		param[len-1] = ')';
		buf = replace_string(buf,param,ev);
	}
	return buf;
}


char *replace_string_l(char *in, const char *param, long v)
{
	char valuestr[32];
	size_t vl = snprintf(valuestr,sizeof(valuestr),"%ld",v);
	assert(vl < sizeof(valuestr));
	return replace_string(in,param,valuestr);
}


static char get_stdin_char(void)
{
	static char buf[8*1024];
	static char *at = buf, *end = buf;
	if (at == end) {
		int num = read(STDIN_FILENO,buf,sizeof(buf));
		if (-1 == num) {
			fprintf(stderr,"error reading stdin: %s\n",strerror(errno));
			return 0;
		} else if (0 == num) {
			return 0;
		} else {
			at = buf;
			end = buf+num;
		}
	}
	return *at++;
}


/* not thread-safe */
int read_to_0(void)
{
	char *b = Token, c;
	if (Token[0] != 0) {
		Token[0] = 0;
		return EOL;
	}
	do {
		c = get_stdin_char();
		*b = c;
		++b;
		assert(b-Token < sizeof(Token));
	} while (c);
	if (*Token == 0)
		return 0;
	Fill = b - Token;
	Buffer = Token;
	return UQUOTED;
}


/* not thread-safe */
int read_to_nl(void)
{
	char *b = Token, c;
	if (Token[0] != 0) {
		Token[0] = 0;
		return EOL;
	}
	do {
		c = get_stdin_char();
		*b = c;
		++b;
		assert(b-Token < sizeof(Token));
	} while (c && (c != '\n'));
	if (*Token == 0)
		return 0;
	*--b = 0;
	Fill = b - Token;
	Buffer = Token;
	return UQUOTED;
}


int resolve_symlink(char *cmd)
{
	char lt[PATH_MAX];
	ssize_t l;
	/* handle symlinks */
	bzero(lt,sizeof(lt));
	while (-1 != (l = readlink(cmd,lt,PATH_MAX))) {
		char *base = cmd;
		lt[l] = '\0';
		dbug("is symlink: %s\n",lt);
		/* handle relative links */
		if (lt[0] != '/') {
			base = strrchr(cmd,'/');   
			if (base == 0) {
				base = cmd;
			} else if ((l + (base - cmd)) > PATH_MAX) {
				warn("command exceed PATH_MAX limit of your system\n");
				return -1;
			}
			++base;
		}
		(void) memcpy(base,lt,l+1);
		dbug("resolved to: %s\n",cmd);
	}
	return 0;
}


char *add_cache(char *exe, char *file)
{
	cache_t *c = Malloc(sizeof(cache_t));
	c->next = Cache;
	Cache = c;
	size_t el = strlen(exe) + 1;
	c->exe = Malloc(el);
	memcpy(c->exe,exe,el);
	char *r;
	if (file) {
		size_t fl = strlen(file) + 1;
		c->file = Malloc(fl);
		memcpy(c->file,file,fl);
		r = Malloc(fl);
		memcpy(r,file,fl);
	} else {
		c->file = 0;
		r = Malloc(el);
		memcpy(r,exe,el);
	}
	return r;
}


char *complete_exe(char *exe)
{
	if (Path == 0) {
		Path = getenv("PATH");
		dbug("PATH=\"%s\"\n", Path);
		if (0 == getcwd(PWD,sizeof(PWD)))
			error("current work directory exceeds maximum allowed size\n");
		/* on Linux for chroot environments */
		if (0 == strncmp(PWD,"(unreachable)",13))
			error("current work directory is unreachable\n");
		PwdLen = strlen(PWD);
		if (PWD[PwdLen-1] != '/') {
			if (PwdLen >= sizeof(PWD)-1)
				error("current work directory exceeds maximum allowed size\n");
			PWD[PwdLen] = '/';
			++PwdLen;
			PWD[PwdLen] = 0;
		}
	}
	cache_t *c = Cache;
	while (c) {
		if (0 == strcmp(exe,c->exe))
			return Strdup(c->file ? c->file : c->exe);
		c = c->next;
	}
	// is exe given as an absolute path?
	if (exe[0] == '/') {
		if (0 == access(exe,X_OK))
			return add_cache(exe,0);
		warn("unable to find executable '%s'",exe);
		return 0;
	}

	// relative path
	size_t exel = strlen(exe);
	if (0 != strchr(exe,'/')) {
		// relative path we need to resolve over PWD
		if (0 == access(exe,X_OK)) {
			dbug("executable: %s\n",exe);
			return add_cache(exe,0);
		}
		char cmd[PATH_MAX];
		if (0 == realpath(exe,cmd)) {
			warn("unable to resolve %s: %s",exe,strerror(errno));
			return 0;
		}
		if (0 == access(cmd,X_OK)) {
			dbug("%s resolved to: %s\n",exe,cmd);
			return add_cache(exe,cmd);
		}
		warn("unable to execute %s\n",cmd);
		return 0;
	}

	// resolve via PATH environment variable
	char *token = Path;
	char *next = strchr(Path,':');
        while (token && *token) {
		if (next)
			*next = 0;
		size_t off = strlen(token);
		if (exel + off >= PATH_MAX)
			warn("command exceeds PATH_MAX limit of your system\n");
		char cmd[off+exel+1];
		(void) memcpy(cmd, token, off);
		if (cmd[off-1] != '/') {
			cmd[off] = '/';
			++off;
		}
		(void) memcpy(cmd+off, exe, exel+1);
		if (next) {
			*next = ':';
			token = next + 1;
			next = strchr(token,':');
		} else
			token = 0;
		if ((Verbose == Debug) && resolve_symlink(cmd))
			continue;
		dbug("check via PATH: %s\n",cmd);
		if (0 == access(cmd,X_OK)) {
			dbug("%s resolved to: %s\n",exe,cmd);
			return add_cache(exe,cmd);
		}
        }
        return 0;
}


const char *timestr(long long usec, char *s, size_t n)
{
	unsigned days, hours, mins, secs;
	double sec;
	assert(s);
	secs = usec / 1000000L;
	usec = usec % 1000000L;
	days = secs / (60 * 60 * 24);
	secs -= days * 60 * 60 * 24;
	hours = secs / (60 * 60);
	secs -= hours * 60 * 60;
	mins = secs / 60;
	secs -= mins * 60;
	assert(secs < 60);
	sec = (double)secs + (double)usec * 1E-6;
	if (days != 0)
		snprintf(s,n,"%ud %uh %um",days,hours,mins);
	else if (hours != 0)
		snprintf(s,n,"%uh %um",hours,mins);
	else if (mins != 0)
		snprintf(s,n,"%um %us",mins,secs);
	else
		snprintf(s,n,"%.4gs",sec);
	return s;
}


