//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000-2018 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
//
// This program is free software.  The contents of this file are subject
// to 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.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
//
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
//
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
//////////////////////////////////////////////////////////////////
// unixfsservices.cpp
//
//  Implements cUnixFSServices class in unixfsservices.h
//

#include "core/stdcore.h"
#include "core/corestrings.h"
#include "core/file.h"

#if !IS_UNIX //encase this all in an ifdef so it won't cause compile errors
#    error Must be unix for unixfsservices
#endif

//=========================================================================
// STANDARD LIBRARY INCLUDES
//=========================================================================

#include <ctype.h>
#include <iostream>

#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/time.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#ifdef HAVE_SYS_USTAT_H
#include <sys/ustat.h>
#endif
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif
#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif

#if HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif

#include <pwd.h>

#if IS_REDOX
#    define restrict __restrict__
#endif

#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#endif

#include <grp.h>
#include <fcntl.h>
#include <errno.h>

//=========================================================================
// INCLUDES
//=========================================================================

#include "unixfsservices.h"

// commented out definition of _TWNBITSMAJOR because we should use the
// makedev macro in sys/sysmacros.h for portability.
// except linux has the body of sysmacros.h commented out. why?
// -jeb 7/26/99
// reduced to sys/statfs.h.  Linux is OK and doesn't deserve
// special treatment.  20010317-PH
#ifdef HAVE_SYS_STATFS_H
#include <sys/statfs.h>
#endif //HAVE_SYS_STATFS_H


//=========================================================================
// DEFINES AND MACROS
//=========================================================================

#define TW_SLASH _T('/')

//=========================================================================
// OTHER DIRECTIVES
//=========================================================================

using namespace std;

//=========================================================================
// GLOBALS
//=========================================================================

//=========================================================================
// UTIL FUNCTION PROTOTYES
//=========================================================================

static bool                             util_FileIsExecutable(const TSTRING&);
static bool                             util_PathFind(TSTRING& strFullPath, const TSTRING& strFilename);
static void                             util_RemoveLastPathElement(TSTRING& strPath, TSTRING& strElem);
static bool                             util_GetNextPathElement(const TSTRING& strPathC, TSTRING& strElem, int index);
static void                             util_RemoveDuplicateSeps(TSTRING& strPath);
static bool                             util_TrailingSep(TSTRING& str, bool fLeaveSep);
static void                             util_RemoveTrailingSeps(TSTRING& str);
template<typename T> static inline void util_ZeroMemory(T& obj);

//=========================================================================
// PUBLIC METHOD CODE
//=========================================================================

cUnixFSServices::cUnixFSServices() : mResolveNames(true)
{
}

cUnixFSServices::~cUnixFSServices()
{
}

//=========================================================================
// *** VIRTUAL FUNCTION CODE ***
//=========================================================================

///////////////////////////////////////////////////////////////////////////////
// GetErrString
///////////////////////////////////////////////////////////////////////////////
TSTRING cUnixFSServices::GetErrString() const
{
    TSTRING ret;
    char*   pErrorStr = strerror(errno);
    ret               = pErrorStr;
    return ret;
}


///////////////////////////////////////////////////////////////////////////////
// GetHostID
///////////////////////////////////////////////////////////////////////////////
void cUnixFSServices::GetHostID(TSTRING& name) const
{
    TOSTRINGSTREAM ret;

    ret.setf(ios_base::hex, ios_base::basefield);
#ifdef HAVE_GETHOSTID
    ret << gethostid();
#else
    ret << 999999;
#endif
}

// returns "/" for unix and "\\" for win32
TCHAR cUnixFSServices::GetPathSeparator() const
{
    return '/';
}

#if !USES_DEVICE_PATH
void cUnixFSServices::ReadDir(const TSTRING& strFilename, std::vector<TSTRING>& v, bool bFullPaths) const
{
#else
void cUnixFSServices::ReadDir(const TSTRING& strFilenameC, std::vector<TSTRING>& v, bool bFullPaths) const
{
    TSTRING strFilename = cDevicePath::AsNative(strFilenameC);
#endif

    //Get all the filenames
    DIR* dp = 0;

#if defined(O_DIRECTORY) && defined(O_NOATIME)
    //dfd will be autoclosed by closedir(), should not be explicitly closed.
    int dfd = open(strFilename.c_str(), O_RDONLY | O_DIRECTORY | O_NOATIME);
    if (dfd > 0)
        dp = fdopendir(dfd);
#else
    dp = opendir(strFilename.c_str());
#endif

    if (dp == NULL)
    {
        throw eFSServicesGeneric(strFilename, iFSServices::GetInstance()->GetErrString());
        return;
    }

    struct dirent* d;

    while ((d = readdir(dp)) != NULL)
    {
        if ((strcmp(d->d_name, _T(".")) != 0) && (strcmp(d->d_name, _T("..")) != 0))
        {
            if (bFullPaths)
            {
                //Create the full pathname
                TSTRING strNewName = strFilename;

                // get full path of dir entry
                util_TrailingSep(strNewName, true);
                strNewName += d->d_name;

                // save full path name
                v.push_back(strNewName);
            }
            else
                v.push_back(d->d_name);
        }
    }

    //Close the directory
    closedir(dp);
}

/* needs to and with S_IFMT, check EQUALITY with S_*, and return more types
cFSStatArgs::FileType cUnixFSServices::GetFileType(const cFCOName &filename) 
{
    cFSStatArgs stat;
    Stat(filename, stat);
    return stat.mFileType;
}
*/

void cUnixFSServices::GetCurrentDir(TSTRING& strCurDir) const
{
    TCHAR pathname[iFSServices::TW_MAX_PATH];
    pathname[0] = '\0';
    TCHAR* ret  = getcwd(pathname, sizeof(TCHAR) * iFSServices::TW_MAX_PATH);

    if (ret == NULL)
        throw eFSServicesGeneric(strCurDir, iFSServices::GetInstance()->GetErrString());

    strCurDir = pathname;
}


TSTRING& cUnixFSServices::MakeTempFilename(TSTRING& strName) const
{
    char* pchTempFileName;
    char  szTemplate[iFSServices::TW_MAX_PATH];
    int   fd;

    strncpy(szTemplate, strName.c_str(), iFSServices::TW_MAX_PATH);

#ifdef HAVE_MKSTEMP
    // create temp filename and check to see if mkstemp failed
    if ((fd = mkstemp(szTemplate)) == -1)
    {
        throw eFSServicesGeneric(strName);
    }
    else
    {
        close(fd);
    }
    pchTempFileName = szTemplate;
#else
    fd = 0;
    // create temp filename
    pchTempFileName = mktemp(szTemplate);

    //check to see if mktemp failed
    if (pchTempFileName == NULL || strlen(pchTempFileName) == 0)
    {
        throw eFSServicesGeneric(strName);
    }
#endif

    // change name so that it has the XXXXXX part filled in
    strName = pchTempFileName;

    // Linux creates the file!!  Doh!
    // So I'll always attempt to delete it -bam
    FileDelete(strName.c_str());

    return (strName);
}

void cUnixFSServices::GetTempDirName(TSTRING& strName) const
{
    strName = mTempPath;
}

void cUnixFSServices::SetTempDirName(TSTRING& tmpPath)
{

    mTempPath = tmpPath;
}


#if !USES_DEVICE_PATH
void cUnixFSServices::Stat(const TSTRING& strName, cFSStatArgs& stat) const
{
#else
void cUnixFSServices::Stat(const TSTRING& strNameC, cFSStatArgs& stat) const
{
    TSTRING strName = cDevicePath::AsNative(strNameC);
#endif
    //local variable for obtaining info on file.
    struct stat statbuf;

    int ret;
    ret = lstat(strName.c_str(), &statbuf);

    cDebug d("cUnixFSServices::Stat");
    d.TraceDetail("Executing on file %s (result=%d)\n", strName.c_str(), ret);

    if (ret < 0)
        throw eFSServicesGeneric(strName, iFSServices::GetInstance()->GetErrString());

#if HAVE_STRUCT_STAT_ST_RDEV
    // new stuff 7/17/99 - BAM
    // if the file is not a device set rdev to zero by hand (most OSs will
    // do this for us, but some don't)
    if (!S_ISBLK(statbuf.st_mode) && !S_ISCHR(statbuf.st_mode))
    {
        // must zero memory instead of '= 0' since we don't know the
        // actual type of the object -- could be a struct (requiring '= {0}' )
        util_ZeroMemory(statbuf.st_rdev);
    }
#endif

    //copy information returned by lstat call into the structure passed in
    stat.gid   = statbuf.st_gid;
    stat.atime = statbuf.st_atime;
    stat.ctime = statbuf.st_ctime;
    stat.mtime = statbuf.st_mtime;
    stat.dev   = statbuf.st_dev;

#if HAVE_STRUCT_STAT_ST_RDEV
    stat.rdev = statbuf.st_rdev;
#else
    stat.rdev = 0;
#endif

    stat.ino     = statbuf.st_ino;
    stat.mode    = statbuf.st_mode;
    stat.nlink   = statbuf.st_nlink;
    stat.size    = statbuf.st_size;
    stat.uid     = statbuf.st_uid;
    stat.blksize = statbuf.st_blksize;

#if HAVE_STRUCT_STAT_ST_BLOCKS
    stat.blocks = statbuf.st_blocks;
#else
    stat.blocks = 0;
#endif

    // set the file type
    if (S_ISREG(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_FILE;
    else if (S_ISDIR(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_DIR;
    else if (S_ISLNK(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_SYMLINK;
    else if (S_ISBLK(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_BLOCKDEV;
    else if (S_ISCHR(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_CHARDEV;
    else if (S_ISFIFO(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_FIFO;
#ifdef S_ISSOCK
    else if (S_ISSOCK(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_SOCK;
#endif

#if HAVE_DOOR_CREATE
    else if (S_ISDOOR(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_DOOR;
#endif

#if HAVE_PORT_CREATE
    else if (S_ISPORT(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_PORT;
#endif

#ifdef S_ISNAM
    else if (S_ISNAM(statbuf.st_mode))
        stat.mFileType = cFSStatArgs::TY_NAMED;
#endif

    else
        stat.mFileType = cFSStatArgs::TY_INVALID;
}

void cUnixFSServices::GetMachineName(TSTRING& strName) const
{
#if HAVE_SYS_UTSNAME_H
    struct utsname namebuf;
    if (uname(&namebuf) == -1)
        throw eFSServicesGeneric(strName);
    else
        strName = namebuf.nodename;
#else
    strName = "localhost";
#endif
}

void cUnixFSServices::GetMachineNameFullyQualified(TSTRING& strName) const
{
#if HAVE_SYS_UTSNAME_H
    char buf[256];
    if (gethostname(buf, 256) != 0)
    {
#    if defined(SOLARIS_NO_GETHOSTBYNAME) || !SUPPORTS_NETWORKING
        strName = buf;
        return;
#    else
        struct hostent* ret;
        ret = gethostbyname(buf);

        if (ret != NULL)
        {
            strName = ret->h_name;
            return;
        }
#    endif
    }
#endif
    try
    {
        cUnixFSServices::GetMachineName(strName);
    }
    catch (eFSServices&)
    {
        strName = TSS_GetString(cCore, core::STR_UNKNOWN);
    }
}

bool cUnixFSServices::FileDelete(const TSTRING& strName) const
{
    return (0 == remove(strName.c_str()));
}

bool cUnixFSServices::GetCurrentUserName(TSTRING& strName) const
{
    bool fSuccess = false;

    uid_t          uid = getuid();
    struct passwd* pp  = getpwuid(uid);

    if (pp)
    {
        strName  = pp->pw_name;
        fSuccess = true;
    }
    else
        strName = _T("");

    return (fSuccess);
}


// returns IP address in network byte order
bool cUnixFSServices::GetIPAddress(uint32& uiIPAddress)
{
    bool   fGotAddress = false;
    cDebug d(_T("cUnixFSServices::GetIPAddress"));

#if SUPPORTS_NETWORKING && HAVE_SYS_UTSNAME_H
    struct utsname utsnameBuf;
    if (EFAULT != uname(&utsnameBuf))
    {
        d.TraceDetail("uname returned nodename: %s\n", utsnameBuf.nodename);

        struct hostent* phostent = gethostbyname(utsnameBuf.nodename);

        if (phostent)
        {
            ASSERT(AF_INET == phostent->h_addrtype);
            ASSERT(sizeof(int32) == phostent->h_length);

            if (phostent->h_length)
            {
                if (phostent->h_addr_list[0])
                {
                    int32* pAddress = reinterpret_cast<int32*>(phostent->h_addr_list[0]);
                    uiIPAddress     = *pAddress;
                    fGotAddress     = true;
                }
                else
                    d.TraceError(_T("phostent->h_addr_list[0] was zero"));
            }
            else
                d.TraceError(_T("phostent->h_length was zero"));
        }
        else
            d.TraceError(_T("gethostbyname failed"));
    }
    else
        d.TraceError(_T("uname failed"));
#endif

    return (fGotAddress);
}

bool cUnixFSServices::IsCaseSensitive() const
{
    return true;
}


void cUnixFSServices::SetResolveNames(bool resolve)
{
    mResolveNames = resolve;
}


bool cUnixFSServices::GetUserName(uid_t user_id, TSTRING& tstrUser) const
{
    bool fSuccess = true;

    if (mResolveNames)
    {
        struct passwd* pp = getpwuid(user_id);
        if (pp == NULL)
        {
            fSuccess = false;
            tstrUser = TSS_GetString(cCore, core::STR_UNKNOWN);
        }
        else
            tstrUser = pp->pw_name;
    }
    else
    {
        std::stringstream sstr;
        sstr << user_id;
        tstrUser = sstr.str();
    }

    return (fSuccess);
}


bool cUnixFSServices::GetGroupName(gid_t group_id, TSTRING& tstrGroup) const
{
    bool fSuccess = true;

#if !IS_REDOX
    if (mResolveNames)
    {
        struct group* pg = getgrgid(group_id);
        if (pg == NULL)
        {
            fSuccess  = false;
            tstrGroup = TSS_GetString(cCore, core::STR_UNKNOWN);
        }
        else
            tstrGroup = pg->gr_name;
    }
    else
    {
#endif
        std::stringstream sstr;
        sstr << group_id;
        tstrGroup = sstr.str();
#if !IS_REDOX
    }
#endif

    return (fSuccess);
}


#ifndef S_ISVTX // DOS/DJGPP doesn't have this
#    define S_ISVTX 0
#endif

////////////////////////////////////////////////////////////////////////
// Function name    : cUnixFSServices::ConvertModeToString
// Description      : takes a TSTRING and fills it with an "ls -l" representation
//                    of the object's permission bits ( e.g. "drwxr-x--x" ).
//
// Returns          : void -- no errors are reported
//
// Argument         : uint64 perm -- st_mode from "stat"
// Argument         : TSTRING& tstrPerm -- converted permissions, ls -l style
//
void cUnixFSServices::ConvertModeToString(uint64 perm, TSTRING& tstrPerm) const
{
    TCHAR szPerm[12]; //10 permission bits plus the NULL
    strncpy(szPerm, _T("----------"), 11);

    ASSERT(sizeof(unsigned short) <= sizeof(uint32));
    // We do this in case an "unsigned short" is ever larger than the
    // value we are switching on, since the size of the mode parameter
    // will be unsigned short (whatever that means, for the given platform...)

    // check file type
    switch ((uint32)perm & S_IFMT) //some versions of Unix don't like to switch on
                                   //64 bit values.
    {
    case S_IFDIR:
        szPerm[0] = _T('d');
        break;
    case S_IFCHR:
        szPerm[0] = _T('c');
        break;
    case S_IFBLK:
        szPerm[0] = _T('b');
        break;
    case S_IFIFO:
        szPerm[0] = _T('p');
        break;
    case S_IFLNK:
        szPerm[0] = _T('l');
        break;

#if HAVE_DOOR_CREATE // Solaris doors
    case S_IFDOOR:
        szPerm[0] = _T('D');
        break;
#endif

#if HAVE_PORT_CREATE // Solaris event ports
    case S_IFPORT:
        szPerm[0] = _T('P');
        break;
#endif

#ifdef S_IFNAM
    case S_IFNAM:
        szPerm[0] = _T('n');
        break;
#endif
        break;
    }

    // check owner read and write
    if (perm & S_IRUSR)
        szPerm[1] = _T('r');
    if (perm & S_IWUSR)
        szPerm[2] = _T('w');

    // check owner execute
    if (perm & S_ISUID && perm & S_IXUSR)
        szPerm[3] = _T('s');
    else if (perm & S_IXUSR)
        szPerm[3] = _T('x');
    else if (perm & S_ISUID)
        szPerm[3] = _T('S');

    // check group read and write
    if (perm & S_IRGRP)
        szPerm[4] = _T('r');
    if (perm & S_IWGRP)
        szPerm[5] = _T('w');

    // check group execute
    if (perm & S_ISGID && perm & S_IXGRP)
        szPerm[6] = _T('s');
    else if (perm & S_IXGRP)
        szPerm[6] = _T('x');
    else if (perm & S_ISGID)
        szPerm[6] = _T('l');

    // check other read and write
    if (perm & S_IROTH)
        szPerm[7] = _T('r');
    if (perm & S_IWOTH)
        szPerm[8] = _T('w');

    // check other execute
    if (perm & S_ISVTX && perm & S_IXOTH)
        szPerm[9] = _T('t');
    else if (perm & S_IXOTH)
        szPerm[9] = _T('x');
    else if (perm & S_ISVTX)
        szPerm[9] = _T('T');

    tstrPerm = szPerm;

    return;
}

////////////////////////////////////////////////////////////////////////
// Function name    : cUnixFSServices::Rename
// Description      : Rename a file.  Overwrites newname if it exists.and overwrite is true
//
// Returns          : false if failure, true on success
bool cUnixFSServices::Rename(const TSTRING& strOldName, const TSTRING& strNewName, bool overwrite) const
{
#ifdef _UNICODE
#    error UNICODE Rename not implemented
#endif

    // delete new file if overwriting
    if (overwrite)
        if (access(strNewName.c_str(), F_OK) == 0 && remove(strNewName.c_str()) != 0)
            return false;

    if (rename(strOldName.c_str(), strNewName.c_str()) == 0)
        return true;

    // Note: errno will be set
    return false;
}


bool cUnixFSServices::GetExecutableFilename(TSTRING& strFullPath, const TSTRING& strFilename) const
{
    bool fGotName = false;

    if (strFilename.empty())
        return false;

    // if there is a slash in the filename, it's absolute or relative to cwd
    if (TSTRING::npos != strFilename.find(_T('/')))
    {
        // if absolute path
        if (strFilename[0] == _T('/'))
        {
            strFullPath = strFilename;
            fGotName    = true;
        }
        else // is relative path; find path from cwd
        {
            fGotName = FullPath(strFullPath, strFilename);
        }
    }
    else // it's just a filename: should be found in path
    {
        fGotName = util_PathFind(strFullPath, strFilename);

        TSTRING strFP;
        if (fGotName && FullPath(strFP, strFullPath))
            strFullPath = strFP;
    }

    return (fGotName);
}


///////////////////////////////////////////////////////////////////////////////
// Function name    : cUnixFSServices::FullPath
// Description      :
//
// Return type      : bool
// Argument         :  TSTRING& strFullPath
// Argument         : const TSTRING& strRelPathC
// Argument         : const TSTRING& pathRelFromC
//
// TODO -- is throwing an exception the more appropriate alternative to returning
//      a bool? I think it is ... mdb
///////////////////////////////////////////////////////////////////////////////
bool cUnixFSServices::FullPath(TSTRING& strFullPath, const TSTRING& strRelPathC, const TSTRING& pathRelFromC) const
{
    cDebug d("cUnixFSServices::FullPath");
    d.TraceDebug("strRelPathC = %s, pathRelFromC = %s\n", strRelPathC.c_str(), pathRelFromC.c_str());

    // don't do anything with an empty path
    if (strRelPathC.empty())
        return false;

#if USES_DEVICE_PATH
    TSTRING strRelPath = cDevicePath::AsPosix(strRelPathC); // make non-const temp var
#else
    TSTRING strRelPath = strRelPathC; // make non-const temp var
#endif
    //
    // get base name (where strRelPath will be relative to), which will either be;
    //  1. the root directory if strRelPath is an absolute path
    //  2. pathRelFrom if it's not empty
    //  3. otherwise ( not abs path AND no rel path ) the current working directory
    //

    if (strRelPath[0] == TW_SLASH) // if is absolute path
    {
        if (IsRoot(strRelPath)) // if it's root, don't monkey with it, just return it.
        {
            strFullPath = strRelPath;
            d.TraceDebug("Is root; returning %s\n", strFullPath.c_str());
            return true;
        }
        else
        {
            strFullPath = _T(""); // push root, then add path elements from strRelPathC
                                  // one by one (in while loop below)
        }
    }
    else // is a relative path, so check pathRelFromC
    {
        if (pathRelFromC.empty()) // if we're relative to CWD...
        {
            //
            // get the current working directory
            //
            try
            {
                GetCurrentDir(strFullPath);
#if USES_DEVICE_PATH
                strFullPath = cDevicePath::AsPosix(strFullPath);
#endif
                util_TrailingSep(strFullPath, false);
            }
            catch (eFSServices&)
            {
                return false;
            }

            d.TraceDebug("Creating prefix relative to CWD: %s\n", strFullPath.c_str());
        }
        else // we're relative to a given dir
        {

#if USES_DEVICE_PATH
            strFullPath = cDevicePath::AsPosix(pathRelFromC);
#else
            strFullPath = pathRelFromC;
#endif
            util_RemoveDuplicateSeps(strFullPath);
            util_TrailingSep(strFullPath, false);

            d.TraceDebug("Creating prefix from supplied path: %s\n", strFullPath.c_str());
        }
    }

    //
    // start adding path elements from strRelPath to the base name
    // ( which already has an absolute starting point.  see above. )
    //

    TSTRING strElem;
    int     index = 0;
    while (util_GetNextPathElement(strRelPath, strElem, index++))
    {
        d.TraceDebug("Path element = %s\n", strElem.c_str());
        if (0 == strElem.compare(_T(".")))
        {
            // ignore it
        }
        else if (0 == strElem.compare(_T("..")))
        {
            // go up a dir ( the function takes care of root dir case )
            TSTRING strDummy;
            util_RemoveLastPathElement(strFullPath, strDummy);
        }
        else // just a regular old path element
        {
            strFullPath += TW_SLASH;
            strFullPath += strElem;
        }

        d.TraceDebug("FullPath is now %s\n", strFullPath.c_str());
    }

#if IS_AROS || IS_REDOX
    strFullPath = cDevicePath::AsNative(strFullPath);
#endif

    d.TraceDebug("Done, returning %s\n", strFullPath.c_str());
    return true;
}

///////////////////////////////////////////////////////////////////////////////
// GetStandardBackupExtension()
//
// Returns normal string to append to backup files for this os.
// (e.g. "~" for unix and ".bak" for winos)
///////////////////////////////////////////////////////////////////////////////
const TCHAR* cUnixFSServices::GetStandardBackupExtension() const
{
    return _T(".bak");
}


void cUnixFSServices::Sleep(int nSeconds) const
{
    sleep(nSeconds);
}


////////////////////////////////////////////////////////////////////////////////
// Function name    : IsRoot
// Description      : A root path is all '/'s
//
// Return type      : bool
// Argument         : const TSTRING& strPath
///////////////////////////////////////////////////////////////////////////////
bool cUnixFSServices::IsRoot(const TSTRING& strPath) const
{
    // and empty path is NOT the root path
    if (strPath.empty())
        return false;

    // check to see if all characters are a slash
    for (TSTRING::const_iterator iter = strPath.begin(); iter != strPath.end(); iter++)
    {
        // if we've found a char that's not '/', then it's not the root path
        if (*iter != TW_SLASH)
            return false;
    }

    return true;
}

//*************************************************************************
//*************************************************************************
//                      UTIL FUNCTION CODE
//*************************************************************************
//*************************************************************************


///////////////////////////////////////////////////////////////////////////////
// Function name    : util_PathFind
// Description      :
//      takes single-element executible filename and looks in path env var for it
//      assumes path is colon-delimited string of directories.
//
// Return type      : bool
// Argument         :  TSTRING& strFullPath
// Argument         : const TSTRING& strFilename
///////////////////////////////////////////////////////////////////////////////
bool util_PathFind(TSTRING& strFullPath, const TSTRING& strFilename)
{
    bool fFoundFile = false;

    if (strFilename.empty())
        return false;

    //
    // get the path environment variable
    //
    TCHAR* pszPathVar = getenv("PATH");
    if (pszPathVar != NULL)
    {
        //
        // cycle over characters in path looking for the ':'
        //
        TSTRING strCurPath;
        TCHAR*  pchTemp    = pszPathVar;
        bool    fMorePaths = true;
        do // while still more paths and haven't found file
        {
            //
            // are we at the ':'?
            //
            if (*pchTemp && *pchTemp != _T(':')) // if we're not at the end of the path
            {
                strCurPath += *pchTemp;
            }
            else // we have found the ':'
            {
                //
                // expand current path into a fully qualified path
                // if it's empty, use current directory
                //
                TSTRING strFP;
                if (strCurPath.empty())
                    strCurPath = _T(".");
                if (iFSServices::GetInstance()->FullPath(strFP, strCurPath))
                    strCurPath = strFP;

                //
                // put the file together with the path dir
                //
                TSTRING strFullName = strCurPath;
                util_TrailingSep(strFullName, true);
                strFullName += strFilename;

                //
                // the file must exist and be executable
                //
                if (util_FileIsExecutable(strFullName))
                {
                    strFullPath = strFullName;
                    fFoundFile  = true;
                }
                else
                    strCurPath.erase(); // start over
            }

            //
            // keep searching if we're not at the end of the path string
            //

            if (*pchTemp)
                pchTemp++;
            else
                fMorePaths = false;
        } while (!fFoundFile && fMorePaths);
    }

    return (fFoundFile);
}


///////////////////////////////////////////////////////////////////////////////
// Function name    : util_FileIsExecutable
// Description      : file ( or file a link points to ) must be a regular
//                    file and executable by someone
//
// Return type      : bool
// Argument         : const TSTRING& strFile
///////////////////////////////////////////////////////////////////////////////
bool util_FileIsExecutable(const TSTRING& strFile)
{
    if (strFile.empty())
        return false;

    struct stat s;
    if (stat(strFile.c_str(), &s) < 0) // this call handles links
        return false;

    return (S_ISREG(s.st_mode) && (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))); // can someone execute it?
}


////////////////////////////////////////////////////////////////////////////////
// Function name    : util_RemoveDuplicateSeps
// Description      :
// takes all adjacent slashes and replaces them with a single slash
//      ///root//foo -> /root/foo
//      rel//foo///  -> rel/foo/
//
// Return type      : void
// Argument         : TSTRING& strPath
///////////////////////////////////////////////////////////////////////////////
void util_RemoveDuplicateSeps(TSTRING& strPath)
{
    bool              fLastCharWasSep = false;
    TSTRING::iterator iter            = strPath.begin();
    while (iter != strPath.end())
    {
        bool fErasedChar = false;
        // if we've found a char that's not '/', then it's not the root
        if (*iter == TW_SLASH)
        {
            // if this char is a duplicate sep, erase it
            if (fLastCharWasSep)
            {
                iter        = strPath.erase(iter);
                fErasedChar = true;
            }

            fLastCharWasSep = true;
        }
        else
        {
            fLastCharWasSep = false;
        }

        // don't go past end of string (could happen with erase)
        if (!fErasedChar)
            iter++;
    }
}


//////////////////////////////////////////////////////////////////////////////////
// Function name    : util_RemoveLastPathElement
// Description      :
//      effectively pops off a path element from the end, except for the root dir, where it does nothing
//      it removes any slashes before and after the element
//      ///root//foo/    -> leaves "///root" ("foo"  is strElem)
//      ///root          -> leaves ""        ("root" is strElem)
//      //               -> leaves ""        (""     is strElem)
//
// Return type      : void
// Argument         :  TSTRING& strPath
// Argument         : TSTRING& strElem
/////////////////////////////////////////////////////////////////////////////////
void util_RemoveLastPathElement(TSTRING& strPath, TSTRING& strElem)
{

    // remove all trailing separators
    util_RemoveTrailingSeps(strPath);

    // find the last separator
    TSTRING::size_type lastSep = strPath.rfind(TW_SLASH);

    // if separator was found, take all chars after it
    if (lastSep != TSTRING::npos)
    {
        strElem = strPath.substr(lastSep + 1);
        strPath.resize(lastSep + 1);
    }
    else // no seps in name, take whole string
    {
        // last element
        strElem = strPath;
        strPath.erase();
    }

    // remove all trailing separators
    util_RemoveTrailingSeps(strPath);
}


////////////////////////////////////////////////////////////////////////////////////
// Function name    : util_GetNextPathElement
// Description      :
//      starting from the left side of the path string, returns the index'th path element
//      returns true if the element exists, false if there aren't <index + 1> many elements
//
//      index is ZERO BASED
//
//      2rd element of   ABC/DEF/GH -> GH
//      1st element of //ABC/DEF/GH -> DEF
//
// Return type      : bool : got path element? ( i.e. was there index path elements? )
// Argument         : const TSTRING& strPathC
// Argument         : TSTRING& strElem
// Argument         : int index
/////////////////////////////////////////////////////////////////////////////////
bool util_GetNextPathElement(const TSTRING& strPathC, TSTRING& strElem, int index)
{

    // don't do anything if root or empty
    if (strPathC.empty() || iFSServices::GetInstance()->IsRoot(strPathC))
        return false;

    TSTRING strPath = strPathC; // writable local version

    bool               fMoreSeps = true;
    TSTRING::size_type nextSep, nextNonSep;
    nextSep = nextNonSep = (TSTRING::size_type)-1;
    for (int i = 0; i <= index && fMoreSeps; i++)
    {
        // go past leading separators
        nextNonSep = strPath.find_first_not_of(TW_SLASH, nextSep + 1);

        if (nextNonSep != TSTRING::npos)
        {
            // find index'th slash (start of index'th element)
            nextSep = strPath.find(TW_SLASH, nextNonSep);

            // if we're at the end and we haven't found the index'th element
            // left, then tell the caller that there aren't that many elemnts
            if (nextSep == TSTRING::npos && i < index)
                fMoreSeps = false;
        }
        else
            fMoreSeps = false;
    }

    // get the element and remove it from the path
    if (fMoreSeps)
        strElem = strPath.substr(nextNonSep, nextSep - nextNonSep);

    return (fMoreSeps);
}

/////////////////////////////////////////////////////////////////////////
// Function name    : util_TrailingSep
// Description      : ensure that a path ( fLeaveSep ? "has" : "does not have" ) a trailing slash
//
// Return type      : bool : was there a trailing slash?
// Argument         : TSTRING& str
// Argument         : bool fLeaveSep
/////////////////////////////////////////////////////////////////////////////////
bool util_TrailingSep(TSTRING& str, bool fLeaveSep)
{
    bool fWasSep = false;

    // if there's a trailing sep
    if (!str.empty() && str[str.size() - 1] == TW_SLASH)
    {
        if (!fLeaveSep)
            str.resize(str.size() - 1);
        fWasSep = true;
    }
    else // else no trailing sep
    {
        if (fLeaveSep)
            str += TW_SLASH;
        fWasSep = false;
    }

    return (fWasSep);
}

/////////////////////////////////////////////////////////////////////////
// Function name    : util_RemoveTrailingSeps
// Description      : removes all trailing separators
//
// Return type      : void
// Argument         : TSTRING& str
/////////////////////////////////////////////////////////////////////////////////
void util_RemoveTrailingSeps(TSTRING& str)
{
    while (util_TrailingSep(str, false))
    {
    }
}

template<typename T> static inline void util_ZeroMemory(T& obj)
{
    memset(&obj, 0, sizeof(obj));
}
