/*
 *  tools.c -- misc utils
 *
 *  tools.c is a part of binkd project
 *
 *  Copyright (C) 1996-1998  Dima Maloff, 5047/13
 *
 *  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. See COPYING.
 */

/*
 * $Id: tools.c,v 2.22.2.4 2006/08/09 07:09:49 gul Exp $
 *
 * $Log: tools.c,v $
 * Revision 2.22.2.4  2006/08/09 07:09:49  gul
 * cosmetic fix
 *
 * Revision 2.22.2.3  2004/10/21 17:02:05  gul
 * Rename trunc() -> trunc_file() due to conflicts under OS/2 EMX and SuSE
 *
 * Revision 2.22.2.2  2003/12/02 14:24:13  gul
 * Use vsnprintf() if exists
 *
 * Revision 2.22.2.1  2003/06/30 22:46:03  hbrew
 * Print only binkd name (without path) in error messages
 *
 * Revision 2.22  2003/04/02 13:12:57  gul
 * Try to use workaround for buggy windows time functions (timezone)
 *
 * Revision 2.21  2003/03/31 22:47:22  gul
 * remove workaround for msvc localtime() bug
 *
 * Revision 2.20  2003/03/31 21:48:59  gul
 * Avoid infinite recursion
 *
 * Revision 2.19  2003/03/31 21:27:12  gul
 * Avoid infinite recursion
 *
 * Revision 2.18  2003/03/31 20:28:24  gul
 * safe_localtime() and safe_gmtime() functions
 *
 * Revision 2.17  2003/03/31 19:35:16  gul
 * Clean semaphores usage
 *
 * Revision 2.16  2003/03/31 18:22:12  gul
 * Use snprintf() instead of sprintf()
 *
 * Revision 2.15  2003/03/10 10:57:45  gul
 * Extern declarations moved to header files
 *
 * Revision 2.14  2003/03/05 13:21:51  gul
 * Fix warnings
 *
 * Revision 2.13  2003/02/28 20:39:08  gul
 * Code cleanup:
 * change "()" to "(void)" in function declarations;
 * change C++-style comments to C-style
 *
 * Revision 2.12  2002/11/14 09:46:59  gul
 * Minor BINKDW9X fix
 *
 * Revision 2.11  2002/11/12 16:55:58  gul
 * Run as service under win9x
 *
 * Revision 2.10  2002/05/06 19:25:40  gul
 * new keyword inboundCase in config
 *
 * Revision 2.9  2002/03/20 15:31:19  gul
 * ftrans bugfix
 *
 * Revision 2.8  2002/03/20 14:32:14  gul
 * Bugfix in ftrans
 *
 * Revision 2.7  2002/03/07 14:29:52  gul
 * print PID as unsigned to bsy/csy
 *
 * Revision 2.6  2002/02/25 21:33:56  gul
 * Dequote \hh in filenames as \xhh; both \20 and \x20 are space now (FSP-1011)
 *
 * Revision 2.5  2001/11/08 14:04:12  gul
 * bugfix
 *
 * Revision 2.4  2001/11/07 17:01:12  gul
 * Check size of buffer in strnzcpy()
 *
 * Revision 2.3  2001/10/23 08:33:44  gul
 * Change filename (not ext) in incoming *.req if already exists
 *
 * Revision 2.2  2001/09/30 13:49:59  gul
 * Do not put log to socket if run via inetd
 *
 * Revision 2.1  2001/09/24 10:31:39  gul
 * Build under mingw32
 *
 * Revision 2.0  2001/01/10 12:12:39  gul
 * Binkd is under CVS again
 *
 * Revision 1.18  1998/05/05  04:37:54  mff
 * Now we add trailing '\n' to pids in pidfiles, added 2nd arg to strquote(),
 * added strdequote(), strwipe() does not touch ' ' and 0xff any more,
 * added istic(), ispkt(), isarcmail().
 *
 * Revision 1.17  1997/10/23  03:31:49  mff
 * minor bug fixes in mkpath*() (again!), added mutex into Log(),
 * stricmp() -> STRICMP(), getwordx() moved to getw.c, minor fixes.
 *
 * Revision 1.16  1997/06/16  05:38:21  mff
 * Changed Log() -> syslog() loglevel mapping, fixed endless recursion
 * in Log() when calling assert() from Log()
 *
 * Revision 1.15  1997/05/17  11:18:14  mff
 * + get_os_string()
 *
 * Revision 1.14  1997/05/17  08:40:35  mff
 * assert() in Log() changed a bit
 *
 * Revision 1.13  1997/05/08  05:23:20  mff
 * Fixed syslog feature of Log()
 *
 * Revision 1.12  1997/03/28  06:10:12  mff
 * + ed()
 *
 * Revision 1.11  1997/03/15  05:03:32  mff
 * added touch()
 *
 * Revision 1.10  1997/03/09  07:15:30  mff
 * GWX_*, syslog
 *
 * Revision 1.9  1997/02/07  06:45:52  mff
 * getwordx now handles not only first `#' as comment. Though it can be
 * quoted with "" or \. TODO: bitmapped arg to turn this and var substing off
 *
 * Revision 1.8  1997/01/09  05:33:35  mff
 * Now we don't mkdir("") (Thanks to Mike Malakhov)
 *
 * Revision 1.7  1996/12/29  09:47:42  mff
 * Added: create_empty_sem_file()
 *
 * Revision 1.6  1996/12/14  07:13:51  mff
 * log() is now Log()
 *
 * Revision 1.5  1996/12/05  08:01:11  mff
 * Changed parse_args(), removed free_args()
 *
 * Revision 1.4  1996/12/05  07:24:35  mff
 * Format strings for Log() should have no "\n" at the end
 *
 * Revision 1.3  1996/12/05  04:30:14  mff
 * Now we don't mkdir drives
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#if defined (HAVE_VSYSLOG) && defined (HAVE_FACILITYNAMES)
#include <syslog.h>
#endif

#include "assert.h"
#include "Config.h"
#include "sys.h"
#include "common.h"
#include "tools.h"
#include "readdir.h"		       /* for [sys/]utime.h */
#include "readcfg.h"
#include "sem.h"

/*
 * Lowercase the string
 */
char *strlower (char *s)
{
  int i;

  for (i = 0; s[i]; ++i)
    s[i] = tolower (s[i]);
  return s;
}

/*
 * Uppercase the string
 */
char *strupper (char *s)
{
  int i;

  for (i = 0; s[i]; ++i)
    s[i] = toupper (s[i]);
  return s;
}

int mkpath0 (const char *path0)
{
  struct stat sb;

  if (stat (path0, &sb) == -1)
  {
    char *path, *s;
    int retval;

    if ((path = strdup (path0)) == NULL)
    {
      errno = ENOMEM;
      return -1;
    }
    if ((s = max (strrchr (path, '\\'), strrchr (path, '/'))) != 0)
    {
      *(s++) = 0;
      if (*path && mkpath0 (path) == -1)
      {
	free (path);
	return -1;
      }
    }
    else
      s = path;

    if (path0[strlen (path0) - 1] == ':')
      retval = 0;		       /* we found the device part of the
				        * path */
    else
      retval = MKDIR (path0);

    free (path);
    return retval;
  }
  else if (sb.st_mode & S_IFDIR)
  {
    return 0;
  }
  else
  {
    errno = ENOTDIR;
    return -1;
  }
}

int mkpath (char *s)
{
  char path[MAXPATHLEN];

  strnzcpy (path, s, MAXPATHLEN);
  if ((s = max (strrchr (path, '\\'), strrchr (path, '/'))) == 0)
    return 0;
  *s = 0;
  return mkpath0 (path);
}

unsigned long rnd (void)
{
  static int i;

  if (!i)
  {
    i = 1;
    srand (time (0));
  }
  return (time (0) + rand ()) & 0xFFFFFFFFul;
}

/*
 * 1 -- created, 0 -- already busy
 */
int create_empty_sem_file (char *name)
{
  int h;

  if ((h = open (name, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1)
    return 0;
  close (h);
  return 1;
}

int create_sem_file (char *name)
{
  int h, i;
  char buf[16];

  if ((h = open (name, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1)
  { Log (5, "Can't create %s: %s", name, strerror(errno));
    return 0;
  }
#ifdef HAVE_SNPRINTF
  snprintf (buf, sizeof (buf), "%u\n", (int) getpid ());
#else
  sprintf (buf, "%u\n", (int) getpid ());
#endif
  if ((i = write(h, buf, strlen(buf))) != (int)strlen(buf))
  { if (i == -1)
      Log (2, "Can't write to %s (handle %d): %s", name, h, strerror(errno));
    else
      Log (2, "Can't write %d bytes to %s, wrote only %d", strlen(buf), name, i);
  }
  close (h);
  Log (5, "created %s", name);
  return 1;
}

#if defined(EMX) || defined(__WATCOMC__)
#include <malloc.h>		       /* for _heapchk() */
#endif

struct tm *safe_gmtime(time_t *t, struct tm *tm)
{
  threadsafe(memcpy(tm, gmtime(t), sizeof(*tm)));
  return tm;
}

#ifdef WIN32
#include <windows.h>

static void stime_to_tm(const SYSTEMTIME *stime, struct tm *tm)
{
  tm->tm_year = stime->wYear-1900;
  tm->tm_mon  = stime->wMonth-1;
  tm->tm_mday = stime->wDay;
  tm->tm_wday = stime->wDayOfWeek;
  tm->tm_hour = stime->wHour;
  tm->tm_min  = stime->wMinute;
  tm->tm_sec  = stime->wSecond;
}

static void tm_to_stime(const struct tm *tm, SYSTEMTIME *stime)
{
  stime->wYear         = tm->tm_year+1900;
  stime->wMonth        = tm->tm_mon+1;
  stime->wDay          = tm->tm_mday;
  stime->wDayOfWeek    = tm->tm_wday;
  stime->wHour         = tm->tm_hour;
  stime->wMinute       = tm->tm_min;
  stime->wSecond       = tm->tm_sec;
  stime->wMilliseconds = 0;
}

static int safe_cmptime(const struct tm *tm1, const struct tm *tm2)
{
  if (tm1->tm_year > tm2->tm_year) return  1;
  if (tm1->tm_year < tm2->tm_year) return -1;
  if (tm1->tm_mon  > tm2->tm_mon ) return  1;
  if (tm1->tm_mon  < tm2->tm_mon ) return -1;
  if (tm1->tm_mday > tm2->tm_mday) return  1;
  if (tm1->tm_mday < tm2->tm_mday) return -1;
  if (tm1->tm_hour > tm2->tm_hour) return  1;
  if (tm1->tm_hour < tm2->tm_hour) return -1;
  return 0;
}

time_t safe_time(void)
{
  /* gmtime(t) should be SystemTime */
  SYSTEMTIME stime;
  struct tm utctm, tm;
  time_t t;
  int i;

  GetSystemTime(&stime);
  stime_to_tm(&stime, &utctm);
  t = time(NULL);
  t -= (t-utctm.tm_sec)%60;
  for (i=0; i<24; i++) {
    safe_gmtime(&t, &tm);
    if (tm.tm_hour == utctm.tm_hour) return t;
    t += safe_cmptime(&tm, &utctm) > 0 ? -3600 : 3600;
  }
  return t;
}

struct tm *safe_localtime(time_t *t, struct tm *tm)
{
  FILETIME utcftime, localftime;
  SYSTEMTIME stime;

  safe_gmtime(t, tm);
  /* convert gmtime to localtime */
  tm_to_stime(tm, &stime);
  SystemTimeToFileTime(&stime, &utcftime);
  FileTimeToLocalFileTime(&utcftime, &localftime);
  FileTimeToSystemTime(&localftime, &stime);
  stime_to_tm(&stime, tm);
  return tm;
}
#else
struct tm *safe_localtime(time_t *t, struct tm *tm)
{
  threadsafe(memcpy(tm, localtime(t), sizeof(*tm)));
  return tm;
}

time_t safe_time(void)
{
  return time(NULL);
}
#endif

#if defined(HAVE_THREADS) || defined(AMIGA)
MUTEXSEM LSem = 0;
#endif

void Log (int lev, char *s,...)
{
  static int first_time = 1;
  char timebuf[60];
  time_t t;
  struct tm tm;
  va_list ap;
  static const char *marks = "!?+-";
  char ch = (0 <= lev && lev < (int) strlen (marks)) ? marks[lev] : ' ';

  if (first_time == 1)
  {
    first_time = 2;
    InitSem (&LSem);
  }

  t = safe_time();
  safe_localtime (&t, &tm);

  if (lev <= conlog
#if defined(UNIX) || defined(OS2) || defined(AMIGA)
      && !inetd_flag
#endif
     )
  {
    strftime (timebuf, sizeof (timebuf), "%H:%M", &tm);
    LockSem (&LSem);
    fprintf (stderr, "%30.30s\r%c %s [%i] ", " ", ch, timebuf, (int) PID ());
    va_start (ap, s);
    vfprintf (stderr, s, ap);
    va_end (ap);
    if (lev >= 0)
      fputc ('\n', stderr);
    ReleaseSem (&LSem);
    if (lev < 0)
      return;
  }

  if (lev <= loglevel && *logpath)
  {
    FILE *logfile = 0;
    int i;

    LockSem (&LSem);
    for (i = 0; logfile == 0 && i < 10; ++i)
    {
      logfile = fopen (logpath, "a");
    }
    if (logfile)
    {
      if (first_time)
      {
	fputc ('\n', logfile);
	first_time = 0;
      }
      strftime (timebuf, sizeof (timebuf), "%d %b %H:%M:%S", &tm);
      fprintf (logfile, "%c %s [%i] ", ch, timebuf, (int) PID ());
      va_start (ap, s);
      vfprintf (logfile, s, ap);
      va_end (ap);
      fputc ('\n', logfile);
      fclose (logfile);
    }
    else
      fprintf (stderr, "Cannot open %s: %s!\n", logpath, strerror (errno));
    ReleaseSem (&LSem);
  }
#ifdef WIN32
#ifdef BINKDW9X
  if(!lev)
#else
  if((!lev)&&(isService))
#endif
  {
    char tmp[256];
    va_start (ap, s);
#ifdef HAVE_SNPRINTF
    vsnprintf (tmp, sizeof(tmp), s, ap);
#else
    vsprintf (tmp, s, ap);
#endif
    va_end (ap);
    MessageBox(NULL, tmp, MYNAME, MB_OK|MB_ICONSTOP|0x00200000L|MB_SYSTEMMODAL|MB_SETFOREGROUND);
  }
#endif

#if defined (HAVE_VSYSLOG) && defined (HAVE_FACILITYNAMES)
  if (lev <= loglevel && syslog_facility >= 0)
  {
    static int opened = 0;
    static int log_levels[] =
    {
      /* Correspondence between binkd's loglevel and syslog's priority */
      LOG_ERR,			       /* 0 */
      LOG_WARNING,		       /* 1 */
      LOG_NOTICE,		       /* 2 */
      LOG_INFO,			       /* 3 */
      LOG_INFO,			       /* 4 */
      LOG_INFO,			       /* 5 */
      LOG_INFO,			       /* 6 */
      LOG_DEBUG			       /* other */
    };

    if (!opened)
    {
      opened = 1;
      openlog ("binkd", LOG_PID, syslog_facility);
    }

    if (lev < 0 || lev >= sizeof log_levels / sizeof (int))
      lev = sizeof log_levels / sizeof (int) - 1;

    va_start (ap, s);
    vsyslog (log_levels[lev], s, ap);
    va_end (ap);

  }
#endif

  if (lev == 0)
    exit (1);

#if defined(EMX) || defined(__WATCOMC__)
/*
  assert (_heapchk () == _HEAPOK || _heapchk () == _HEAPEMPTY);
*/
#endif
}

int o_memicmp (const void *s1, const void *s2, size_t n)
{
  int i;

  for (i = 0; i < (int) n; ++i)
    if (tolower (((char *) s1)[i]) != tolower (((char *) s2)[i]))
      return (tolower (((char *) s1)[i]) - tolower (((char *) s2)[i]));

  return 0;
}

int o_stricmp (const char *s1, const char *s2)
{
  int i;

  for (i = 0;; ++i)
  {
    if (tolower (s1[i]) != tolower (s2[i]))
      return (tolower (s1[i]) - tolower (s2[i]));
    if (!s1[i])
      return 0;
  }
}

int o_strnicmp (const char *s1, const char *s2, size_t n)
{
  int i;

  for (i = 0; i < (int) n; ++i)
  {
    if (tolower (s1[i]) != tolower (s2[i]))
      return (tolower (s1[i]) - tolower (s2[i]));
    if (!s1[i])
      return 0;
  }
  return 0;
}

/*
 * Quotes all special chars. free() it's retval!
 */
char *strquote (char *s, int flags)
{
  char *r = xalloc (strlen (s) * 4 + 1);
  int i;

  for (i = 0; *s; ++s)
  {
    if (((flags & SQ_CNTRL) && iscntrl (*s)) ||
	((flags & SQ_SPACE) && isspace (*s)) ||
	(*s == '\\'))
    {
      sprintf (r + i, "\\x%02x", *(unsigned char *) s);
      i += 4;
    }
    else
      r[i++] = *s;
  }
  r[i] = 0;
  return r;
}

/*
 * Reverse for strquote(), free it's return value!
 */
char *strdequote (char *s)
{
  char *r = xstrdup (s);
  int i = 0;

  while (*s)
  {
#define XD(x) (isdigit(x) ? ((x)-'0') : (tolower(x)-'a'+10))
    if (s[0] == '\\' && s[1] == 'x' && isxdigit (s[2]) && isxdigit (s[3]))
    {
      r[i++] = XD (s[2]) * 16 + XD (s[3]);
      s += 4;
    }
    else if (s[0] == '\\' && isxdigit (s[1]) && isxdigit (s[2]))
    {
      r[i++] = XD (s[1]) * 16 + XD (s[2]);
      s += 3;
    }
#undef XD
    else
      r[i++] = *(s++);
  }
  r[i] = 0;
  return r;
}

/*
 * Makes file system-safe names by wiping suspicious chars with '_'
 */
char *strwipe (char *s)
{
  int i;

  for (i = 0; s[i]; ++i)
    if ((iscntrl (s[i]) || s[i] == '\\' || s[i] == '/' || s[i] == ':')
#ifdef WIN32 /* ungly hack */
        && ((s[i] & 0x80) == 0)
#endif
       )
      s[i] = '_';
  return s;
}

/*
 * Copyes not more than len chars from src into dst, but, unlike strncpy(),
 * it appends '\0' even if src is longer than len.
 */
char *strnzcpy (char *dst, const char *src, size_t len)
{
  dst[len - 1] = 0;
  return strncpy (dst, src, len - 1);
}

char *strnzcat (char *dst, const char *src, size_t len)
{
  size_t x = strlen (dst);

  if (len <= x) return dst;
  return strnzcpy (dst + x, src, len - x);
}

/*
 * Splits args ASCIIZ string onto argc separate words,
 * saves them as argv[0]...argv[argc-1]. Logs error
 * "ID: cannot parse args", if args containes less than argc words.
 */
int parse_args (int argc, char *argv[], char *src, char *ID)
{
  int i = 0;

  while (i < argc)
  {
    while (*src && isspace (*src))
      ++src;
    if (!*src)
      break;
    argv[i] = src;
    while (*src && !isspace (*src))
      ++src;
    ++i;
    if (!*src)
      break;
    src++[0] = 0;
  }
  if (i < argc)
  {
    Log (1, "%s: cannot parse args", ID, src);
    return 0;
  }
  else
    return 1;
}

/*
 * Set times for a file, 0 == success, -1 == error
 */
int touch (char *file, time_t t)
{
#ifndef OS2
  struct utimbuf utb;

  utb.actime = utb.modtime = t;
  return utime (file, &utb);
#else /* FastEcho deletes *.bsy by ctime :-( */
  APIRET r;
  FILESTATUS3 buf;
  struct stat st;

  if ((r = stat(file, &st)) == 0)
  {
    struct tm tm;
    safe_localtime(&t, &tm);
    buf.fdateCreation.day=buf.fdateLastAccess.day=buf.fdateLastWrite.day=
        tm.tm_mday;
    buf.fdateCreation.month=buf.fdateLastAccess.month=buf.fdateLastWrite.month=
        tm.tm_mon+1;
    buf.fdateCreation.year=buf.fdateLastAccess.year=buf.fdateLastWrite.year=
        tm.tm_year-80;
    buf.ftimeCreation.twosecs=buf.ftimeLastAccess.twosecs=buf.ftimeLastWrite.twosecs=
        tm.tm_sec/2;
    buf.ftimeCreation.minutes=buf.ftimeLastAccess.minutes=buf.ftimeLastWrite.minutes=
        tm.tm_min;
    buf.ftimeCreation.hours=buf.ftimeLastAccess.hours=buf.ftimeLastWrite.hours=
        tm.tm_hour;
    buf.cbFile = buf.cbFileAlloc = st.st_size;
    buf.attrFile = FILE_ARCHIVED | FILE_NORMAL;
    r=DosSetPathInfo(file, FIL_STANDARD, &buf, sizeof(buf), 0);
    if (r == 32)
      r = 0; /* Can't touch opened *.bsy */
    if (r)
      Log (1, "touch: DosSetPathInfo(%s) retcode %d", file, r);
  }
  return (r!=0);
#endif
}

/*
 * Replaces all entries of a in s for b, returns edited line.
 * Returned value must be free()'d. Ignores case.
 * size parameter can be used only if "s" was created by malloc()
 */
char *ed (char *s, char *a, char *b, size_t *size)
{
   size_t i, j, sr, sz;
   size_t len_a=a?strlen(a):0;
   size_t len_b=b?strlen(b):0;
   char *r=s;
   
   if ((!len_a) || (!s)) return r;
   if (!size) 
   {
     sz=strlen(s)+1;
     r=xstrdup(s);
   }
   else sz=*size;
   sr=strlen(r);
   for (i=j=0; i<sr; i++)
   {
     if (tolower(r[i])!=tolower(a[j]))
     {
       i-=j;
       j=0;
       continue;
     }
     j++;
     if (a[j]) continue;
     if (sr-len_a+len_b>=sz)
     {
       if (len_b<64) sz+=64;
       else sz+=len_b;
       r=xrealloc(r, sz);
     }
     i-=len_a-1;
     if (len_a!=len_b)
       memmove(r+i+len_b, r+i+len_a, sr-i-len_a+1);
     if (len_b)
       memcpy(r+i, b, len_b);
     j=0;
     i+=len_b-1;
     sr+=len_b-len_a;
   }
   if (size) *size=sz;
   return r;
}

/*
 * Remove/trucate a file, log this
 */
int delete (char *path)
{
  int rc;

  if ((rc = unlink (path)) != 0)
    Log (1, "error unlinking `%s': %s", path, strerror (errno));
  else
    Log (5, "unlinked `%s'", path);

  return rc;
}

int trunc_file (char *path)
{
  int h;

  if ((h = open (path, O_WRONLY | O_TRUNC)) == -1)
  {
    Log (1, "cannot truncate `%s': %s", path, strerror (errno));
    return -1;
  }
  else
  {
    Log (4, "truncated %s", path);
    close (h);
    return 0;
  }
}

/*
 * Get the string with OS name/version
 */
#ifdef HAVE_UNAME
#include <sys/utsname.h>
#endif

char *get_os_string (void)
{
  static char os[80];

  strcpy (os, "/");

#ifdef HAVE_UNAME
  {
    struct utsname name;

    if (uname (&name) == 0)
    {
      strnzcat (os, name.sysname, sizeof (os));
      return os;
    }
  }
#endif

#ifdef OS
  strnzcat (os, OS, sizeof (os));
#else
  *os = 0;
#endif

  return os;
}

/*
 * Test netnames against some wildcards
 */
int ispkt (char *s)
{
  return pmatch ("*.[Pp][Kk][Tt]", s);
}

int isreq (char *s)
{
  return pmatch ("*.[Rr][Ee][Qq]", s);
}

int istic (char *s)
{
  return pmatch ("*.?[Ii][Cc]", s);
}

int isarcmail (char *s)
{
  /* *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? */
  return (pmatch ("*.[Ss][Uu]?", s) ||
	  pmatch ("*.[Mm][Oo]?", s) ||
	  pmatch ("*.[Tt][Uu]?", s) ||
	  pmatch ("*.[Ww][Ee]?", s) ||
	  pmatch ("*.[Tt][Hh]?", s) ||
	  pmatch ("*.[Ff][Rr]?", s) ||
	  pmatch ("*.[Ss][Aa]?", s));
}

/*
 * Formats and prints argv into buf (for logging purposes)
 */
void print_args (char *buf, size_t sizeof_buf, int argc, char *argv[])
{
  int i, j, quote;

  assert (sizeof_buf > 5);
  *buf = 0;
  for (i = 0; i < argc; ++i)
  {
    quote = 0;
    if (argv[i][0] == 0)
      quote = 1;
    else
      for (j = 0; argv[i][j]; ++j)
	if (argv[i][j] <= ' ')
	{
	  quote = 1;
	  break;
	}

    strnzcat (buf, " ", sizeof_buf);
    if (quote)
      strnzcat (buf, "\"", sizeof_buf);
    strnzcat (buf, argv[i], sizeof_buf);
    if (quote)
      strnzcat (buf, "\"", sizeof_buf);
  }
}

/*
 * Dup argv
 */
char **mkargv (int argc, char **argv)
{
  int i;
  char **p;

  p = (char **) xalloc ((argc + 1) * sizeof (*p));

  for (i = 0; i < argc; i++)
    p[i] = xstrdup (argv[i]);

  p[i] = NULL;

  return p;
}

/*
 * Apply filename case style defined in inboundcase
 */
char *makeinboundcase (char *s)
{
  int i;
  
  switch (inboundcase) 
  {
      case INB_UPPER:
	s = strupper(s);
	Log (8, "uppercase filename");
        break;
      case INB_LOWER:
	s = strlower(s);
	Log (8, "lowercase filename");
        break;
      case INB_MIXED:
        s[0] = toupper (s[0]);
        for (i = 1; s[i]; ++i)
          s[i] = isalnum(s[i-1]) ? tolower(s[i]) : toupper(s[i]);
        Log (8, "mixing filename case");
        break;
      default:
	Log (8, "nothing to do with filename case");
        break;
  }

  return s;
}

/* Extract filename from path */
char *extract_filename(char *s)
{
  char *tmp = max(max(strrchr(s, '\\'), strrchr(s, '/')), strrchr(s, ':'));
  return tmp? tmp+1: s;
}
