/* datei.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2024 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 "datei.h"
#include "aguix/lowlevelfunc.h"
#include "wconfig.h"
#include "grouphash.h"
#include "aguix/mutex.h"
#include "aguix/utf8.hh"
#include "nwc_os.hh"
#include "nwc_path.hh"
#include "worker.h"
#include "error_count.hh"

Datei::Datei()
{
  fd = -1;
  error=0;
  iseof = false;

  sectiondepth = 0;
}

Datei::~Datei()
{
  if ( fd >= 0 ) close();
}

int Datei::open( const char *name, const char *mode )
{
  int m;
  mode_t p;

  if ( name == NULL ) return 1;
  if ( mode == NULL ) return 1;

  if ( strlen( mode ) < 1 ) return 1;  // wrong mode

  //TODO: Currently "b" in mode will fail open in any case
  
  p = 0;
  if ( strcasecmp( mode, "r" ) == 0 ) {
    m = O_RDONLY;
  } else if ( strcasecmp( mode, "r+" ) == 0 ) {
    m = O_RDWR;
  } else if ( strcasecmp( mode, "w" ) == 0 ) {
    m = O_WRONLY | O_CREAT | O_TRUNC;
    p = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
  } else if ( strcasecmp( mode, "w+" ) == 0 ) {
    m = O_RDWR | O_CREAT | O_TRUNC;
    p = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
  } else if ( strcasecmp( mode, "a" ) == 0 ) {
    m = O_WRONLY | O_CREAT | O_APPEND;
    p = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
  } else if ( strcasecmp( mode, "a+" ) == 0 ) {
    m = O_RDWR | O_CREAT | O_APPEND;
    p = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
  } else {
    return 1;
  }

  p = p & ~Worker::getUMask();

  fd = ::worker_open( name, m, p );
  if ( fd < 0 ) return 1;
  iseof = false;
  return 0;
}

int Datei::close()
{
  int res = -1;

  if ( fd < 0 ) return res;
  res = ::worker_close( fd );
  fd = -1;
  iseof = false;

  if ( res != 0 ) error++;
  return res;
}

int Datei::putUChar(unsigned char value)
{
  if ( fd < 0 ) return -1;
  int x;
  x = putCharacter( value );
  if(x!=EOF) return 1;
  return 0;
}

int Datei::putUShort(unsigned short int value)
{
  if ( fd < 0 ) return -1;
  int x;
  x = putCharacter( value >> 8 );
  if(x==EOF) return 0;
  x = putCharacter( value & 0xff );
  if(x==EOF) return 1;
  return 2;
}

int Datei::putInt(int value)
{
  if ( fd < 0 ) return -1;
  int x,tv;
  if(value<0) {
    x = putCharacter( 1 );
    if(x==EOF) return 0;
  } else {
    x = putCharacter( 0 );
    if(x==EOF) return 0;
  }
  tv=abs(value);
  int i;
  for(i=0;i<4;i++) {
    x = putCharacter( tv & 0xff );
    if(x==EOF) return i+1;
    tv>>=8;
  }
  return i+1;
}

int Datei::getInt()
{
  if ( fd < 0 ) return -1;
  int vz,x,value;
  vz = getCharacter();
  if(vz<0) {
    error++;
    return 0;
  }
  value=0;
  for(int i=0;i<4;i++) {
    x = getCharacter();
    if(x==EOF) {
      error++;
      return 0;
    }
    value+=(x<<(8*i));
  }
  if(vz==1) value*=-1;
  return value;
}

int Datei::putULong(unsigned long value)
{
  if ( fd < 0 ) return -1;
  int x;
  x = putCharacter( ( value & 0xff000000 ) >> 24 );
  if(x==EOF) return 0;
  x = putCharacter( ( value & 0xff0000 ) >> 16 );
  if(x==EOF) return 1;
  x = putCharacter( ( value & 0xff00 ) >> 8 );
  if(x==EOF) return 2;
  x = putCharacter( value & 0xff );
  if(x==EOF) return 3;
  return 4;
}

int Datei::putString( const char *str1 )
{
  int x, l;

  if ( fd < 0 ) return -1;
  if ( str1 == NULL ) return -1;
  
  l = strlen( str1 );
  x = worker_write( fd, str1, l );
  if ( x < 0 ) {
    error++;
    return -2;
  } else if ( x != l ) {
    error++;
  }
  return x;
}

unsigned char Datei::getUChar()
{
  if ( fd < 0 ) {
    error++;
    return 0;
  }
  int x;
  x = getCharacter();
  if(x==EOF) {
    error++;
    return 0;
  }
  return (unsigned char) x;
}

int Datei::getCharAsInt()
{
  if ( fd < 0 ) {
    error++;
    return 0;
  }
  return getCharacter();
}

unsigned short Datei::getUShort()
{
  if ( fd < 0 ) {
    error++;
    return 0;
  }
  int x;
  unsigned short int a;
  x = getCharacter();
  if(x==EOF) {
    error++;
    return 0;
  }
  a=((unsigned int)x)*0x100;
  x = getCharacter();
  if(x==EOF) {
    error++;
    return 1;
  }
  a+=(unsigned int)x;
  return a;
}

unsigned long Datei::getULong()
{
  if ( fd < 0 ) {
    error++;
    return 0;
  }
  int x;
  unsigned long a;
  x = getCharacter();
  if(x==EOF) {
    error++;
    return 0;
  }
  a=((unsigned int)x)*0x1000000;
  x = getCharacter();
  if(x==EOF) {
    error++;
    return 1;
  }
  a+=((unsigned int)x)*0x10000;
  x = getCharacter();
  if(x==EOF) {
    error++;
    return 2;
  }
  a+=((unsigned int)x)*0x100;
  x = getCharacter();
  if(x==EOF) {
    error++;
    return 3;
  }
  a+=(unsigned int)x;
  return a;
}

char *Datei::getString(int count)
{
  if ( fd < 0 ) return NULL;
  unsigned char *tstr,*tstr2;
  int ch;
  long x;
  long size;
  if(count==-1) {
    /* lesen bis NULL-Byte */
    size=1024;
    tstr=(unsigned char*)_allocsafe(size);
    x=0;
    do {
      ch = getCharacter();
      if(ch!=EOF) {
        tstr[x++]=(unsigned char)ch;
      } else {
        tstr[x++]=0;
        break;
      }
      if(x==size) {
        size*=2;
        tstr2=(unsigned char*)_allocsafe(size);
        if(tstr2==NULL) break;
        strcpy((char*)tstr2,(char*)tstr);
        _freesafe(tstr);
        tstr=tstr2;
      }
    } while(ch!=0);
  } else {
    tstr=(unsigned char*)_allocsafe(count+1);
    if(tstr!=NULL) {
      for(x=0;x<count;x++) {
	ch = getCharacter();
        tstr[x]=(unsigned char)ch;
      }
      tstr[count]=0;
    }
  }
  return (char*)tstr;
}

int Datei::getString(char *buf,int count)
{
  buf[0]=0;
  char *str=getString(count);
  strncpy(buf,str,count);
  buf[count]=0;
  _freesafe(str);
  return strlen(buf);
}

bool Datei::fileExists(const char *name)
{
  worker_struct_stat stbuf;
  if ( worker_lstat( name, &stbuf ) != 0 ) {
      ErrorCount::account_errno( errno );
      return false;
  } else {
    /* kann auch verzeichnis sein */
//    if(S_ISDIR(stbuf.st_mode)) return false;
  }
  return true;
}

Datei::d_fe_t Datei::fileExistsExt(const char*name)
{
  worker_struct_stat stbuf;
  d_fe_t return_value;
  if ( worker_stat( name, &stbuf ) != 0 ) {
      ErrorCount::account_errno( errno );
      return_value = D_FE_NOFILE;
  } else {
    if(S_ISDIR(stbuf.st_mode)) return_value=D_FE_DIR;
    else return_value=D_FE_FILE;
    if(S_ISLNK(stbuf.st_mode)) return_value=D_FE_LINK;
  }
  return return_value;
}

Datei::d_fe_t Datei::lfileExistsExt(const char*name)
{
  worker_struct_stat stbuf;
  d_fe_t return_value;
  if ( worker_lstat( name, &stbuf ) != 0 ) {
      ErrorCount::account_errno( errno );
      return_value = D_FE_NOFILE;
  } else {
    if(S_ISDIR(stbuf.st_mode)) return_value=D_FE_DIR;
    else return_value=D_FE_FILE;
    if(S_ISLNK(stbuf.st_mode)) return_value=D_FE_LINK;
  }
  return return_value;
}

void Datei::seek(long offset,int mode)
{
  if ( fd >= 0 ) worker_lseek( fd, offset, mode );
}

int getOwnerStringLen(uid_t user,gid_t gr)
{
  char *tstr;
  #ifdef DEBUG
//  printf("entering getOwnerStringLen\n");
  #endif
  int len=0;
  int ost=wconfig->getOwnerstringtype();
  #ifdef DEBUG
//  printf("  getUserNameS\n");
  #endif
  tstr=ugdb->getUserNameS(user);
  if(tstr!=NULL) len+=strlen(tstr);
  len+=(ost==1)?1:3;
  #ifdef DEBUG
//  printf("  getGroupNameS\n");
  #endif
  tstr=ugdb->getGroupNameS(gr);
  if(tstr!=NULL) len+=strlen(tstr);
  #ifdef DEBUG
//  printf("leaving getOwnerStringLen\n");
  #endif
  return len;
}

char* getOwnerString(uid_t user,gid_t mygroup)
{
  int len=0;
  char *tstr1,*tstr2;
  int ost=wconfig->getOwnerstringtype();
  
  tstr1=ugdb->getUserNameS(user);
  tstr2=ugdb->getGroupNameS(mygroup);
  if(tstr1!=NULL) len+=strlen(tstr1);
  len+=(ost==1)?1:3;
  if(tstr2!=NULL) len+=strlen(tstr2);
  char *str=(char*)_allocsafe((len+2)*sizeof(char));
  str[0]=0;
  if(tstr1!=NULL) strcat(str,tstr1);
  strcat(str,(ost==1)?".":" @ ");
  if(tstr2!=NULL) strcat(str,tstr2);
  return str;
}

int Datei::overreadChunk()
{
  int chunksize=getInt();
  while(chunksize>0) {
    getUChar();
    chunksize--;
  }
  return 0;
}

int Datei::getIntSize()
{
  return 5;
}

int Datei::getUCharSize()
{
  return 1;
}

int Datei::getUShortSize()
{
  return 2;
}

int Datei::getULongSize()
{
  return 4;
}

long Datei::errors()
{
  long e=error;
  error=0;
  return e;
}

char *Datei::getFilenameFromPath(const char*path)
{
  char *tstr;
  int pos;
  if(path==NULL) return NULL;
  pos=strlen(path)-2;  // -2 because a name contains min. 1 character
                       // but it can be a slash so skiping it
  while(pos>=0) {
    if(path[pos]=='/') break;
    pos--;
  }
  if(pos>=0) tstr=dupstring(&path[pos+1]);
  else tstr=dupstring(path);
  return tstr;
}

char *Datei::getNameWithoutExt(const char *name)
{
  char *tstr;
  int pos,len;
  bool found=false;
  if(name==NULL) return NULL;
  len=strlen(name);
  pos=len-1;
  while(pos>=0) {
    if((name[pos]=='/')&&(pos<(len-1))) break;
    if(name[pos]=='.') {
      found=true;
      break;
    }
    pos--;
  }
  if(found==false) tstr=dupstring(name);
  else {
    tstr=(char*)_allocsafe(pos+1);
    if(pos>0) strncpy(tstr,name,pos);
    tstr[pos]=0;
  }
  return tstr;
}

const char *Datei::getExtension( const char *filename )
{
    if ( ! filename ) return NULL;

    const char *p = strrchr( filename, '.' );

    if ( ! p ) return NULL;

    return p + 1;
}

/**
 * function returns name of tmp file in user's worker directory
 * file will be created
 * It can however return NULL if after a certain number of
 * retries no tmp file couldn't be created
 * THREADSAFE
 */
char *Datei::createTMPName( const char *infix,
                            const char *suffix )
{
    char *returnstr = NULL, buffer[ 2 * A_BYTESFORNUMBER( int ) ];
    int rannr;
    static int lfdnr = 0;
    int tfd;
    static MutEx lock;
  
    if ( infix == NULL ) infix = "";
    if ( suffix == NULL ) suffix = "";
  
    lock.lock();
    for( int retry = 0; retry < 1000; retry++ ) {
        rannr=rand();
        sprintf( buffer, "%d%d", lfdnr++, rannr );
        returnstr = (char*)_allocsafe( strlen( worker_tmpdir() ) + 
                                       strlen( "/worker" ) +
                                       strlen( infix ) + strlen( buffer ) +
                                       strlen( suffix ) + 1 );
        sprintf( returnstr, "%s/worker%s%s%s", worker_tmpdir(), infix, buffer, suffix );
        if(Datei::lfileExistsExt(returnstr)==Datei::D_FE_NOFILE) {
            tfd = ::worker_open( returnstr, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR );
            if ( tfd >= 0 ) {
                ::worker_close( tfd );
                worker_chmod( returnstr, S_IRUSR | S_IWUSR );
                break;
            }
        }
        _freesafe(returnstr);
        returnstr = NULL;
    }
    lock.unlock();
    return returnstr;
}

std::string Datei::createTMPHardlink( const std::string &name )
{
    std::string returnstr;
    int rannr;
    static int lfdnr = 0;
    static MutEx lock;
    
    lock.lock();

    for( int retry = 0; retry < 1000; retry++ ) {
        rannr = rand();

        returnstr = AGUIXUtils::formatStringToString( "%s/workerlnk%s%d%d",
                                                      worker_tmpdir(),
                                                      "",
                                                      lfdnr++, rannr );
        if ( Datei::lfileExistsExt( returnstr.c_str() ) == Datei::D_FE_NOFILE ) {
            if ( worker_link( name.c_str(),
                              returnstr.c_str() ) == 0 ) {
                break;
            } else {
                if ( errno != EEXIST ) {
                    returnstr = "";
                    break;
                }
            }
        }
        returnstr = "";
    }

    lock.unlock();

    return returnstr;
}

int Datei::putStringExt(const char *format,const char *str)
{
  int written, size;
  char *tstr;
  
  if ( fd < 0 ) return -1;
  if ( format == NULL ) return -1;
  if ( str == NULL ) return -1;
  
  size = strlen( format ) + strlen( str ) + 1;
  tstr = (char*)_allocsafe( size );
  while ( 1 ) {
    written = snprintf( tstr, size, format, str );
    if ( ( written > -1 ) && ( written < size ) ) break;

    //TODO: in case of an error we try to use more space
    //      but if for whatever reason does not work at all
    //      we should perhaps not end the program (because)
    //      alloc will fail) but only quit this function
    size *= 2;
    _freesafe( tstr );
    tstr = (char*)_allocsafe( size );
  }
  putString( tstr );
  _freesafe( tstr );
  return 0;
}

int Datei::putLine( const char *str )
{
  int x, y, l;
  
  if ( str == NULL ) return -1;
  if ( fd < 0 ) return -1;
  
  l = strlen( str );
  x = worker_write( fd, str, l );
  if ( x < 0 ) {
    error++;
    return -2;
  } else if ( x != l ) {
    error++;
  }

  y = worker_write( fd, "\n", 1 );
  if ( y < 0 ) {
    error++;
    return -2;
  } else if ( y != 1 ) {
    error++;
  }

  return x + y;
}

char *Datei::getLine()
{
  if ( fd < 0 ) return NULL;
  unsigned char *tstr,*tstr2;
  int ch;
  long x;
  long size;

  size=1024;
  tstr = (unsigned char*)_allocsafe( size );
  x = 0;
  do {
    ch = getCharacter();
    if ( ch != EOF ) {
      tstr[ x++ ] = (unsigned char)ch;
    } else {
      tstr[ x++ ] = 0;
      break;
    }
    if ( x == size ) {
      size *= 2;
      tstr2 = (unsigned char*)_allocsafe( size );
      if ( tstr2 == NULL ) break;
      memcpy( (char*)tstr2, (char*)tstr, x );
      _freesafe( tstr );
      tstr = tstr2;
    }
  } while ( ( ch != 0 ) && ( ch != '\n' ) );
  if ( tstr[ x - 1 ] == '\n' ) tstr[ x - 1 ] = '\0';
  return (char*)tstr;
}

bool Datei::isEOF()
{
  if ( fd < 0 ) return true;
  return iseof;
}

loff_t Datei::fileSize( const char *name )
{
  worker_struct_stat stbuf;
  loff_t return_value;

  if ( worker_stat( name, &stbuf ) != 0 ) {
      return_value = -1;
      ErrorCount::account_errno( errno );
  } else {
    return_value = stbuf.st_size;
  }
  return return_value;
}

/*
 * getRelativePath
 *
 * will return relative path for source in destdir
 * in other words: destdir/returnvalue will be the same fileentry
 * as source
 */
char *Datei::getRelativePath( const char *source, const char *destdir, bool use_realpath )
{
  int startpos, curpos, mode, i;
  char *mysource, *mydestdir;
  std::string erg;
  
  if ( ( source == NULL ) || ( destdir == NULL ) ) return NULL;
  
  /* basicly it works this way:
     first remove same directories from the beginning
     then for each directory in destdir add ".." before the source
     
     because this code cannot handle .. and . in the paths
     I remove them with HandlePath
     It's not a big deal to handle these strings too (by ignoring
     "." and removing an added "../" for ".." but this way this function
     keeps small and clean :-)
   */

  if ( use_realpath ) {
      mysource = NWC::Path::handlePath( source );
      mydestdir = NWC::Path::handlePath( NWC::Path::get_real_path( destdir ).c_str() );

      std::string real_destdir = NWC::Path::get_real_path( destdir );
      if ( real_destdir.empty() ) {
          mydestdir = NWC::Path::handlePath( destdir );
      } else {
          mydestdir = NWC::Path::handlePath( real_destdir.c_str() );
      }
  } else {
      mysource = NWC::Path::handlePath( source );
      mydestdir = NWC::Path::handlePath( destdir );
  }

  for ( i = strlen( mysource ) - 1; i >= 0; i-- ) {
    if ( mysource[i] == '/' ) mysource[i] = '\0';
    else break;
  }
  for ( i = strlen( mydestdir ) - 1; i >= 0; i-- ) {
    if ( mydestdir[i] == '/' ) mydestdir[i] = '\0';
    else break;
  }

  if ( ( strlen( mysource ) < 1 ) || ( strlen( mydestdir ) < 1 ) ) {
    _freesafe( mysource );
    _freesafe( mydestdir );
    return NULL;
  }
  
  startpos = curpos = 0;
  erg = "";
  mode = 0;
  
  // I just parse mydestdir and add "../" for every /
  // but because I want to remove same basedir
  // I will also parse the source string 
  for (;;) {
    // the end of mydestdir is the only way to break this loop
    if ( mydestdir[curpos] == '\0' ) {
      // add "../" because destdir is a dir (as the name says)
      // then add the rest of mysource
#if 0
      // this code is correct!
      // but it will create results the user
      // doesn't expect (because it's too complicated)
      // for example
      // linking /tmp/file1 into /tmp would
      // would result in ../tmp/file1 which is 100%
      // correct but the user expect just file1 as
      // relative path and that's what the #else part
      // checks
      erg += "../";
      erg += &mysource[startpos];
#else
      if ( ( mode == 0 ) && ( mysource[ curpos ] == '/' ) ) {
        // mydestdir ended but we are still in "same character" mode
        // and the source also ends as an dir so there is no need
        // for any "../"
        // read the above comment !
        erg += &mysource[ curpos + 1 ];
      } else {
        erg += "../";
        erg += &mysource[startpos];
      }
#endif
      break;
    }
    switch ( mode ) {
      case 1:
        if ( mydestdir[ curpos++ ] == '/' ) {
          erg += "../";
        }
        break;
      default:
        if ( mysource[ curpos ] == '\0' ) mode = 1;  // no curpos change!
        else if ( ( mysource[ curpos ] == '/' ) &&
                  ( mydestdir[ curpos ] == '/' ) ) {
          // new directory, old one was identically
          // so set new start
          startpos = ++curpos;
        } else if ( mysource[ curpos ] == mydestdir[ curpos ] ) {
          // same character => keep in this mode
          curpos++;
        } else mode = 1;  // no cursorchange
        break;
    }
  }
  _freesafe( mysource );
  _freesafe( mydestdir );
  if ( erg.length() < 1 ) return NULL;
  return dupstring( erg.c_str() );
}

char *Datei::getRelativePathExt( const char *source,
                                 const char *destdir,
                                 bool use_realpath )
{
    char *erg[2] = { NULL, NULL };

    if ( ! source || ! destdir ) return NULL;

    erg[0] = getRelativePath( source, destdir, use_realpath );
    if ( erg[0] == NULL ) {
        return NULL;
    }

    if ( use_realpath ) {
        std::string real_source = NWC::Path::get_real_path( source );
        if ( ! real_source.empty() ) {
            erg[1] = getRelativePath( real_source.c_str(),
                                      destdir,
                                      use_realpath );
        }
    }

    if ( erg[1] == NULL ) {
        return erg[0];
    }

    // if the relative path for the real_source is shorter, take this one
    if ( strlen( erg[1] ) < strlen( erg[0] ) ) {
        _freesafe( erg[0] );
        return erg[1];
    }

    _freesafe( erg[1] );
    return erg[0];
}

int Datei::putCharacter( int c )
{
  unsigned char cf[1];
  int count;
  
  if ( fd < 0 ) return EOF;
  
  cf[0] = (unsigned char)c;
  
  count = worker_write( fd, cf, 1 );
  if ( count < 1 ) {
    error++;
    return EOF;
  }
  return cf[0];
}

int Datei::getCharacter()
{
  unsigned char cf[1];
  int count;
  
  if ( fd < 0 ) return EOF;
  
  count = ::worker_read( fd, cf, 1 );
  
  if ( count < 1 ) {
    iseof = true;
    return EOF;
  }
  return (int)( cf[0] );
}

int Datei::copyfile( const char *dest, const char *source,
                     CopyfileProgressCallback *progress_callback,
                     bool follow_symlinks )
{
  int fdw,fdr;
  bool useregcopy, cancel, isCorrupt;
  ssize_t readbytes,writebytes;
#define BUFSIZE ( 4 * 1024 )
  char *buf[BUFSIZE];
  worker_struct_stat st, dst;
  int erg;
  loff_t total_bytes = 0, bytes_copied = 0;

  if ( ( dest == NULL ) || ( source == NULL ) ) return 1;
  
  isCorrupt = false;
  cancel = false;

  // get stat for file
  if ( worker_lstat( source, &st ) != 0 ) return 1;

  // get stat for destination if link (will fail for corrupt links)
  // but currently I don't handle them anyway
  if ( worker_stat( source, &dst ) != 0 ) {
      //isCorrupt = true;
      return 1;
  }
  
  useregcopy = false;
  if ( S_ISREG( st.st_mode ) ) {
      useregcopy = true;
      total_bytes = st.st_size;
  } else if ( ( S_ISLNK( st.st_mode ) ) &&
	    ( follow_symlinks == true ) &&
	    ( isCorrupt == false ) &&
              ( S_ISREG( dst.st_mode ) ) ) {
      useregcopy = true;
      total_bytes = dst.st_size;
  }
  
  if ( useregcopy == false ) return 1;
  
  fdr = ::worker_open( source, O_RDONLY, 0 );
  if ( fdr == -1 ) {
    // can't open inputfile
    return 1;
  }
  
  fdw = ::worker_open( dest, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR );
  if ( fdw == -1 ) {
    ::worker_close( fdr );
    return 1;
  } 

  writebytes = -1;
  erg = 0;
  do {
    readbytes = ::worker_read( fdr, buf, BUFSIZE );
    if ( readbytes < 0 ) {
      // error while reading
      erg = 1;
      break;
    } else if ( readbytes == 0 ) {
      break;
    } else {
      writebytes = worker_write( fdw, buf, readbytes );
      if ( writebytes != readbytes ) {
	// something went wrong
	erg = 1;
	break;
      }

      bytes_copied += writebytes;

      if ( progress_callback != NULL ) {
          if ( progress_callback->progress_callback( bytes_copied,
                                                     total_bytes ) == CopyfileProgressCallback::COPYFILE_ABORT ) {
              cancel = true;
          }
      }
    }
  } while ( ( readbytes > 0 ) &&
	    ( cancel == false ) );

  ::worker_close( fdr );
  if ( ::worker_close( fdw ) != 0 ) {
    // error while closing -> incomplete copy
    erg = 1;
  }
  return erg;
#undef BUFSIZE
}

void Datei::configPutPair( const char *id, const char *info )
{
  std::string str1;
  int x;

  if ( ( id == NULL ) || ( info == NULL ) ) return;
  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";
  str1 += id;
  str1 += " = ";
  str1 += info;
  str1 += ";";
  putLine( str1.c_str() );
}

void Datei::configPutPairNum( const char *id, int num )
{
  std::string str1;
  char buf[A_BYTESFORNUMBER( int )];
  int x;

  if ( id == NULL ) return;
  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";
  sprintf( buf, "%d", num );
  str1 += id;
  str1 += " = ";
  str1 += buf;
  str1 += ";";
  putLine( str1.c_str() );
}

void Datei::configPutPairNum( const char *id, int num1, int num2 )
{
  std::string str1;
  char buf[A_BYTESFORNUMBER( int )];
  int x;

  if ( id == NULL ) return;
  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";
  sprintf( buf, "%d", num1 );
  str1 += id;
  str1 += " = ";
  str1 += buf;
  str1 += ",";
  sprintf( buf, "%d", num2 );
  str1 += buf;
  str1 += ";";
  putLine( str1.c_str() );
}

void Datei::configPutPairString( const char *id, const std::string &str, bool escape_newlines )
{
    configPutPairString( id, str.c_str(), escape_newlines );
}

void Datei::configPutPairString( const char *id, const char *str, bool escape_newlines )
{
  std::string str1;
  char *buf;
  int i, o;
  int x;

  if ( ( id == NULL ) || ( str == NULL ) ) return;
  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";

  buf = (char*)_allocsafe( strlen( str ) * 2 + 1 );
  for ( i = 0, o = 0; i < (int)strlen( str ); i++ ) {
    if ( str[i] == '"' ) {
      buf[o++] = '\\';
      buf[o++] = '"';
    } else if ( str[i] == '\\' ) {
      buf[o++] = '\\';
      buf[o++] = '\\';
    } else if ( str[i] == '\n' ) {
        if ( escape_newlines ) {
            buf[o++] = '\\';
            buf[o++] = '\n';
        }
    } else {
      buf[o++] = str[i];
    }
  }
  buf[o] = '\0';
  str1 += id;
  str1 += " = \"";
  str1 += buf;
  str1 += "\";";
  _freesafe( buf );
  putLine( str1.c_str() ); 
}

void Datei::configPutPairBool( const char *id, bool val )
{
  std::string str1;
  int x;

  if ( id == NULL ) return;
  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";

  str1 += id;
  str1 += " = ";
  str1 += ( ( val == true ) ? "true" : "false" );
  str1 += ";";
  putLine( str1.c_str() );
}

void Datei::configPutInfo( const char *str, bool semicolon )
{
  std::string str1;
  int x;

  if ( str == NULL ) return;
  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";

  str1 += str;
  if ( semicolon == true ) str1 += ";";
  putLine( str1.c_str() );
}

void Datei::configPutString( const char *str, bool semicolon, bool escape_newlines )
{
  std::string str1;
  char *buf;
  int i, o;
  int x;

  if ( str == NULL ) return;
  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";

  buf = (char*)_allocsafe( strlen( str ) * 2 + 1 );
  for ( i = 0, o = 0; i < (int)strlen( str ); i++ ) {
    if ( str[i] == '"' ) {
      buf[o++] = '\\';
      buf[o++] = '"';
    } else if ( str[i] == '\\' ) {
      buf[o++] = '\\';
      buf[o++] = '\\';
    } else if ( str[i] == '\n' ) {
        if ( escape_newlines ) {
            buf[o++] = '\\';
            buf[o++] = '\n';
        }
    } else {
      buf[o++] = str[i];
    }
  }
  buf[o] = '\0';
  str1 += "\"";
  str1 += buf;
  str1 += "\";";
  _freesafe( buf );
  putLine( str1.c_str() ); 
}

void Datei::configOpenSection( const char *sectioname )
{
  int x;
  std::string str1;

  if ( sectioname == NULL ) return;
  if ( ( sectiondepth < 0 ) || ( sectiondepth > 100 ) ) sectiondepth = 0;

  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";

  str1 += sectioname;
  str1 += " {";
  putLine( str1.c_str() );
  sectiondepth++;
}

void Datei::configCloseSection()
{
  int x;
  std::string str1;

  sectiondepth--;
  if ( ( sectiondepth < 0 ) || ( sectiondepth > 100 ) ) sectiondepth = 0;

  str1 = "";
  for ( x = 0; x < sectiondepth; x++ ) str1 += "\t";

  str1 += "}";
  putLine( str1.c_str() );
}

ssize_t Datei::read( char *buffer, size_t len )
{
    if ( buffer == NULL || len < 1 ) return 0;
    if ( fd < 0 ) return 0;

    ssize_t count;
  
    count = ::worker_read( fd, buffer, len );
    
    if ( count < 1 ) {
        iseof = true;
    }
    return count;
}

CopyfileProgressCallback::~CopyfileProgressCallback()
{
}
