/*
 *
 *  This file is part of the KDE libraries
 *  Copyright (c) 1999 Waldo Bastian <bastian@kde.org>
 *
 * $Id$
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License version 2 as published by the Free Software Foundation.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#include <config.h>

#include <sys/types.h>

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

#ifdef HAVE_TEST
#include <test.h>
#endif
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif

#ifndef _PATH_TMP
#define _PATH_TMP "/tmp"
#endif

#include <tqdatetime.h>
#include <tqfile.h>
#include <tqdatastream.h>
#include <tqtextstream.h>

#include "tdeglobal.h"
#include "tdeapplication.h"
#include "tdeinstance.h"
#include "tdetempfile.h"
#include "kstandarddirs.h"
#include "kde_file.h"
#include "kdebug.h"

/* antlarr: KDE 4: make the parameters const TQString & */
KTempFile::KTempFile(TQString filePrefix, TQString fileExtension, int mode)
{
   bAutoDelete = false;
   mFd = -1;
   mStream = 0;
   mFile = 0;
   mTextStream = 0;
   mDataStream = 0;
   mError = 0;
   bOpen = false;
   if (fileExtension.isEmpty())
      fileExtension = ".tmp";
   if (filePrefix.isEmpty())
   {
      filePrefix = locateLocal("tmp", TDEGlobal::instance()->instanceName());
   }
   (void) create(filePrefix, fileExtension, mode);
}

KTempFile::KTempFile(bool)
{
   bAutoDelete = false;
   mFd = -1;
   mStream = 0;
   mFile = 0;
   mTextStream = 0;
   mDataStream = 0;
   mError = 0;
   bOpen = false;
}

bool
KTempFile::create(const TQString &filePrefix, const TQString &fileExtension,
		  int mode)
{
   // make sure the random seed is randomized
   (void) TDEApplication::random();

   TQCString ext = TQFile::encodeName(fileExtension);
   TQCString nme = TQFile::encodeName(filePrefix) + "XXXXXX" + ext;
   if((mFd = mkstemps(nme.data(), ext.length())) < 0)
   {
       // Recreate it for the warning, mkstemps emptied it
       TQCString nme = TQFile::encodeName(filePrefix) + "XXXXXX" + ext;
       kdWarning() << "KTempFile: Error trying to create " << nme << ": " << strerror(errno) << endl;
       mError = errno;
       mTmpName = TQString::null;
       return false;
   }

   // got a file descriptor. nme contains the name
   mTmpName = TQFile::decodeName(nme);
   mode_t tmp = 0;
   mode_t umsk = umask(tmp);
   umask(umsk);
   fchmod(mFd, mode&(~umsk));

   // Success!
   bOpen = true;

   uid_t uid  = getuid();
   uid_t euid = geteuid();
   if (uid != euid) {
       // Set uid/gid (necessary for SUID programs)
       fchown(mFd, getuid(), getgid());
   }

   // Set close on exec
   fcntl(mFd, F_SETFD, FD_CLOEXEC);
   
   return true;
}

KTempFile::~KTempFile()
{
   close();
   if (bAutoDelete)
      unlink();
}

int
KTempFile::status() const
{
   return mError;
}

TQString
KTempFile::name() const
{
   return mTmpName;
}

int
KTempFile::handle() const
{
   return mFd;
}

FILE *
KTempFile::fstream()
{
   if (mStream) return mStream;
   if (mFd < 0) return 0;

   // Create a stream
   mStream = KDE_fdopen(mFd, "r+");
   if (!mStream) {
     kdWarning() << "KTempFile: Error trying to open " << mTmpName << ": " << strerror(errno) << endl;
     mError = errno;
   }
   return mStream;
}

TQFile *
KTempFile::file()
{
   if (mFile) return mFile;
   if ( !fstream() ) return 0;

   mFile = new TQFile();
   mFile->setName( name() );
   mFile->open(IO_ReadWrite, mStream);
   return mFile;
}

TQTextStream *
KTempFile::textStream()
{
   if (mTextStream) return mTextStream;
   if ( !file() ) return 0; // Initialize mFile

   mTextStream = new TQTextStream( mFile );
   return mTextStream;
}

TQDataStream *
KTempFile::dataStream()
{
   if (mDataStream) return mDataStream;
   if ( !file() ) return 0;  // Initialize mFile

   mDataStream = new TQDataStream( mFile );
   return mDataStream;
}

void
KTempFile::unlink()
{
   if (!mTmpName.isEmpty())
      TQFile::remove( mTmpName );
   mTmpName = TQString::null;
}

#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
#define FDATASYNC fdatasync
#else
#define FDATASYNC fsync
#endif

bool
KTempFile::sync()
{
   int result = 0;

   if (mStream)
   {
      do {
         result = fflush(mStream); // We need to flush first otherwise fsync may not have our data
      }
      while ((result == -1) && (errno == EINTR));
      
      if (result)
      {
         kdWarning() << "KTempFile: Error trying to flush " << mTmpName << ": " << strerror(errno) << endl;
         mError = errno;
      }
   }

   if (mFd >= 0)
   {
      if( qstrcmp( getenv( "TDE_EXTRA_FSYNC" ), "1" ) == 0 )
      {
         result = FDATASYNC(mFd);
         if (result)
         {
            kdWarning() << "KTempFile: Error trying to sync " << mTmpName << ": " << strerror(errno) << endl;
            mError = errno;
         }
      }
   }

   return (mError == 0);
}

#undef FDATASYNC

bool
KTempFile::close()
{
   int result = 0;
   delete mTextStream; mTextStream = 0;
   delete mDataStream; mDataStream = 0;
   delete mFile; mFile = 0;

   if (mStream)
   {
      result = ferror(mStream);
      if (result)
         mError = ENOSPC; // Assume disk full.
         
      result = fclose(mStream);
      mStream = 0;
      mFd = -1;
      if (result != 0) {
         kdWarning() << "KTempFile: Error trying to close " << mTmpName << ": " << strerror(errno) << endl;
         mError = errno;
      }
   }


   if (mFd >= 0)
   {
      result = ::close(mFd);
      mFd = -1;
      if (result != 0) {
         kdWarning() << "KTempFile: Error trying to close " << mTmpName << ": " << strerror(errno) << endl;
         mError = errno;
      }
   }

   bOpen = false;
   return (mError == 0);
}

