/* nwc_fsentry.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2006-2019 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "nwc_fsentry.hh"
#include "grouphash.h"
#include "aguix/util.h"
#include <string.h>  //memcpy
#include "nwc_path.hh"
#include "nwc_os.hh"
#include "aguix/lowlevelfunc.h"
#include "error_count.hh"

namespace NWC
{

  FSEntry::FSEntry( const std::string &fullname )
  {
    _fullname = fullname;
    m_basename_calculated = false;
    _exists = false;
  
    memset( &_statbuf, 0, sizeof( _statbuf ) );
    memset( &_dest_statbuf, 0, sizeof( _dest_statbuf ) );
    _is_link = false;
    _is_corrupt = false;

    if ( _fullname[0] == '/' ) {
      worker_struct_stat stbuf;
    
      if ( worker_lstat( _fullname.c_str(), &stbuf ) == 0 ) {
        _statbuf.size = stbuf.st_size;
        _statbuf.lastaccess = stbuf.st_atime;
        _statbuf.lastmod = stbuf.st_mtime;
        _statbuf.lastchange = stbuf.st_ctime;
        _statbuf.mode = stbuf.st_mode;
        _statbuf.userid = stbuf.st_uid;
        _statbuf.groupid = stbuf.st_gid;
        _statbuf.inode = stbuf.st_ino;
        _statbuf.nlink = stbuf.st_nlink;
        _statbuf.blocks = stbuf.st_blocks;
        _statbuf.rdev = stbuf.st_rdev;
        _statbuf.dev = stbuf.st_dev;

        _exists = true;
      } else {
          ErrorCount::account_errno( errno );
      }

      if ( S_ISLNK( _statbuf.mode ) ) {
        _is_link = true;

        if ( worker_stat( _fullname.c_str(), &stbuf ) == 0 ) {
          _dest_statbuf.size = stbuf.st_size;
          _dest_statbuf.lastaccess = stbuf.st_atime;
          _dest_statbuf.lastmod = stbuf.st_mtime;
          _dest_statbuf.lastchange = stbuf.st_ctime;
          _dest_statbuf.mode = stbuf.st_mode;
          _dest_statbuf.userid = stbuf.st_uid;
          _dest_statbuf.groupid = stbuf.st_gid;
          _dest_statbuf.inode = stbuf.st_ino;
          _dest_statbuf.nlink = stbuf.st_nlink;
          _dest_statbuf.blocks = stbuf.st_blocks;
          _dest_statbuf.rdev = stbuf.st_rdev;
          _dest_statbuf.dev = stbuf.st_dev;
        } else {
          /* corrupt */
          ErrorCount::account_errno( errno );
          _is_corrupt = true;
          _dest_statbuf.mode = 0;  // to prevent corrupted links treated as dirs
          if ( errno == ENOENT ) {
            // Eventl. fuer spaetere Betrachtungen
          }
        }
      }
    }
  }

  FSEntry::FSEntry( const FSEntry &other )
  {
    _fullname = other._fullname;
    m_basename = other.m_basename;
    m_basename_calculated = other.m_basename_calculated;
    _exists = other._exists;
    memcpy( &_statbuf, &other._statbuf, sizeof( other._statbuf ) );
    memcpy( &_dest_statbuf, &other._dest_statbuf, sizeof( other._dest_statbuf ) );
    _is_link = other._is_link;
    _is_corrupt = other._is_corrupt;
  }

  FSEntry::~FSEntry()
  {
  }

  bool FSEntry::isDir( bool follow_symlinks ) const
  {
    bool is_dir = false;

    if ( follow_symlinks == true && _is_link == true && _is_corrupt == false ) {
        if ( S_ISDIR( _dest_statbuf.mode ) ) is_dir = true;
    } else if ( S_ISDIR( _statbuf.mode ) ) is_dir = true;

    return is_dir;
  }

  const std::string &FSEntry::getFullname() const
  {
    return _fullname;
  }

  bool FSEntry::entryExists() const
  {
    return _exists;
  }

  FSEntry *FSEntry::clone() const
  {
    return new FSEntry( *this );
  }

  const std::string &FSEntry::getBasename() const
  {
      if ( m_basename_calculated ) return m_basename;

      const std::string &fn = getFullname();
  
      m_basename_calculated = true;

      std::string::size_type pos = fn.rfind( "/" );
      if ( pos == std::string::npos ) {
          m_basename = fn;
          return fn;
      }

      m_basename = std::string( fn, pos + 1 );

      return m_basename;
  }

  std::string FSEntry::getDirname() const
  {
      const std::string &fn = getFullname();

      return NWC::Path::dirname( fn );
  }

  std::string FSEntry::getExtension() const
  {
      const std::string &bn = getBasename();
  
      std::string::size_type pos = bn.rfind( "." );
      if ( pos == std::string::npos ) {
          return std::string( "" );
      }

      return std::string( bn, pos + 1, std::string::npos );
  }

  bool FSEntry::isReg( bool follow_symlinks ) const
  {
    bool is_reg = false;

    if ( _exists == true ) {
      if ( follow_symlinks == true && _is_link == true && _is_corrupt == false ) {
        if ( S_ISREG( _dest_statbuf.mode ) ) is_reg = true;
      } else if ( S_ISREG( _statbuf.mode ) ) is_reg = true;
    }
    return is_reg;
  }

    bool FSEntry::isLink() const
    {
        return _is_link;
    }

    loff_t FSEntry::stat_size() const
    {
        return _statbuf.size;
    }
    
    loff_t FSEntry::stat_dest_size() const
    {
        return _dest_statbuf.size;
    }

    dev_t FSEntry::stat_dev() const
    {
        return _statbuf.dev;
    }

    dev_t FSEntry::stat_dest_dev() const
    {
        return _dest_statbuf.dev;
    }

    ino_t FSEntry::stat_inode() const
    {
        return _statbuf.inode;
    }

    ino_t FSEntry::stat_dest_inode() const
    {
        return _dest_statbuf.inode;
    }

    time_t FSEntry::stat_lastmod() const
    {
        return _statbuf.lastmod;
    }

    time_t FSEntry::stat_dest_lastmod() const
    {
        return _dest_statbuf.lastmod;
    }

    dev_t FSEntry::stat_rdev() const
    {
        return _statbuf.rdev;
    }

    dev_t FSEntry::stat_dest_rdev() const
    {
        return _dest_statbuf.rdev;
    }

    mode_t FSEntry::stat_mode() const
    {
        return _statbuf.mode;
    }

    mode_t FSEntry::stat_dest_mode() const
    {
        return _dest_statbuf.mode;
    }

    time_t FSEntry::stat_lastaccess() const
    {
        return _statbuf.lastaccess;
    }

    time_t FSEntry::stat_dest_lastaccess() const
    {
        return _dest_statbuf.lastaccess;
    }

    time_t FSEntry::stat_lastchange() const
    {
        return _statbuf.lastchange;
    }

    time_t FSEntry::stat_dest_lastchange() const
    {
        return _dest_statbuf.lastchange;
    }

    nlink_t FSEntry::stat_nlink() const
    {
        return _statbuf.nlink;
    }

    nlink_t FSEntry::stat_dest_nlink() const
    {
        return _dest_statbuf.nlink;
    }

    uid_t FSEntry::stat_userid() const
    {
        return _statbuf.userid;
    }

    uid_t FSEntry::stat_dest_userid() const
    {
        return _dest_statbuf.userid;
    }

    gid_t FSEntry::stat_groupid() const
    {
        return _statbuf.groupid;
    }

    gid_t FSEntry::stat_dest_groupid() const
    {
        return _dest_statbuf.groupid;
    }

    loff_t FSEntry::stat_blocks() const
    {
        return _statbuf.blocks;
    }

    loff_t FSEntry::stat_dest_blocks() const
    {
        return _dest_statbuf.blocks;
    }

    bool FSEntry::isHiddenEntry() const
    {
        if ( AGUIXUtils::starts_with( getBasename(), "." ) ) return true;

        return false;
    }

    bool FSEntry::isBrokenLink() const
    {
        return _is_link && _is_corrupt;
    }

    bool FSEntry::isExe( bool fastTest ) const
    {
        if ( fastTest == true ) {
            if ( ( _statbuf.mode & S_IXUSR ) == S_IXUSR ) return true;
            if ( ( _statbuf.mode & S_IXGRP ) == S_IXGRP ) return true;
            if ( ( _statbuf.mode & S_IXOTH ) == S_IXOTH ) return true;
        } else {
            uid_t uid;
            gid_t gid;
            mode_t m;
            bool e = false;

            if ( isLink() == false ) {
                uid = stat_userid();
                gid = stat_groupid();
                m = stat_mode();
            } else if ( ! isBrokenLink() ) {
                uid = stat_dest_userid();
                gid = stat_dest_groupid();
                m = stat_dest_mode();
            } else {
                // corrupt symlink is not executable
                return false;
            }
    
            if ( uid == geteuid() ) {
                e = ( ( m & S_IXUSR ) != 0 ) ? true : false;
            } else if ( ugdb->isInGroup( gid ) == true ) {
                e = ( ( m & S_IXGRP ) != 0 ) ? true : false;
            } else {
                e = ( ( m & S_IXOTH ) != 0 ) ? true : false;
            }
            return e;
        }
        return false;
    }

    bool FSEntry::getPermissionString( std::string &res ) const
    {
        char str[11];

        if ( isLink() == true ) str[0] = 'l';
        else {
            if ( isBrokenLink() ) str[0] = '?';
            else if ( S_ISDIR( _statbuf.mode ) ) str[0] = 'd';
            else if ( S_ISFIFO( _statbuf.mode ) ) str[0] = 'p';
            else if ( S_ISSOCK( _statbuf.mode ) ) str[0] = 's';
            else if ( S_ISCHR( _statbuf.mode ) ) str[0] = 'c';
            else if ( S_ISBLK( _statbuf.mode ) ) str[0] = 'b';
            else str[0] = '-';
        }
        if ( ( _statbuf.mode & S_IRUSR ) == S_IRUSR ) str[1] = 'r'; else str[1] = '-';
        if ( ( _statbuf.mode & S_IWUSR ) == S_IWUSR ) str[2] = 'w'; else str[2] = '-';
        if ( ( _statbuf.mode & S_ISUID ) == S_ISUID ) {
            if ( ( _statbuf.mode & S_IXUSR ) == S_IXUSR ) str[3] = 's'; else str[3] = 'S';
        } else {
            if ( ( _statbuf.mode & S_IXUSR ) == S_IXUSR ) str[3] = 'x'; else str[3] = '-';
        }
        if ( ( _statbuf.mode & S_IRGRP ) == S_IRGRP ) str[4] = 'r'; else str[4] = '-';
        if ( ( _statbuf.mode & S_IWGRP ) == S_IWGRP ) str[5] = 'w'; else str[5] = '-';
        if ( ( _statbuf.mode & S_ISGID ) == S_ISGID ) {
            if ( ( _statbuf.mode & S_IXGRP ) == S_IXGRP ) str[6] = 's'; else str[6] = 'S';
        } else {
            if ( ( _statbuf.mode & S_IXGRP ) == S_IXGRP ) str[6] = 'x'; else str[6] = '-';
        }
        if ( ( _statbuf.mode & S_IROTH ) == S_IROTH ) str[7] = 'r'; else str[7] = '-';
        if ( ( _statbuf.mode & S_IWOTH ) == S_IWOTH ) str[8] = 'w'; else str[8] = '-';
        if ( ( _statbuf.mode & S_ISVTX ) == S_ISVTX ) {
            if ( ( _statbuf.mode & S_IXOTH ) == S_IXOTH ) str[9] = 't'; else str[9] = 'T';
        } else {
            if ( ( _statbuf.mode & S_IXOTH ) == S_IXOTH ) str[9] = 'x'; else str[9] = '-';
        }
        str[10] = 0;

        res += str;

        return true;
    }

    bool FSEntry::getDestination( std::string &res ) const
    {
        char *buf;

        if ( ! isLink() ) return false;

        buf = NWC::OS::getLinkTarget( _fullname.c_str() );

        if ( buf ) {
            res += buf;
            _freesafe( buf );

            return true;
        }

        return false;
    }

    int FSEntry::changeBasename( const std::string &newbasename )
    {
        std::string newfullname = Path::join( Path::dirname( _fullname ),
                                              Path::basename( newbasename ) );


        m_basename = Path::basename( newbasename );
        _fullname = newfullname;
        m_basename_calculated = true;

        return 0;
    }

    int FSEntry::updateStats()
    {
        _exists = false;
  
        _is_link = false;
        _is_corrupt = false;

        if ( _fullname[0] == '/' ) {
            worker_struct_stat stbuf;
    
            if ( worker_lstat( _fullname.c_str(), &stbuf ) == 0 ) {
                _statbuf.size = stbuf.st_size;
                _statbuf.lastaccess = stbuf.st_atime;
                _statbuf.lastmod = stbuf.st_mtime;
                _statbuf.lastchange = stbuf.st_ctime;
                _statbuf.userid = stbuf.st_uid;
                _statbuf.groupid = stbuf.st_gid;
                _statbuf.inode = stbuf.st_ino;
                _statbuf.nlink = stbuf.st_nlink;
                _statbuf.blocks = stbuf.st_blocks;
                _statbuf.rdev = stbuf.st_rdev;
                _statbuf.dev = stbuf.st_dev;

                // only apply access mode changes to keep type of entry
                _statbuf.mode &= ~(S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
                _statbuf.mode |= stbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);

                _exists = true;
            } else {
                ErrorCount::account_errno( errno );
            }

            if ( S_ISLNK( _statbuf.mode ) ) {
                _is_link = true;

                if ( worker_stat( _fullname.c_str(), &stbuf ) == 0 ) {
                    _dest_statbuf.size = stbuf.st_size;
                    _dest_statbuf.lastaccess = stbuf.st_atime;
                    _dest_statbuf.lastmod = stbuf.st_mtime;
                    _dest_statbuf.lastchange = stbuf.st_ctime;
                    _dest_statbuf.userid = stbuf.st_uid;
                    _dest_statbuf.groupid = stbuf.st_gid;
                    _dest_statbuf.inode = stbuf.st_ino;
                    _dest_statbuf.nlink = stbuf.st_nlink;
                    _dest_statbuf.blocks = stbuf.st_blocks;
                    _dest_statbuf.rdev = stbuf.st_rdev;
                    _dest_statbuf.dev = stbuf.st_dev;

                    _dest_statbuf.mode &= ~(S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
                    _dest_statbuf.mode |= stbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
                } else {
                    /* corrupt */
                    ErrorCount::account_errno( errno );
                    _is_corrupt = true;
                    _dest_statbuf.mode = 0;  // to prevent corrupted links treated as dirs
                    if ( errno == ENOENT ) {
                        // Eventl. fuer spaetere Betrachtungen
                    }
                }
            }
        }

        return 0;
    }

    void FSEntry::resetToDummy()
    {
        memset( &_dest_statbuf, 0, sizeof( _dest_statbuf ) );

        _statbuf.lastaccess = 0;
        _statbuf.lastmod = 0;
        _statbuf.lastchange = 0;
        _statbuf.userid = 0;
        _statbuf.groupid = 0;
        _statbuf.inode = 0;
        _statbuf.nlink = 2;
        _statbuf.rdev = 0;
        _statbuf.dev = 0;

        _is_link = false;
        _is_corrupt = false;
        _exists = true;
    }
}
