/*
 * Various sundry utilites for dos emulator.
 *
 */
#include "emu.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/time.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
#include <wordexp.h>

#include "bios.h"
#include "timers.h"
#include "pic.h"
#include "dpmi.h"
#include "debug.h"
#include "utilities.h"
#include "dos2linux.h"
#include "dosemu_config.h"
#include "mhpdbg.h"

/*
 * NOTE: SHOW_TIME _only_ should be enabled for
 *      internal debugging use, but _never_ for productions releases!
 *      (this would break the port traceing stuff -D+T, which expects
 *      a machine interpretable and compressed format)
 *
 *                                     --Hans 990213
 */
#define SHOW_TIME	0		/* 0 or 1 */
#ifdef X86_EMULATOR
#include "cpu-emu.h"
#endif

#ifndef INITIAL_LOGBUFSIZE
#define INITIAL_LOGBUFSIZE      0
#endif
#ifndef INITIAL_LOGBUFLIMIT
#define INITIAL_LOGFILELIMIT	(1024*1024*1024ULL)
#endif

static pthread_mutex_t log_mtx = PTHREAD_MUTEX_INITIALIZER;

#ifdef CIRCULAR_LOGBUFFER
#define NUM_CIRC_LINES	32768
#define SIZ_CIRC_LINES	384
#define MAX_LINE_SIZE	(SIZ_CIRC_LINES-16)
char *logbuf;
static char *loglines[NUM_CIRC_LINES];
static int loglineidx = 0;
char *logptr;
#else
#define MAX_LINE_SIZE	1000
static char logbuf_[INITIAL_LOGBUFSIZE+1025];
char *logptr=logbuf_;
char *logbuf=logbuf_;
#endif
int logbuf_size = INITIAL_LOGBUFSIZE;
int logfile_limit = INITIAL_LOGFILELIMIT;
int log_written = 0;

static char hxtab[16]="0123456789abcdef";

#if 0
static inline char *prhex8 (char *p, unsigned long v)
{
  int i;
  for (i=7; i>=0; --i) { p[i]=hxtab[v&15]; v>>=4; }
  p[8]=' ';
  return p+9;
}
#endif
#if SHOW_TIME
static char *timestamp (char *p)
{
  unsigned long t;
  int i;

#ifdef DBG_TIME
  t = GETusTIME(0);
#else
  t = pic_sys_time/1193;
#endif
  /* [12345678]s - SYS time */
    {
      p[0] = '[';
      for (i=8; i>0; --i)
	{ if (t) { p[i]=(t%10)+'0'; t/=10; } else p[i]='0'; }
      p[9]=']'; p[10]=' ';
    }
  return p+11;
}
#else
#define timestamp(p)	(p)
#endif

int is_printable(const char *s)
{
  int i;
  int l = strlen(s);

  for (i = 0; i < l; i++) {
    if (!isprint(s[i]))
      return 0;
  }
  return 1;
}

char *strprintable(char *s)
{
  static char buf[8][128];
  static int bufi = 0;
  char *t, c;

  bufi = (bufi + 1) & 7;
  t = buf[bufi];
  while (*s) {
    c = *s++;
    if ((unsigned)c < ' ') {
      *t++ = '^';
      *t++ = c | 0x40;
    }
    else if ((unsigned)c > 126) {
      *t++ = 'X';
      *t++ = hxtab[(c >> 4) & 15];
      *t++ = hxtab[c & 15];
    }
    else *t++ = c;
  }
  *t++ = 0;
  return buf[bufi];
}

char *chrprintable(char c)
{
  char buf[2];
  buf[0] = c;
  buf[1] = 0;
  return strprintable(buf);
}

int vlog_printf(int flg, const char *fmt, va_list args)
{
  int i;
  static int is_cr = 1;

#ifdef USE_MHPDBG
  if (dosdebug_flags & DBGF_INTERCEPT_LOG) {
    va_list args2;
    va_copy(args2, args);
    /* we give dosdebug a chance to interrupt on given logoutput */
    i = vmhp_log_intercept(flg, fmt, args2);
    va_end(args2);
    if ((dosdebug_flags & DBGF_DISABLE_LOG_TO_FILE) || !dbg_fd) return i;
  }
#endif

  if (!flg || !dbg_fd ||
#ifdef USE_MHPDBG
      (shut_debug && (flg<10) && !mhpdbg.active)
#else
      (shut_debug && (flg<10))
#endif
     ) return 0;

#ifdef CIRCULAR_LOGBUFFER
  logptr = loglines[loglineidx++];
#endif
  {
    char *q;

    q = (is_cr? timestamp(logptr) : logptr);
    i = vsnprintf(q, MAX_LINE_SIZE, fmt, args);
    if (i < 0) {	/* truncated for buffer overflow */
      i = MAX_LINE_SIZE-2;
      q[i++]='\n'; q[i]=0; is_cr=1;
    }
    else if (i > 0) is_cr = (q[i-1]=='\n');
    i += (q-logptr);
  }

#ifdef CIRCULAR_LOGBUFFER
  loglineidx %= NUM_CIRC_LINES;
  *(loglines[loglineidx]) = 0;

  if (flg == -1) {
    char *p;
    int i, k;
    k = loglineidx;
    for (i=0; i<NUM_CIRC_LINES; i++) {
      p = loglines[k%NUM_CIRC_LINES]; k++;
      if (*p) {
	fprintf(dbg_fd, "%s", p);
	*p = 0;
      }
    }
    fprintf(dbg_fd,"****************** END CIRC_BUF\n");
    fflush(dbg_fd);
  }
#else
  logptr += i;

  if ((dbg_fd==stderr) || ((logptr-logbuf) > logbuf_size) || (flg == -1)) {
    int fsz = logptr-logbuf;
    /* writing a big buffer can produce timer bursts, which under DPMI
     * can cause stack overflows!
     */
    if (terminal_pipe) {
      write(terminal_fd, logptr, fsz);
    }
    if (write(fileno(dbg_fd), logbuf, fsz) < 0) {
      if (errno==ENOSPC) leavedos(0x4c4c);
    }
    logptr = logbuf;
#if 1
    if (logfile_limit) {
      log_written += fsz;
      if (log_written > logfile_limit) {
        fflush(dbg_fd);
#if 1
        ftruncate(fileno(dbg_fd),0);
        fseek(dbg_fd, 0, SEEK_SET);
        log_written = 0;
#else
	fclose(dbg_fd);
	shut_debug = 1;
	dbg_fd = 0;	/* avoid recursion in leavedos() */
	leavedos(0);
#endif
      }
    }
#endif
  }
#endif
  return i;
}

static int in_log_printf=0;

int log_printf(int flg, const char *fmt, ...)
{
#ifdef CIRCULAR_LOGBUFFER
	static int first = 1;
#endif
	va_list args;
	int ret;

#ifdef CIRCULAR_LOGBUFFER
	if (first) {
	  int i;
	  logbuf = calloc((NUM_CIRC_LINES+4), SIZ_CIRC_LINES);
	  for (i=0; i<NUM_CIRC_LINES; i++)
	    loglines[i] = logbuf + SIZ_CIRC_LINES*i;
	  loglineidx = 0;
	  first=0;
	}
#endif
	if (in_log_printf) return 0;
#ifdef USE_MHPDBG
	if (!(dosdebug_flags & DBGF_INTERCEPT_LOG))
#endif
	{
		if (!flg || !dbg_fd ) return 0;
	}
	in_log_printf = 1;
	va_start(args, fmt);
	pthread_mutex_lock(&log_mtx);
	ret = vlog_printf(flg, fmt, args);
	pthread_mutex_unlock(&log_mtx);
	va_end(args);
	in_log_printf = 0;
	return ret;
}

void vprint(const char *fmt, va_list args)
{
  pthread_mutex_lock(&log_mtx);
  if (!config.quiet) {
    va_list copy_args;
    va_copy(copy_args, args);
    vfprintf(real_stderr ?: stderr, fmt, copy_args);
    va_end(copy_args);
  }
  vlog_printf(10, fmt, args);
  pthread_mutex_unlock(&log_mtx);
}

void verror(const char *fmt, va_list args)
{
  char fmtbuf[1025];

  if (fmt[0] == '@') {
    fmt++;
  } else {
    snprintf(fmtbuf, sizeof(fmtbuf), "ERROR: %s", fmt);
    fmt = fmtbuf;
  }
  vprint(fmt, args);
}

void error(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	verror(fmt, args);
	va_end(args);
}


/* write string to dos? */
int p_dos_vstr(const char *fmt, va_list args)
{
  static char buf[1024];
  char *s;
  int i;

  i = com_vsnprintf(buf, sizeof(buf), fmt, args);
  s = buf;
  g_printf("CONSOLE MSG: '%s'\n",buf);
  while (*s)
	char_out(*s++, READ_BYTE(BIOS_CURRENT_SCREEN_PAGE));
  return i;
}

int p_dos_str(const char *fmt, ...)
{
  va_list args;
  int i;

  va_start(args, fmt);
  i = p_dos_vstr(fmt, args);
  va_end(args);

  return i;
}

/* some stuff to handle reading of /proc */

static char *procfile_name=0;
static char *procbuf=0;
static char *procbufptr=0;
#define PROCBUFSIZE	4096
static char *proclastpos=0;
static char proclastdelim=0;

void close_proc_scan(void)
{
  if (procfile_name) free(procfile_name);
  if (procbuf) free(procbuf);
  procfile_name = procbuf = procbufptr = proclastpos = 0;
}

void open_proc_scan(const char *name)
{
  int size, fd;
  close_proc_scan();
  fd = open(name, O_RDONLY);
  if (fd == -1) {
    error("cannot open %s\n", name);
    leavedos(5);
  }
  procfile_name = strdup(name);
  procbuf = malloc(PROCBUFSIZE);
  size = read(fd, procbuf, PROCBUFSIZE-1);
  procbuf[size] = 0;
  procbufptr = procbuf;
  close(fd);
}

void advance_proc_bufferptr(void)
{
  if (proclastpos) {
    procbufptr = proclastpos;
    if (proclastdelim == '\n') procbufptr++;
  }
}

void reset_proc_bufferptr(void)
{
  if (proclastpos) {
    *proclastpos = proclastdelim;
    proclastpos =0;
  }
  procbufptr = procbuf;
}

char *get_proc_string_by_key(const char *key)
{

  char *p;

  if (proclastpos) {
    *proclastpos = proclastdelim;
    proclastpos =0;
  }
  p = strstr(procbufptr, key);
  if (p) {
    if ((p == procbufptr) || (p[-1] == '\n')) {
      p += strlen(key);
      while (*p && isblank(*p)) p++;
      if (*p == ':') p++;
      while (*p && isblank(*p)) p++;
      proclastpos = p;
      while (*proclastpos && (*proclastpos != '\n')) proclastpos++;
      proclastdelim = *proclastpos;
      *proclastpos = 0;
      return p;
    }
  }
#if 0
  error("Unknown format on %s\n", procfile_name);
  leavedos(5);
#endif
  return 0; /* just to make GCC happy */
}

int get_proc_intvalue_by_key(const char *key)
{
  char *p = get_proc_string_by_key(key);
  int val;

  if (p) {
    if (sscanf(p, "%d",&val) == 1) return val;
  }
  error("Unknown format on %s\n", procfile_name);
  leavedos(5);
  return -1; /* just to make GCC happy */
}


int integer_sqrt(int x)
{
	unsigned y;
	int delta;

	if (x < 1) return 0;
	if (x <= 1) return 1;
        y = power_of_2_sqrt(x);
	if ((y*y) == x) return y;
	delta = y >> 1;
	while (delta) {
		y += delta;
		if (y*y > x) y -= delta;
		delta >>= 1;
	}
	return y;
}

int exists_dir(const char *name)
{
	struct stat st;
	if (stat(name, &st)) return 0;
	return (S_ISDIR(st.st_mode));
}

int exists_file(const char *name)
{
	struct stat st;
	if (stat(name, &st)) return 0;
	return (S_ISREG(st.st_mode));
}

char *strcatdup(char *s1, char *s2)
{
	char *s;
	if (!s1 || !s2) return 0;
	s = malloc(strlen(s1)+strlen(s2)+1);
	if (!s) return 0;
	strcpy(s,s1);
	return strcat(s,s2);
}

// Concatenate a filename, s1+s2, and return a newly-allocated string of it.
// Watch out for "s1=concat(s1,s2)" if s1 is allocated; if there's not
// another pointer to s1, you won't be able to free s1 later.
static char *_concat_dir(const char *s1, const char *s2)
{
  size_t strlen_s1 = strlen(s1);
  char *_new = malloc(strlen_s1+strlen(s2)+2);
//  debug("concat_dir(%s,%s)", s1, s2);
  strcpy(_new,s1);
  if (s1[strlen_s1 - 1] != '/')
    strcat(_new, "/");
  strcat(_new,s2);
//  debug("->%s\n", _new);
  return _new;
}

char *concat_dir(const char *s1, const char *s2)
{
	if (s2[0] == '/')
		return strdup(s2);
	return _concat_dir(s1, s2);
}

char *assemble_path(const char *dir, const char *file)
{
	char *s;
	wordexp_t p;
	int err;

	err = wordexp(dir, &p, WRDE_NOCMD);
	assert(!err);
	assert(p.we_wordc == 1);
	asprintf(&s, "%s/%s", p.we_wordv[0], file);
	wordfree(&p);
	return s;
}

char *expand_path(const char *dir)
{
	char *s;
	wordexp_t p;
	int err;

	err = wordexp(dir, &p, WRDE_NOCMD);
	assert(!err);
	assert(p.we_wordc == 1);
	s = realpath(p.we_wordv[0], NULL);
	wordfree(&p);
	return s;
}

const char *mkdir_under(const char *basedir, const char *dir)
{
	const char *s = basedir;

	if (dir) s = assemble_path(basedir, dir);
	if (!exists_dir(s)) {
		if (mkdir(s, S_IRWXU)) {
			fprintf(stderr, "can't create local %s directory\n", s);
		}
	}
	return s;
}

char *get_path_in_HOME(const char *path)
{
	char *home = getenv("HOME");

	if (!home) {
		fprintf(stderr, "odd environment, you don't have $HOME, giving up\n");
		leavedos(0x45);
	}
	if (!path) {
		return strdup(home);
	}
	return assemble_path(home, path);
}

const char *get_dosemu_local_home(void)
{
	return mkdir_under(get_path_in_HOME(".dosemu"), 0);
}

int argparse(char *s, char *argvx[], int maxarg)
{
   int mode = 0;
   int argcx = 0;
   char delim = 0;

   maxarg --;
   for ( ; *s; s++) {
      if (!mode) {
         if (*s > ' ') {
            mode = 1;
            argvx[argcx++] = s;
            switch (*s) {
              case '"':
              case '\'':
                delim = *s;
                mode = 2;
            }
            if (argcx >= maxarg)
               break;
         }
      } else if (mode == 1) {
         if (*s <= ' ') {
            mode = 0;
            *s = 0x00;
         }
      } else {
         if (*s == delim) mode = 1;
      }
   }
   argvx[argcx] = NULL;
   return(argcx);
}

void subst_file_ext(char *ptr)
{
#define ext_fix(s) { char *r=(s); \
		     while (*r) { *r=toupperDOS(*r); r++; } }
    static int subst_sys=2;

    if (ptr == NULL) {
      /* reset */
      subst_sys = 2;
      return;
    }

    /* skip leading drive name and \ */
    if (ptr[1]==':' && ptr[2]=='\\') ptr+=3;
    else if (ptr[0]=='\\') ptr++;

    if (subst_sys && config.emusys) {
        char config_name[6+1+3+1];
#if 0	/*
	 * NOTE: as the method used in fatfs.c can't handle multiple
	 * files to be faked, we can't do it here, because this would
	 * confuse more than doing anything valuable --Hans 2001/03/16
	 */

	/* skip the D for DCONFIG.SYS in DR-DOS */
	if (toupperDOS(ptr[0]) == 'D') ptr++;
#endif
        ext_fix(config.emusys);
        sprintf(config_name, "CONFIG.%-3s", config.emusys);
        if (subst_sys == 1 && !strequalDOS(ptr, config_name) &&
            !strequalDOS(ptr, "CONFIG.SYS")) {
            subst_sys = 0;
        } else if (strequalDOS(ptr, "CONFIG.SYS")) {
            strcpy(ptr, config_name);
	    d_printf("DISK: Substituted %s for CONFIG.SYS\n", ptr);
	    subst_sys = 1;
	}
    }
}

void call_cmd(const char *cmd, int maxargs, const struct cmd_db *cmdtab,
	cmdprintf_func *printf)
{
   int argc1;
   char **argv1;
   char *tmpcmd;
   void (*cmdproc)(int, char *[]);
   const struct cmd_db *cmdp;

   tmpcmd = strdup(cmd);
   if (!tmpcmd) {
      if (printf) (*printf)("out of memory\n");
      return;
   }
   argv1 = malloc(maxargs * sizeof(char *));
   if (!argv1) {
      if (printf) (*printf)("out of memory\n");
      free(tmpcmd);
      return;
   };
   argc1 = argparse(tmpcmd, argv1, maxargs);
   if (argc1 < 1) {
      free(tmpcmd);
      free(argv1);
      return;
   }
   for (cmdp = cmdtab, cmdproc = NULL; cmdp->cmdproc; cmdp++) {
      if (!memcmp(cmdp->cmdname, argv1[0], strlen(argv1[0])+1)) {
         cmdproc = cmdp->cmdproc;
         break;
      }
   }
   if (!cmdproc) {
      if (printf) (*printf)("Command %s not found\n", argv1[0]);
   }
   else (*cmdproc)(argc1, argv1);
   free(tmpcmd);
   free(argv1);
}

void sigalarm_onoff(int on)
{
  static struct itimerval itv_old;
#ifdef X86_EMULATOR
  static struct itimerval itv_oldp;
#endif
  static struct itimerval itv;
  static volatile int is_off = 0;
  if (on) {
    if (is_off--) {
	setitimer(ITIMER_REAL, &itv_old, NULL);
#ifdef X86_EMULATOR
	setitimer(ITIMER_VIRTUAL, &itv_oldp, NULL);
#endif
    }
  }
  else if (!is_off++) {
    itv.it_interval.tv_sec = itv.it_interval.tv_usec = 0;
    itv.it_value = itv.it_interval;
    setitimer(ITIMER_REAL, &itv, &itv_old);
#ifdef X86_EMULATOR
    setitimer(ITIMER_VIRTUAL, &itv, &itv_oldp);
#endif
  }
}

/* dynamic readlink, adapted from "info libc" */
char *readlink_malloc (const char *filename)
{
  int size = 50;
  int nchars;
  char *buffer;

  do {
    size *= 2;
    nchars = 0;
    buffer = malloc(size);
    if (buffer != NULL) {
      nchars = readlink(filename, buffer, size);
      if (nchars < 0) {
        free(buffer);
        buffer = NULL;
      }
    }
  } while (nchars >= size);
  if (buffer != NULL)
    buffer[nchars] = '\0';
  return buffer;
}

void dosemu_error(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    verror(fmt, args);
    va_end(args);
    gdb_debug();
}

#ifdef USE_DL_PLUGINS
static void *do_dlopen(const char *filename, int flags)
{
    void *handle = dlopen(filename, flags | RTLD_NOLOAD);
    if (handle)
	return handle;
    handle = dlopen(filename, flags);
    if (handle)
	return handle;
    error("%s\n", dlerror());
    return NULL;
}

void *load_plugin(const char *plugin_name)
{
    char *fullname;
    char *p;
    void *handle;
    int ret;
    static int warned;

    if (!warned && dosemu_proc_self_exe &&
	    (p = strrchr(dosemu_proc_self_exe, '/'))) {
	asprintf(&fullname, "%.*s/libplugin_%s.so",
		(int)(p - dosemu_proc_self_exe),
		dosemu_proc_self_exe, plugin_name);
	if (access(fullname, R_OK) == 0 &&
			strcmp(dosemu_plugin_dir_path, DOSEMUPLUGINDIR) == 0) {
		error("running from build dir must be done via script\n");
		warned++;
	}
	free(fullname);

    }
    ret = asprintf(&fullname, "%s/libplugin_%s.so",
	     dosemu_plugin_dir_path, plugin_name);
    assert(ret != -1);
    handle = do_dlopen(fullname, RTLD_LOCAL | RTLD_NOW);
    free(fullname);
    return handle;
}

void close_plugin(void *handle)
{
    dlclose(handle);
}
#else
void *load_plugin(const char *plugin_name)
{
    return NULL;
}
void close_plugin(void *handle)
{
}
#endif

/* http://media.unpythonic.net/emergent-files/01108826729/popen2.c */
int popen2_custom(const char *cmdline, struct popen2 *childinfo)
{
    pid_t p;
    int pipe_stdin[2], pipe_stdout[2];
    sigset_t oset;

    if(pipe(pipe_stdin)) return -1;
    if(pipe(pipe_stdout)) return -1;

//    printf("pipe_stdin[0] = %d, pipe_stdin[1] = %d\n", pipe_stdin[0], pipe_stdin[1]);
//    printf("pipe_stdout[0] = %d, pipe_stdout[1] = %d\n", pipe_stdout[0], pipe_stdout[1]);

    signal_block_async_nosig(&oset);
    p = fork();
    assert(p >= 0);
    if(p == 0) { /* child */
        setsid();	// escape ctty
        close(pipe_stdin[1]);
        dup2(pipe_stdin[0], 0);
        close(pipe_stdin[0]);
        close(pipe_stdout[0]);
        dup2(pipe_stdout[1], 1);
        dup2(pipe_stdout[1], 2);
        close(pipe_stdout[1]);

	/* close signals, then unblock */
	/* SIGIOs should not disturb us because they only go
	 * to the F_SETOWN pid. OTOH disarming SIGIOs will cause this:
	 * https://github.com/stsp/dosemu2/issues/455
	 * ioselect_done(); - not calling.
	 * Instead we mark all SIGIO fds with FD_CLOEXEC.
	 */
	signal_done();
	sigprocmask(SIG_SETMASK, &oset, NULL);

        execl("/bin/sh", "sh", "-c", cmdline, NULL);
        perror("execl");
        _exit(99);
    }
    sigprocmask(SIG_SETMASK, &oset, NULL);
    close(pipe_stdin[0]);
    close(pipe_stdout[1]);
    if (fcntl(pipe_stdin[1], F_SETFD, FD_CLOEXEC) == -1)
      error("fcntl failed to set FD_CLOEXEC '%s'\n", strerror(errno));
    if (fcntl(pipe_stdout[0], F_SETFD, FD_CLOEXEC) == -1)
      error("fcntl failed to set FD_CLOEXEC '%s'\n", strerror(errno));
    childinfo->child_pid = p;
    childinfo->to_child = pipe_stdin[1];
    childinfo->from_child = pipe_stdout[0];
    return 0; 
}

int popen2(const char *cmdline, struct popen2 *childinfo)
{
    int ret = popen2_custom(cmdline, childinfo);
    if (ret)
	return ret;
    ret = sigchld_enable_cleanup(childinfo->child_pid);
    if (ret) {
	error("failed to popen %s\n", cmdline);
	pclose2(childinfo);
	kill(childinfo->child_pid, SIGKILL);
    }
    return ret;
}

int pclose2(struct popen2 *childinfo)
{
    int err;
    if (!childinfo->child_pid)
	return -1;
    err = close(childinfo->from_child);
    err |= close(childinfo->to_child);
    /* kill process too? */
    childinfo->child_pid = 0;
    return err;
}

/*
 * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <string.h>
#if 0
/* disable this and use libbsd */
/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t
strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0) {
	while (--nleft != 0) {
	    if ((*dst++ = *src++) == '\0')
		break;
	}
    }

    /* Not enough room in dst, add NUL and traverse rest of src. */
    if (nleft == 0) {
	if (dsize != 0)
	    *dst = '\0';		/* NUL-terminate dst */
	while (*src++)
	    ;
    }

    return(src - osrc - 1);	/* count does not include NUL */
}
#endif

/* Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com> */
/* modified by stsp */
char *findprog(char *prog, const char *pathc)
{
	static char filename[PATH_MAX];
	char *p;
	char *path;
	char dot[] = ".";
	int proglen, plen;
	struct stat sbuf;
	char *pathcpy;

	/* Special case if prog contains '/' */
	if (strchr(prog, '/')) {
		if ((stat(prog, &sbuf) == 0) && S_ISREG(sbuf.st_mode) &&
		    access(prog, X_OK) == 0) {
			return prog;
		} else {
//			warnx("%s: Command not found.", prog);
			return NULL;
		}
	}

	if (!pathc)
		return NULL;
	path = strdup(pathc);
	assert(path);
	pathcpy = path;

	proglen = strlen(prog);
	while ((p = strsep(&pathcpy, ":")) != NULL) {
		if (*p == '\0')
			p = dot;

		plen = strlen(p);
		while (p[plen-1] == '/')
			p[--plen] = '\0';	/* strip trailing '/' */

		if (plen + 1 + proglen >= sizeof(filename)) {
//			warnx("%s/%s: %s", p, prog, strerror(ENAMETOOLONG));
			free(path);
			return NULL;
		}

		snprintf(filename, sizeof(filename), "%s/%s", p, prog);
		if ((stat(filename, &sbuf) == 0) && S_ISREG(sbuf.st_mode) &&
		    access(filename, X_OK) == 0) {
			free(path);
			return filename;
		}
	}
	(void)free(path);
	return NULL;
}

char *strupper(char *src)
{
  char *s = src;
  for (; *src; src++)
    *src = toupper(*src);
  return s;
}

char *strlower(char *src)
{
  char *s = src;
  for (; *src; src++)
    *src = tolower(*src);
  return s;
}

int replace_string(struct string_store *store, const char *old, char *str)
{
    int i;
    int empty = -1;

    for (i = 0; i < store->num; i++) {
        if (old == store->strings[i]) {
            free(store->strings[i]);
            store->strings[i] = str;
            return 1;
        }
        if (!store->strings[i] && empty == -1)
            empty = i;
    }
    assert(empty != -1);
    store->strings[empty] = str;
    return 0;
}

struct tee_struct {
    FILE *stream[2];
};

static ssize_t tee_write(void *cookie, const char *buf, size_t size)
{
    struct tee_struct *c = cookie;
    fwrite(buf, size, 1, c->stream[0]);
    return fwrite(buf, size, 1, c->stream[1]);
}

static int tee_close(void *cookie)
{
    int ret;
    struct tee_struct *c = cookie;
    fclose(c->stream[0]);
    ret = fclose(c->stream[1]);
    free(c);
    return ret;
}

static cookie_io_functions_t tee_ops = {
    .write = tee_write,
    .close = tee_close,
};

FILE *fstream_tee(FILE *orig, FILE *copy)
{
    FILE *f;
    struct tee_struct *c = malloc(sizeof(struct tee_struct));
    assert(c);
    c->stream[0] = copy;
    c->stream[1] = orig;
    f = fopencookie(c, "w", tee_ops);
    assert(f);
    setbuf(f, NULL);
    return f;
}