/* util.c --

   This file is part of the lzop file compressor.

   Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer
   All Rights Reserved.

   lzop and the LZO library are free software; you can redistribute them
   and/or modify them 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; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

   Markus F.X.J. Oberhumer
   <markus@oberhumer.com>
   http://www.oberhumer.com/opensource/lzop/
 */


#include "conf.h"


/*************************************************************************
// filename util
**************************************************************************/

static const char dir_sep[] = DIR_SEP;

#define fn_is_sep(c)        (strchr(dir_sep,c) != NULL)

#if defined(DOSISH)
#define fn_is_drive(n)      (n[0] && n[1] == ':')
#define fn_skip_drive(n)    (fn_is_drive(n) ? (n) + 2 : (n))
#else
#define fn_is_drive(n)      (0)
#define fn_skip_drive(n)    (n)
#endif


unsigned fn_baseindex(const char *name)
{
    const char *n, *nn;

    n = fn_skip_drive(name);
    for (nn = n; *nn; nn++)
        if (fn_is_sep(*nn))
            n = nn + 1;
    return (unsigned) (n - name);
}


const char *fn_basename(const char *name)
{
    return name + fn_baseindex(name);
}


void fn_addslash(char *name, lzo_bool slash)
{
    char *p;

    name = fn_skip_drive(name);
    p = name + strlen(name);
    while (p > name && fn_is_sep(p[-1]))
        *p-- = 0;
    if (p > name)
    {
        if (slash)
            *p++ = dir_sep[0];
        *p = 0;
    }
}


char *fn_strlwr(char *n)
{
    char *p;
    for (p = n; *p; p++)
        *p = (char) fn_tolower(*p);
    return n;
}


int fn_strcmp(const char *n1, const char *n2)
{
    for (;;)
    {
        if (*n1 != *n2)
        {
            int c = fn_tolower(*n1) - fn_tolower(*n2);
            if (c)
                return c;
        }
        if (*n1 == 0)
            return 0;
        n1++; n2++;
    }
}


lzo_bool fn_is_same_file(const char *n1, const char *n2)
{
    /* very simple... */
    if (fn_strcmp(n1,n2) == 0)
        return 1;
    return 0;
}


int fn_has_suffix(const char *name)
{
    size_t l;
    size_t s;

    name = fn_skip_drive(name);
    l = strlen(name);
    if (l > 4 && name[l-4] == '.')
    {
        if (strcasecmp(&name[l-3],"lzo") == 0)
            return SUFF_LZO;
        if (strcasecmp(&name[l-3],"nrv") == 0)
            return SUFF_NRV;
        if (strcasecmp(&name[l-3],"tar") == 0)
            return SUFF_TAR;
        if (strcasecmp(&name[l-3],"tnv") == 0)
            return SUFF_TNV;
        if (strcasecmp(&name[l-3],"tzo") == 0)
            return SUFF_TZO;
    }

    if (l > 5 && name[l-5] == '.')
    {
        if (strcasecmp(&name[l-4],"lzop") == 0)
            return SUFF_LZOP;
    }

    s = strlen(opt_suffix);
    if (s > 0 && l > s)
    {
        if (strcasecmp(&name[l-s],opt_suffix) == 0)
            return SUFF_USER;
    }

    return SUFF_NONE;
}


int fn_cleanpath(const char *name, char *newname, size_t size, int flags)
{
    size_t l = 0;
    size_t n = 0;
    int slashes = 0;
#define add(x)  if (l >= size) return -1; else newname[l++] = (x)

    name = fn_skip_drive(name);
    while (*name && fn_is_sep(*name))
        name++;
    while (name[n])
    {
        size_t last_n;
        assert(!fn_is_sep(name[n]));
        last_n = n++;
        while (name[n] && !fn_is_sep(name[n]))
            n++;
        if (n - last_n == 2 && name[n-2] == '.' && name[n-1] == '.')
        {
            if (flags & 1)
                return -2;
            while (l > 0) {
                if (newname[--l] == '/')
                {
                    --slashes;
                    break;
                }
            }
            /* newname[l] = 0; printf("del %s\n", newname); */
        }
        else if (n - last_n == 1 && name[n-1] == '.')
        {
            if (flags & 2)
                return -3;
        }
        else
        {
            if (l > 0)
                { ++slashes; add('/'); }
            while (last_n < n)
                { add(name[last_n++]); }
            /* newname[l] = 0; printf("add %s\n", newname); */
        }
        while (name[n] && fn_is_sep(name[n]))
            n++;
    }
    add('\0');
    return slashes;
#undef add
}


/*************************************************************************
// time util
**************************************************************************/

time_t fix_time(time_t t)
{
    if (t == (time_t) -1)
        t = 0;
    return t;
}

time_t get_mtime(const header_t *h)
{
    lzop_ulong_t t;
    t = h->mtime_high;
    t <<= 16; t <<= 16;
    t |= h->mtime_low;
    return t == (lzop_ulong_t)(time_t)t ? (time_t)t : (time_t)0;
}


#if defined(HAVE_LOCALTIME)
void tm2str(char *s, size_t size, const struct tm *tmp)
{
    assert(size >= 18);
#if defined(HAVE_SNPRINTF)
    snprintf(s, size, "%04d-%02d-%02d %02d:%02d:%02d",
            (int) tmp->tm_year + 1900, (int) tmp->tm_mon + 1,
            (int) tmp->tm_mday,
            (int) tmp->tm_hour, (int) tmp->tm_min, (int) tmp->tm_sec);
#else
    sprintf(s, "%04d-%02d-%02d %02d:%02d:%02d",
            (int) tmp->tm_year + 1900, (int) tmp->tm_mon + 1,
            (int) tmp->tm_mday,
            (int) tmp->tm_hour, (int) tmp->tm_min, (int) tmp->tm_sec);
#endif
}
#endif


void time2str(char *s, size_t size, const time_t *t)
{
#if defined(HAVE_LOCALTIME)
    tm2str(s, size, localtime(t));
#elif defined(HAVE_CTIME)
    const char *p = ctime(t);
    assert(size >= 18);
    memset(s, ' ', 16);
    memcpy(s + 2, p + 4, 6);
    memcpy(s + 11, p + 11, 5);
    s[16] = 0;
#else
    s[0] = 0;
    UNUSED(size);
#endif
}


void time2ls(char *s, size_t size, const time_t *t)
{
#if defined(HAVE_LOCALTIME) && defined(HAVE_STRFTIME)
    const char *fmt = "%b %e  %Y";
#if defined(HAVE_DIFFTIME)
#  if (ACC_OS_DOS16)
    /* do not introduce any floating point dependencies */
    long d = (long)current_time - (long)*t;
#  else
    const double d = difftime(current_time, *t);
#  endif
    if (d <= 6 * 30 * 24 * 3600L && d >= -3600L)
        fmt = "%b %e %H:%M";
#endif
    assert(size >= 13);
    if (strftime(s, 13, fmt, localtime(t)) == 13)
        s[0] = 0;
#else
    s[0] = 0;
    UNUSED(size);
    UNUSED(t);
#endif
}


/*************************************************************************
//
**************************************************************************/

lzo_bool file_exists(const char *name)
{
    int fd, r;
    struct stat st;

    /* return true if we can open it */
    fd = open(name, O_RDONLY, 0);
    if (fd >= 0)
    {
        (void) close(fd);
        return 1;
    }

    /* return true if we can stat it */
    r = stat(name, &st);
    if (r != -1)
        return 1;

    /* return true if we can lstat it */
#if defined(HAVE_LSTAT)
    r = lstat(name, &st);
    if (r != -1)
        return 1;
#endif

    return 0;
}


/*************************************************************************
// Some systems have very exotic mode values.
// Convert them to standard values for portable use in our header.
**************************************************************************/

lzo_uint32 fix_mode_for_header(lzo_uint32 mode)
{
    lzo_uint32 m = mode;

    if (mode == 0)
        return 0;

    /* This function can only deal with S_ISREG and S_ISDIR modes. */
    assert(S_ISREG(mode) || S_ISDIR(mode));

#if defined(ACC_OS_TOS) && defined(__PUREC__)
    m = 0444;
    if (mode & S_IWRITE)
        m |= 0200;
    if (mode & S_IEXEC)
        m |= 0111;
    if (S_ISREG(mode))
        m |= 0100000 | 0400;
    else if (S_ISDIR(mode))
        m |= 0040000 | 0700;
#elif defined(DOSISH)
    m &= 0777;
    if (S_ISREG(mode))
        m |= 0100000 | 0400;
    else if (S_ISDIR(mode))
        m |= 0040000 | 0700;
#else
    m &= 07777;
    if (S_ISREG(mode))
        m |= 0100000;
    else if (S_ISDIR(mode))
        m |= 0040000 | 0700;
#endif

    if ((m & 0777) == 0)
        m |= (0644 & ~u_mask);
    return m;
}


MODE_T fix_mode_for_chmod(lzo_uint32 mode)
{
    MODE_T m = (MODE_T) (mode & 07777);
    if ((m & 0777) == 0)
        m |= (MODE_T) (0644 & ~u_mask);
    return m;
}


MODE_T fix_mode_for_ls(lzo_uint32 mode)
{
    MODE_T m = (MODE_T) mode;
    if ((m & 0777) == 0)
        m |= (MODE_T) (0644 & ~u_mask);
    return m;
}


MODE_T fix_mode_for_open(MODE_T mode)
{
    MODE_T m = (MODE_T) (mode & 0666);
#if defined(ACC_OS_TOS) && defined(__PUREC__)
    m = S_IWRITE | S_IREAD;
#else
    if ((m & 0777) == 0)
        m |= (MODE_T) (0644 & ~u_mask);
    m |= 0600;
#endif
    return m;
}


/*************************************************************************
// ls util - adapted from GNU fileutils 3.16
**************************************************************************/

/* Return a character indicating the type of file described by
   file mode BITS:
   'd' for directories
   'b' for block special files
   'c' for character special files
   'm' for multiplexor files
   'l' for symbolic links
   's' for sockets
   'p' for fifos
   '-' for regular files
   '?' for any other file type.  */

static char ftypelet(unsigned mode)
{
    if (S_ISREG(mode)) return '-';
    if (S_ISDIR(mode)) return 'd';
#ifdef S_ISBLK
    if (S_ISBLK(mode)) return 'b';
#endif
#ifdef S_ISCHR
    if (S_ISCHR(mode)) return 'c';
#endif
#ifdef S_ISFIFO
    if (S_ISFIFO(mode)) return 'p';
#endif
#ifdef S_ISLNK
    if (S_ISLNK(mode)) return 'l';
#endif
#ifdef S_ISSOCK
    if (S_ISSOCK(mode)) return 's';
#endif
#ifdef S_ISMPC
    if (S_ISMPC(mode)) return 'm';
#endif
#ifdef S_ISNWK
    if (S_ISNWK(mode)) return 'n';
#endif
#ifdef S_ISOFD
    if (S_ISOFD(mode)) return 'M';     /* Cray migrated dmf file.  */
#endif
#ifdef S_ISOFL
    if (S_ISOFL(mode)) return 'M';     /* Cray migrated dmf file.  */
#endif
    return '?';
}


/* Set the read, write, and execute flags. */
static void set_rwx(unsigned mode, char *str)
{
    str[0] = (char) ((mode & 4) ? 'r' : '-');
    str[1] = (char) ((mode & 2) ? 'w' : '-');
    str[2] = (char) ((mode & 1) ? 'x' : '-');
}


/* Set the 's' and 't' flags in file attributes string. */
static void set_st(unsigned mode, char *str)
{
#ifdef S_ISUID
    if (mode & S_ISUID)
        str[3] = (char) (str[3] == 'x' ? 's' : 'S');
#endif
#ifdef S_ISGID
    if (mode & S_ISGID)
        str[6] = (char) (str[6] == 'x' ? 's' : 'S');
#endif
#ifdef S_ISVTX
    if (mode & S_ISVTX)
        str[9] = (char) (str[9] == 'x' ? 't' : 'T');
#endif
    UNUSED(mode);
    UNUSED(str);
}


void mode_string(MODE_T m, char *str)
{
    unsigned mode = (unsigned) m;
    str[0] = ftypelet(mode);
    set_rwx((mode & 0700) >> 6, &str[1]);
    set_rwx((mode & 0070) >> 3, &str[4]);
    set_rwx((mode & 0007) >> 0, &str[7]);
    set_st(mode, str);
}


/*************************************************************************
// adapted from the djgpp port of cpio 2.4.2 by Eli Zaretskii
**************************************************************************/

#if defined(DOSISH)

/* If the original file name includes characters illegal for MS-DOS and
   MS-Windows, massage it to make it suitable and return a pointer to
   static storage with a new name.  If the original name is legit,
   return it instead.  Return NULL if the rename failed (shouldn't happen).

   The file name changes are: (1) any name that is reserved by a DOS
   device driver (such as 'prn.txt' or 'aux.c') is prepended with a '_';
   and (2) illegal characters are replaced with '_' or '-' (well, almost;
   look at the code below for details).  */

#define is_slash(x)     fn_is_sep(x)

char *maybe_rename_file(const char *original_name)
{
    static char dosified_name[PATH_MAX+1];
    static const char illegal_chars_dos[] = ".+, ;=[]|<>\":?*";
    const char *illegal_chars = illegal_chars_dos;
    int idx, dot_idx;
    const char *s = original_name;
    char *d = dosified_name;

    /* Support for Win32 VFAT systems, when available.  */
#if defined(__DJGPP__)
    if (_use_lfn(original_name))
        illegal_chars = illegal_chars_dos + 8;
#elif (ACC_OS_WIN32 || ACC_OS_WIN64 || ACC_OS_CYGWIN)
    illegal_chars = illegal_chars_dos + 8;
#endif

    /* Get past the drive letter, if any. */
    if (fn_is_drive(s))
    {
        *d++ = *s++;
        *d++ = *s++;
    }

    for (idx = 0, dot_idx = -1; *s; s++, d++)
    {
        if (strchr(illegal_chars, *s))
        {
            /* Dots are special on DOS: it doesn't allow them as the leading
             character, and a file name cannot have more than a single dot.
             We leave the first non-leading dot alone, unless it comes too
             close to the beginning of the name: we want sh.lex.c to become
             sh_lex.c, not sh.lex-c.  */
            if (*s == '.')
            {
                if (idx == 0
                    && (is_slash(s[1]) || s[1] == '\0'
                        || (s[1] == '.' && (is_slash(s[2]) || s[2] == '\0'))))
                {
                    /* Copy "./" and "../" verbatim.  */
                    *d++ = *s++;
                    if (*s == '.')
                        *d++ = *s++;
                    *d = *s;
                }
                else if (idx == 0)
                    *d = '_';
                else if (dot_idx >= 0)
                {
                    if (dot_idx < 5) /* 5 is merely a heuristic ad-hoc'ery */
                    {
                        d[dot_idx - idx] = '_'; /* replace previous dot */
                        *d = '.';
                    }
                    else
                        *d = '-';
                }
                else
                    *d = '.';

                if (*s == '.')
                    dot_idx = idx;
            }
            else if (*s == '+' && s[1] == '+')
            {
                if (idx - 2 == dot_idx) /* .c++, .h++ etc. */
                {
                    *d++ = 'x';
                    *d   = 'x';
                }
                else
                {
                    /* libg++ etc.  */
                    memcpy (d, "plus", 4);
                    d += 3;
                }
                s++;
                idx++;
            }
            else
                *d = '_';
        }
        else
            *d = *s;
        if (is_slash(*s))
        {
            idx = 0;
            dot_idx = -1;
        }
        else
            idx++;
    }

    *d = '\0';

#if defined(S_ISCHR)
    /* We could have a file in an archive whose name is reserved
     on MS-DOS by a device driver.  Trying to extract such a
     file would fail at best and wedge us at worst.  We need to
     rename such files.  */

    if (idx > 0)
    {
        struct stat st_buf;
        char *base = d - idx;
        int i = 0;

        /* The list of character devices is not constant: it depends on
         what device drivers did they install in their CONFIG.SYS.
         'stat' will tell us if the basename of the file name is a
         characer device.  */
        while (stat(base, &st_buf) == 0 && S_ISCHR(st_buf.st_mode))
        {
            size_t blen = strlen(base);

            /* I don't believe any DOS character device names begin with a
             '_'.  But in case they invent such a device, let us try twice.  */
            if (++i > 2)
                return (char *)0;

            /* Prepend a '_'.  */
            memmove(base + 1, base, blen + 1);
            base[0] = '_';
        }
    }
#endif

    return dosified_name;
}

#endif /* DOSISH */


/*************************************************************************
//
**************************************************************************/

#if (ACC_CC_MSC && (_MSC_VER >= 1000 && _MSC_VER < 1200))
   /* avoid '-W4' warnings in <windows.h> */
#  pragma warning(disable: 4201 4214 4514)
#endif
#if (ACC_CC_MSC && (_MSC_VER >= 1300))
   /* avoid '-Wall' warnings in <windows.h> */
#  pragma warning(disable: 4255)
#endif

#define ACC_WANT_ACC_INCI_H 1
#define ACC_WANT_ACCLIB_GETOPT 1
#define ACC_WANT_ACCLIB_HALLOC 1
#define ACC_WANT_ACCLIB_HMEMCPY 1
#define ACC_WANT_ACCLIB_HSREAD 1
#define ACC_WANT_ACCLIB_MISC 1
#define ACC_WANT_ACCLIB_WILDARGV 1
#if (ACC_HAVE_MM_HUGE_PTR)
#  define ACC_WANT_ACCLIB_HREAD 1
#endif
#include "miniacc.h"
#undef ACC_WANT_ACC_INCI_H
#undef ACC_WANT_ACCLIB_HALLOC
#undef ACC_WANT_ACCLIB_MISC
#undef ACC_WANT_ACCLIB_WILDARGV
#undef ACC_WANT_ACCLIB_HREAD

#if (__ACCLIB_REQUIRE_HMEMCPY_CH) && !defined(__ACCLIB_HMEMCPY_CH_INCLUDED)
#  define ACC_WANT_ACCLIB_HMEMCPY 1
#  include "acc/acclib/hmemcpy.ch"
#  undef ACC_WANT_ACCLIB_HMEMCPY
#endif


/* vim:set ts=4 sw=4 et: */
