/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    QC_File.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/QC_File.h"
#include "qore/intern/QC_Queue.h"
#include "qore/intern/QC_TermIOS.h"

#include <unistd.h>

extern QoreClass* QC_READONLYFILE;

#ifndef TCSANOW
#define TCSANOW 0
#endif

QoreFileHelper::QoreFileHelper(QoreObject* obj, ExceptionSink* xs) : QorePrivateObjectAccessHelper(xs) {
    File* f = reinterpret_cast<File*>(obj->getReferencedPrivateData(CID_FILE, xs));
    if (!f) {
        if (!*xsink)
            xsink->raiseException("FILE-ERROR", "expecting an object derived from File; got class '%s' instead", obj->getClassName());
        return;
    }
    ptr = (void*)f;
}

QoreFileHelper::~QoreFileHelper() {
   if (ptr)
      reinterpret_cast<File*>(ptr)->deref(xsink);
}

QoreFile* QoreFileHelper::operator*() const {
   return reinterpret_cast<File*>(ptr);
}

QoreFile* QoreFileHelper::operator->() const {
   return reinterpret_cast<File*>(ptr);
}

static void FILE_system_constructor(QoreObject* self, int fd, va_list args) {
    //printd(5, "FILE_system_constructor() self=%p, descriptor=%d\n", self, fd);
    File* f = new File(QCS_DEFAULT);
    f->makeSpecial(fd);
    self->setPrivate(CID_FILE, f);
#ifdef _Q_WINDOWS
    // we have to set binary mode on stdin/stdout/stderr so that \r\n is not automatically
    // translated to \n causing breakage on Windows
    // note that this only has to be done once per descriptor, meaning that the fix here
    // addresses stdin, stdout, stderr as well as stdin_stream, stdout_stream, and
    // stderr_stream
    _setmode(fd, O_BINARY);
#endif
}

#ifdef HAVE_STRUCT_FLOCK
static int lock_intern(struct flock& fl, const QoreListNode* args, ExceptionSink* xsink) {
    fl.l_type = (short)HARD_QORE_VALUE_INT(args, 0);
    fl.l_start = (off_t)HARD_QORE_VALUE_INT(args, 1);
    fl.l_len = (off_t)HARD_QORE_VALUE_INT(args, 2);
    if (fl.l_len < 0) {
        xsink->raiseException("FILE-LOCK-ERROR", "length of locked area cannot be negative (value passed=%d)", fl.l_len);
        return -1;
    }
    fl.l_whence = (short)HARD_QORE_VALUE_INT(args, 3);
    return 0;
}
#else
static int file_lock_error(const char* f, ExceptionSink* xsink) {
    xsink->raiseException("MISSING-FEATURE-ERROR", "this platform does not support UNIX-style file locking with fnctl(), therefore the File::%s() method is not available; for maximum portability, check Option::HAVE_FILE_LOCKING before calling this method", f);
    return 0;
}
#ifndef F_RDLCK
#define F_RDLCK 0
#endif
#ifndef F_UNLCK
#define F_UNLCK 0
#endif
#ifndef F_WRLCK
#define F_WRLCK 0
#endif
#endif

AbstractQoreNode* missing_method_error(const char* meth, const char* feat, ExceptionSink* xsink) {
    xsink->raiseException("MISSING-FEATURE-ERROR", "the %s() method is not available on this build; for maximum portability, check Option::HAVE_%s before calling this method", meth, feat);
    return nullptr;
}

//! Pipe info hash description
/** @see Qore::File::getPipe() "File::getPipe()"

    @since %Qore 1.12
*/
hashdecl PipeInfo {
    //! Read end of the pipe
    ReadOnlyFile read;

    //! Write end of the pipe
    File write;
}

/** @defgroup file_open_constants File Open Constants
    These are the possible values that can be or'ed together when calling File::open() or File::open2()
*/
///@{
//! Mask for access modes (O_RDONLY|O_WRONLY|O_RDWR)
const O_ACCMODE = O_ACCMODE;
//! Open the file in append mode (append on each write)
const O_APPEND = O_APPEND;
//! Create the file if it doesn't exist
const O_CREAT = O_CREAT;
//! Open the file read-only
const O_RDONLY = O_RDONLY;
//! Open the file write-only
const O_WRONLY = O_WRONLY;
//! Open for reading and writing
const O_RDWR = O_RDWR;
//! Truncate the size to zero
const O_TRUNC = O_TRUNC;
//! Raise an error if used with O_CREAT and the file exists
const O_EXCL = O_EXCL;

// the following constants were defined earlier but are system-specific; they are included in the build
// in case anyone is using them on the platforms where they are supported
#ifndef O_NOCTTY
#define O_NOCTTY 0
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK 0
#endif
#ifndef O_NDELAY
#define O_NDELAY 0
#endif
#ifndef O_SYNC
#define O_SYNC 0
#endif
#ifndef O_DIRECT
#define O_DIRECT 0
#endif
#ifndef O_DIRECTORY
#define O_DIRECTORY 0
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
//! don't allocate controlling tty (0 on platforms where this is not available)
const O_NOCTTY = O_NOCTTY;
//! non-blocking I/O (untested with Qore; 0 on platforms where this is not available)
const O_NONBLOCK = O_NONBLOCK;
//! synonym for O_NONBLOCK (untested with Qore; 0 on platforms where this is not available)
const O_NDELAY = O_NDELAY;
//! synchronized file update option (0 on platforms where this is not available)
const O_SYNC = O_SYNC;
//! direct disk access hint (0 on platforms where this is not available)
const O_DIRECT = O_DIRECT;
//! must be a directory (0 on platforms where this is not available)
const O_DIRECTORY = O_DIRECTORY;
//! don't follow links (0 on platforms where this is not available)
const O_NOFOLLOW = O_NOFOLLOW;
///@}

/** @defgroup file_locking_constants File Locking Constants
    These are the possible values for the File::lock() or File::lockBlocking() methods.

    @note These contants are set to 0 on platforms where HAVE_FILE_LOCKING is @ref False (such as native Windows ports)
*/
///@{
//! Use for read-only locking
const F_RDLCK = F_RDLCK;
//! Use for exclusive write locking
const F_WRLCK = F_WRLCK;
//! Use for unlocking a lock
const F_UNLCK = F_UNLCK;
///@}

/** @defgroup file_seek_constants File Seek Constants
    These are the possible values for the \c whence parameter of the File::lock() and File::lockBlocking() methods
 */
///@{
//! Indicates that the offset is from the start of the file
const SEEK_SET = SEEK_SET;
//! Indicates that the offset is from the current position in the file
const SEEK_CUR = SEEK_CUR;
//! Indicates that the offset is from the end of the file
const SEEK_END = SEEK_END;
///@}

//! The %File class allows %Qore programs to read, write, and create files
/** @note This class is not available with the @ref PO_NO_FILESYSTEM parse option

    %File objects are created and/or opened with a specific @ref character_encoding "character encoding", meaning that any string read from the file will be tagged with the file's @ref character_encoding "character encoding", and any string data written to the file will be transparently converted to that @ref character_encoding "character encoding" before being written (if necessary).
    If no @ref character_encoding "character encoding" is specified, then the @ref default_encoding "default character encoding" is assumed for the file.

    This class supports posting events to a @ref Qore::Thread::Queue "Queue". See @ref event_handling for more information.

    The events raised by this object are listed in the following table:

    @anchor file_events
    <b>File Events</b>
    |!Name|!Description
    |@ref EVENT_DATA_READ|Raised when data is read from the file
    |@ref EVENT_DATA_WRITTEN|Raised when data is written to the file
    |@ref EVENT_CHANNEL_CLOSED|Raised when the file is closed
    |@ref EVENT_DELETED|Raised when the object being monitored is deleted
    |@ref EVENT_OPEN_FILE|Raised right before an attempt to open a file is made
    |@ref EVENT_FILE_OPENED|Raised when the file has been successfully opened
 */
qclass File [system_constructor=FILE_system_constructor; arg=File* f; vparent=ReadOnlyFile; dom=FILESYSTEM];

//! Creates the %File object
/** It accepts one optional argument that will set the default @ref character_encoding "character encoding" for the file (only affects reading and writing string data)
    To open the file, call File::open() or File::open2(); the @ref character_encoding "character encoding" can also be set or
    changed by the File::open(), File::open2(), or File::setEncoding() methods.

    @par Example:
    @code{.py}
File f();
f.open2("/tmp/my-file.txt");
    @endcode

    @param encoding The @ref character_encoding "character encoding" for the %File. Any strings written to the %File will be converted to this @ref character_encoding "character encoding" if necessary; if this argument is not given then the %File will receive the @ref default_encoding "default encoding"

    @throw ILLEGAL-EXPRESSION File::constructor() cannot be called with a TTY target when @ref no-terminal-io "%no-terminal-io" is set

    @see File::open(), File::open2()
 */
File::constructor(*string encoding) {
   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;

   SimpleRefHolder<File> f(new File(qe));
   if (f->isTty()) {
      xsink->raiseException("ILLEGAL-EXPRESSION", "File::constructor() cannot be called with a TTY target when 'no-terminal-io' is set");
      return;
   }

   self->setPrivate(CID_FILE, f.release());
}

//! Closes the %File if it is open and destroys the %File object
/** Closes the %File if it is open and destroys the %File object
 */
File::destructor() [doconly=true] {
   // this code should never be output - this function is only here for documentation
   assert(false);
}

//! Creates a new %File object with the same @ref character_encoding "character encoding" specification as the original, otherwise no other information is copied
/** @par Example:
    @code{.py}
File f1 = f.copy();
    @endcode
 */
File::copy() {
   self->setPrivate(CID_FILE, new File(f->getEncoding()));
}

//! Opens a %File in a particular mode, returns an error code on failure
/** Opens the %File in the mode given; if the %File was previously open, it is closed first. Additionally, the file permissions can be given if the file is to be created, and optionally the %File's @ref character_encoding "character encoding" can be specified.

    Note that if no encoding is specified, the %File will be tagged with the @ref character_encoding "character encoding" set in the %File's @ref File::constructor() "constructor".
    Any string data written to the %File will be converted to the %File's encoding, and any string data read from the %File will be automatically tagged with the %File's encoding.

    @par Example:
    @code{.py}
# open a file for writing, truncate data if already exists, create the file if doesn't exist
# set 0644 permissions, and convert all string data to ISO-8859-1 encoding
if (f.open(fn, O_CREAT | O_TRUNC | O_WRONLY, 0644, "ISO-8859-1"))
    printf("%s: %s\n", fn, strerror(errno()));
    @endcode

    @par Events:
    @ref EVENT_OPEN_FILE, @ref EVENT_FILE_OPENED

    @param path the path to the file
    @param flags flags that determine the way the file is accessed, see @ref file_open_constants for more information; if this argument is not given, \c O_RDONLY will be used as the default value.
    @param mode permission bits for when the file is to be created (default: 0666)
    @param encoding the name of the @ref character_encoding "character encoding" for the %File; if this argument is not given, the file will be tagged with the @ref character_encoding "character encoding" given in the @ref File::constructor() "constructor"

    @return 0 = no error, -1 = see errno() and strerror() for the error message

    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::open2() for a version of this method that throws an exception when errors occur opening the file
 */
int File::open(string path, softint flags = O_RDONLY, softint mode = 0666, *string encoding) {
   if (check_terminal_io(self, "File::open", xsink))
      return QoreValue();

   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : f->getEncoding();
   return f->open(path->getBuffer(), flags, mode, qe);
}

//! Opens a file in a particular mode; throws an exception on failure
/** Opens the file in the mode given; if the %File was previously open, it is closed first. Additionally, the file permissions can be given if the file is to be created, and optionally the %File's default character encoding can be specified.

    Note that if no encoding is specified, the %File will be tagged with the @ref character_encoding "character encoding" set in the %File's @ref File::constructor() "constructor".
    Any string data written to the %File will be converted to the %File's encoding, and any string data read from the %File will be automatically tagged with the %File's encoding.

    If an error occurs, a \c FILE-OPEN2-ERROR exception is thrown. For a version of this method that returns an error code, see File::open().

    @par Example:
    @code{.py}
# open a file for writing, truncate data if already exists, create the file if doesn't exist
# set 0644 permissions, and convert all string data to ISO-8859-1 encoding
try {
    f.open2(fn, O_CREAT | O_TRUNC | O_WRONLY, 0644, "ISO-8859-1");
}
catch (hash ex) {
    printf("%s: %s: %s\n", fn, ex.err, ex.desc);
}
    @endcode

    @par Events:
    @ref EVENT_OPEN_FILE, @ref EVENT_FILE_OPENED

    @param path the path to the file
    @param flags flags that determine the way the file is accessed, see @ref file_open_constants for more information; if this argument is not given, \c O_RDONLY will be used as the default value.
    @param mode permission bits for when the file is to be created (default: 0666)
    @param encoding the name of the @ref character_encoding "character encoding" for the %File; if this argument is not given, the %File will be tagged with the @ref character_encoding "character encoding" given in the @ref File::constructor() "constructor"

    @throw FILE-OPEN2-ERROR an error occurred opening the file
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::open() for a version of this method that returns an error code instead of throwing an exception when errors occur opening the %File
 */
nothing File::open2(string path, softint flags = O_RDONLY, softint mode = 0666, *string encoding) {
   if (check_terminal_io(self, "File::open2", xsink))
      return QoreValue();

   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : f->getEncoding();
   f->open2(xsink, path->getBuffer(), flags, mode, qe);
}

//! Flushes the file's buffer to disk
/** @par Example:
    @code{.py}
if (f.sync())
    printf("error in File::sync(): %s\n", strerror(errno()));
    @endcode

    @return 0 for success, -1 for error (see errno() and strerror() for the error information)

    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set
 */
int File::sync() {
    if (check_terminal_io(self, "File::sync", xsink))
        return QoreValue();

    return f->sync(xsink);
}

//! Writes binary data to a file
/** @par Example:
    @code{.py}
f.write(data);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param data the data to be written to the file

    @return the number of bytes written

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::write(binary data) {
   if (check_terminal_io(self, "File::write", xsink))
      return QoreValue();

   return f->write(data, xsink);
}

//! Writes string data to a file; string data is converted to the %File's @ref character_encoding "character encoding" if necessary before writing
/** @par Example:
    @code{.py}
f.write(data);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param data the data to be written to the file; string data is converted to the %File's @ref character_encoding "character encoding" if necessary before writing

    @return the number of bytes written

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ENCODING-CONVERSION-ERROR error converting from the string's @ref character_encoding "character encoding" to the %File's @ref character_encoding "character encoding"
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @note this variant of this method is equivalent to File::print()

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::write(string data) {
   if (check_terminal_io(self, "File::write", xsink))
      return QoreValue();

   return f->write(data, xsink);
}

//! Writes string data to a file; string data is converted to the %File's @ref character_encoding "character encoding" if necessary before writing
/** @par Example:
    @code{.py}
f.print(data);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param data the data to be written to the file; string data is converted to the %File's @ref character_encoding "character encoding" if necessary before writing

    @return the number of bytes written

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ENCODING-CONVERSION-ERROR error converting from the string's @ref character_encoding "character encoding" to the %File's @ref character_encoding "character encoding"
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @note this method is equivalent to File::write(string)

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::print(string data) {
   if (check_terminal_io(self, "File::print", xsink))
      return QoreValue();

   return f->write(data, xsink);
}

//! Writes a 1-byte integer to the file
/** @par Example:
    @code{.py}
f.writei1(val);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param c the integer to write; only the least-significant 8 bits will be written to the file

    @return 0 for success

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::writei1(int c) {
   if (check_terminal_io(self, "File::writei1", xsink))
      return QoreValue();

   return f->writei1(c, xsink);
}

//! Writes a 2-byte (16 bit) integer to the file in binary big-endian format
/** @par Example:
    @code{.py}
f.writei2(val);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param s the integer to write in binary big-endian format; only the least-significant 16 bits of the integer will be written to the file

    @return 0 for success

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::writei2(int s) {
   if (check_terminal_io(self, "File::writei2", xsink))
      return QoreValue();

   return f->writei2(s, xsink);
}

//! Writes a 4-byte (32 bit) integer to the file in binary big-endian format
/** @par Example:
    @code{.py}
f.writei4(val);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param i the integer to write in binary big-endian format; only the least-significant 32 bits of the integer will be written to the file

    @return 0 for success

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::writei4(int i) {
   if (check_terminal_io(self, "File::writei4", xsink))
      return QoreValue();

   return f->writei4(i, xsink);
}

//! Writes an 8-byte (64 bit) integer to the file in binary big-endian format
/** @par Example:
    @code{.py}
f.writei8(val);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param i the integer to write in binary big-endian format; only the least-significant 64 bits of the integer will be written to the file

    @return 0 for success

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::writei8(int i) {
   if (check_terminal_io(self, "File::writei8", xsink))
      return QoreValue();

   return f->writei8(i, xsink);
}

//! Writes a 2-byte (16 bit) integer to the file in binary little-endian format
/** @par Example:
    @code{.py}
f.writei2LSB(val);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param s the integer to write in binary little-endian format; only the least-significant 16 bits of the integer will be written to the file

    @return 0 for success

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::writei2LSB(int s) {
   if (check_terminal_io(self, "File::writei2LSB", xsink))
      return QoreValue();

   return f->writei2LSB(s, xsink);
}

//! Writes a 4-byte (32 bit) integer to the file in binary little-endian format
/** @par Example:
    @code{.py}
f.writei4LSB(val);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param i the integer to write in binary little-endian format; only the least-significant 32 bits of the integer will be written to the file

    @return 0 for success

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::writei4LSB(int i) {
   if (check_terminal_io(self, "File::writei4LSB", xsink))
      return QoreValue();

   return f->writei4LSB(i, xsink);
}

//! Writes an 8-byte (64 bit) integer to the file in binary little-endian format
/** @par Example:
    @code{.py}
f.writei8LSB(val);
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param i the integer to write in binary little-endian format; only the least-significant 64 bits of the integer will be written to the file

    @return 0 for success

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::writei8LSB(int i) {
   if (check_terminal_io(self, "File::writei8LSB", xsink))
      return QoreValue();

   return f->writei8LSB(i, xsink);
}

//! Writes a formatted string with soft field widths to the file
/** This method will allow arguments to overrun field width specifiers in the format string.

    @par Example:
    @code{.py}
f.printf("%5s\n", "hello there"); # outputs "hello there\n", exceeding field width
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param fmt the format string; see @ref string_formatting for more information about the format string

    @return the number of bytes written

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::f_printf() for a similar method that enforces field widths

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::printf(string[doc] fmt, ...) {
   if (check_terminal_io(self, "File::printf", xsink))
      return QoreValue();

   QoreStringNodeHolder str(q_sprintf(args, 0, 0, xsink));
   if (!str)
      return QoreValue();

   return f->write(*str, xsink);
}

//! This method variant does nothing except return a constant 0; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/** @return a constant 0
 */
int File::printf() [flags=RUNTIME_NOOP] {
   return 0;
}

//! Writes a formatted string with soft field widths to a file, where the second argument is the formatting argument list
/** This method will allow arguments to overrun field width specifiers in the format string.

    @par Example:
    @code{.py}
f.vprintf("%5s: %d\n", ("hello there", 2)); # outputs "hello there: 2\n", exceeding field width
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param fmt the format string; see @ref string_formatting for more information about the format string
    @param fmt_args the single argument or list of arguments that will be used as the argument list or the format string. If a single argument is passed instead of a list, it will be used as the first argument as if a list were passed

    @return the number of bytes written

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::f_vprintf() for a similar method that enforces field widths

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::vprintf(string[doc] fmt, any[doc] fmt_args) {
   if (check_terminal_io(self, "File::vprintf", xsink))
      return QoreValue();

   QoreStringNodeHolder str(q_vsprintf(args, 0, 0, xsink));
   if (!str)
      return QoreValue();

   return f->write(*str, xsink);
}

//! This method variant does nothing except return a constant 0; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/** @return a constant 0
 */
int File::vprintf() [flags=RUNTIME_NOOP] {
   return 0;
}

//! Writes a formatted string with hard field widths to the file
/** This method does not allow arguments to overrun field width specifiers in the format string.

    @par Example:
    @code{.py}
f.f_printf("%5s\n", "hello there"); # outputs "hello\n", enforcing the field width
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param fmt the format string; see @ref string_formatting for more information about the format string

    @return the number of bytes written

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::printf() for a similar method that does not enforce field widths

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::f_printf(string[doc] fmt, ...) {
   if (check_terminal_io(self, "File::f_printf", xsink))
      return QoreValue();

   QoreStringNodeHolder str(q_sprintf(args, 1, 0, xsink));
   if (!str)
      return QoreValue();

   return f->write(*str, xsink);
}

//! This method variant does nothing except return a constant 0; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/** @return a constant 0
 */
int File::f_printf() [flags=RUNTIME_NOOP] {
   return 0;
}

//! Writes a formatted string with hard field widths to a file, where the second argument is the formatting argument list
/** This method will not allow arguments to overrun field width specifiers in the format string.

    @par Example:
    @code{.py}
f.f_vprintf("%5s: %d\n", ("hello there", 2)); # outputs "hello: 2\n", enforcing the field width
    @endcode

    @par Events:
    @ref EVENT_DATA_WRITTEN

    @param fmt the format string; see @ref string_formatting for more information about the format string
    @param fmt_args the single argument or list of arguments that will be used as the argument list or the format string. If a single argument is passed instead of a list, it will be used as the first argument as if a list were passed

    @return the number of bytes written

    @throw FILE-WRITE-ERROR %File is not open or an I/O error occurred writing data to the File
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::vprintf() for a similar method that does not enforce field widths

    @since %Qore 0.8.7 this method throws exceptions on I/O errors to avoid silent write errors in Qore code
 */
int File::f_vprintf(string[doc] fmt, any[doc] fmt_args) {
   if (check_terminal_io(self, "File::f_vprintf", xsink))
      return QoreValue();

   QoreStringNodeHolder str(q_vsprintf(args, 1, 0, xsink));
   if (!str)
      return QoreValue();

   return f->write(*str, xsink);
}

//! This method variant does nothing except return a constant 0; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/** @return a constant 0
 */
int File::f_vprintf() [flags=RUNTIME_NOOP] {
   return 0;
}

//! Sets the @ref character_encoding "character encoding" for the file; if called with no argument, the @ref default_encoding "default encoding" is set
/** A method synonym for @ref Qore::ReadOnlyFile::setEncoding(), kept for backwards-compatibility
    @param encoding the @ref character_encoding "character encoding" for the file; if called with no argument, the @ref default_encoding "default encoding" is set

    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set
 */
nothing File::setCharset(*string encoding) {
   if (check_terminal_io(self, "File::setCharset", xsink))
      return QoreValue();

   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
   f->setEncoding(qe);
}

//! Returns the @ref character_encoding "character encoding" for the %File
/** A method synonym for @ref Qore::ReadOnlyFile::getEncoding(), kept for backwards-compatibility

    @return the @ref character_encoding "character encoding" for the %File
 */
string File::getCharset() [flags=CONSTANT] {
   return new QoreStringNode(f->getEncoding()->getCode());
}

//! Attempts to lock the file according to the arguments passed, does not block
/** Locks or unlocks a portion of the file or the entire file, for reading or writing, non-blocking. The file must be opened in the appropriate mode before this call or the call will fail with an exception.

    @par Platform Availability:
    @ref Qore::Option::HAVE_FILE_LOCKING

    @par Example:
    @code{.py}
# lock the entire file exclusively
f.lock(F_WRLCK);

# lock a section of the file for reading, start byte 512, 2K range
f.lock(F_RDLCK, 512, 2048);

# release all locks
f.lock(F_UNLCK);
    @endcode

    @param type %Type of lock (or unlock); see @ref file_locking_constants
    @param start Start byte for lock, 0 is the default (start of file)
    @param len Length in bytes for range to lock, 0 is the default (rest of file)
    @param whence Indicates how the relative offset of the file should be calculated for the lock; see @ref file_seek_constants

    @return 0 for success, @ref Err::EACCES "EACCES" if the lock would block (only in the case that the lock would block is no exception thrown and @ref Err::EACCES "EACCES" returned)

    @throw FILE-LOCK-ERROR %File is not open, lock length is negative, or the fcntl operation failed
    @throw MISSING-FEATURE-ERROR this exception is thrown when the method is not available on the runtime platform; for maximum portability, check the constant @ref Qore::Option::HAVE_FILE_LOCKING before calling this function.
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::lockBlocking() for a blocking version of this method
 */
int File::lock(softint[doc] type = F_RDLCK, softint[doc] start = 0, softint[doc] len = 0, softint[doc] whence = SEEK_SET) {
#ifdef HAVE_STRUCT_FLOCK
   if (check_terminal_io(self, "lock", xsink))
      return QoreValue();

   struct flock fl;
   if (lock_intern(fl, args, xsink))
      return QoreValue();
   return f->lock(fl, xsink);
#else
   file_lock_error("lock", xsink);
   return QoreValue();
#endif
}

//! Attempts to lock the file according to the arguments passed, blocking
/** Locks or unlocks a portion of the file or the entire file, for reading or writing, blocking. The file must be opened in the appropriate mode before this call or the call will fail with an exception.

    @par Platform Availability:
    @ref Qore::Option::HAVE_FILE_LOCKING

    @par Example:
    @code{.py}
# lock the entire file exclusively
f.lockBlocking(F_WRLCK);

# lock a section of the file for reading, start byte 512, 2K range
f.lockBlocking(F_RDLCK, 512, 2048);

# release all locks
f.lockBlocking(F_UNLCK);
    @endcode

    @param type %Type of lock (or unlock); see @ref file_locking_constants
    @param start Start byte for lock, 0 is the default (start of file)
    @param len Length in bytes for range to lock, 0 is the default (rest of file)
    @param whence Indicates how the relative offset of the file should be calculated for the lock; see @ref file_seek_constants

    @throw FILE-LOCK-ERROR %File is not open, lock length is negative, or the fcntl operation failed
    @throw MISSING-FEATURE-ERROR this exception is thrown when the method is not available on the runtime platform; for maximum portability, check the constant @ref Qore::Option::HAVE_FILE_LOCKING before calling this function.
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @see File::lockBlocking() for a blocking version of this method
 */
nothing File::lockBlocking(softint[doc] type = F_RDLCK, softint[doc] start = 0, softint[doc] len = 0, softint[doc] whence = SEEK_SET) {
#ifdef HAVE_STRUCT_FLOCK
   if (check_terminal_io(self, "File::lockBlocking", xsink))
      return QoreValue();

   struct flock fl;
   if (lock_intern(fl, args, xsink))
      return QoreValue();

   f->lockBlocking(fl, xsink);
   return QoreValue();
#else
   file_lock_error("lockBlocking", xsink);
   return QoreValue();
#endif
}

//! Returns a hash of lock information
/** @par Platform Availability:
    @ref Qore::Option::HAVE_FILE_LOCKING

    @par Example:
    @code{.py}
hash hash = f.getLockInfo();
    @endcode

    @return a hash with the following keys:
    - \c start: starting byte of the lock
    - \c len: the length in bytes of the locked region
    - \c pid: the PID of the process holding the lock
    - \c type: see @ref file_locking_constants; if no lock is set on the file, the key type has the value \c F_UNLCK.
    - \c whence: always returned as \c SEEK_SET when the call is successful

    @throw FILE-LOCK-ERROR %File is not open or the internal fcntl operation failed
    @throw MISSING-FEATURE-ERROR this exception is thrown when the method is not available on the runtime platform; for maximum portability, check the constant @ref Qore::Option::HAVE_FILE_LOCKING before calling this function.
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set
 */
hash File::getLockInfo() [flags=RET_VALUE_ONLY] {
#ifdef HAVE_STRUCT_FLOCK
    if (check_terminal_io(self, "File::getLockInfo", xsink))
        return QoreValue();

    struct flock fl;
    if (f->getLockInfo(fl, xsink))
        return QoreValue();

    QoreHashNode *h = new QoreHashNode(autoTypeInfo);
    h->setKeyValue("start", fl.l_start, xsink);
    h->setKeyValue("len", fl.l_len, xsink);
    h->setKeyValue("pid", fl.l_pid, xsink);
    h->setKeyValue("type", fl.l_type, xsink);
    h->setKeyValue("whence", fl.l_whence, xsink);

    return h;
#else
    file_lock_error("getLockInfo", xsink);
    return QoreValue();
#endif
}

//! Changes the user and group owners of the file on the filesystem (if the current user has sufficient permission to do so)
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @par Example:
    @code{.py}
f.chown(0, 0);
    @endcode

    @param uid The user id of the user to change to; -1 means do not change uid
    @param gid The group id of the user to change to; -1 means do not change gid

    @throw FILE-CHOWN-ERROR %File is not open or the chown operation failed
    @throw MISSING-FEATURE-ERROR this method is not supported on this platform; check Option::HAVE_UNIX_FILEMGT before calling this method to avoid this exception
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set
 */
nothing File::chown(softint uid, softint gid = -1) {
#ifdef HAVE_CHOWN
   if (check_terminal_io(self, "File::chown", xsink))
      return QoreValue();

   f->chown( (uid_t)uid, (gid_t)gid, xsink);
   return QoreValue();
#else
   return missing_method_error("File::chown", "UNIX_FILEMGT", xsink);
#endif
}

//! Saves the current terminal attributes for the file in the TermIOS object passed; changes the object passed as an argument to reflect the terminal attributes as set for the File
/** Do not pass a reference to the TermIOS object; pass the object itself as an argument.

    @par Platform Availability:
    @ref Qore::Option::HAVE_TERMIOS

    @par Example:
    @code{.py}
TermIOS termios();
stdin.getTerminalAttributes(termios);
    @endcode

    @param termios The method writes the current terminal attributes for the file to the object passed

    @throw FILE-OPERATION-ERROR the %File is not open
    @throw TERMIOS-GET-ERROR error reading terminal attributes from the file descriptor (ex: not a TTY)
    @throw MISSING-FEATURE-ERROR this method is not supported on this platform; check Option::HAVE_TERMIOS before calling this method to avoid this exception
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set
 */
nothing File::getTerminalAttributes(TermIOS[QoreTermIOS] termios) {
#ifdef HAVE_TERMIOS_H
   ReferenceHolder<QoreTermIOS> holder(termios, xsink);
   if (check_terminal_io(self, "File::getTerminalAttributes", xsink))
      return QoreValue();

   f->getTerminalAttributes(termios, xsink);
   return QoreValue();
#else
   return missing_method_error("File::getTerminalAttributes", "TERMIOS", xsink);
#endif
}

//! Returns the current terminal attributes for the file as a TermIOS object returned as the return value
/** @par Platform Availability:
    @ref Qore::Option::HAVE_TERMIOS

    @par Example:
    @code{.py}
TermIOS termios = stdin.getTerminalAttributes();
    @endcode

    @return the current terminal attributes for the File

    @throw FILE-OPERATION-ERROR the %File is not open
    @throw TERMIOS-GET-ERROR error reading terminal attributes from the file descriptor (ex: not a TTY)
    @throw MISSING-FEATURE-ERROR this method is not supported on this platform; check Option::HAVE_TERMIOS before calling this method to avoid this exception
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin, @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @since %Qore 0.8.7
 */
TermIOS File::getTerminalAttributes() {
#ifdef HAVE_TERMIOS_H
   if (check_terminal_io(self, "File::getTerminalAttributes", xsink))
      return QoreValue();

   ReferenceHolder<QoreTermIOS> holder(new QoreTermIOS, xsink);
   f->getTerminalAttributes(*holder, xsink);
   if (*xsink)
      return QoreValue();
   return new QoreObject(QC_TERMIOS, getProgram(), holder.release());
#else
   return (QoreObject*)missing_method_error("File::getTerminalAttributes", "TERMIOS", xsink);
#endif
}

//! Sets the current terminal attributes for the %File from the TermIOS object passed; does not change the object passed
/** @par Platform Availability:
    @ref Qore::Option::HAVE_TERMIOS

    @par Example:
    @code{.py}
TermIOS termios();
stdin.getTerminalAttributes(termios);
TermIOS orig = termios.copy();
on_exit
    stdin.setTerminalAttributes(TCSADRAIN, orig);

int lflag = termios.getLFlag();
lflag &= ~ICANON;
lflag &= ~ECHO;
lflag &= ~ISIG;
termios.setLFlag(lflag);
termios.setCC(VMIN, 1);
termios.setCC(VTIME, 0);
stdin.setTerminalAttributes(TCSADRAIN, termios);
    @endcode

    @param action a binary or'ed value of the following actions:
    - @ref Qore::TCSANOW "TCSANOW": the change occurs immediately
    - @ref Qore::TCSADRAIN "TCSADRAIN": the change occurs after all output written to the %File has been transmitted
      to the terminal
    - @ref Qore::TCSAFLUSH "TCSAFLUSH": the change occurs after all output written to the %File has been transmitted
      to the terminal
    - @ref Qore::TCSASOFT "TCSASOFT": the values of the \c c_cflag, \c c_ispeed, and \c c_ospeed fields are ignored
    @param termios the TermIOS to use

    @throw FILE-OPERATION-ERROR the %File is not open
    @throw TERMIOS-SET-ERROR error setting terminal attributes on the file descriptor
    @throw MISSING-FEATURE-ERROR this method is not supported on this platform; check Option::HAVE_TERMIOS before
    calling this method to avoid this exception
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called with a system constant object (@ref stdin,
    @ref stdout, @ref stderr) when @ref no-terminal-io is set
 */
nothing File::setTerminalAttributes(softint action = TCSANOW, TermIOS[QoreTermIOS] termios) {
#ifdef HAVE_TERMIOS_H
   ReferenceHolder<QoreTermIOS> holder(termios, xsink);
   if (check_terminal_io(self, "File::setTerminalAttributes", xsink))
      return QoreValue();

   f->setTerminalAttributes(action, termios, xsink);
   return QoreValue();
#else
   return missing_method_error("File::setTerminalAttributes", "TERMIOS", xsink);
#endif
}

//! redirects an existing file to another file
/** @par Example:
    @code{.py}
File my_file("/tmp/my_stderr.txt", O_CREAT|O_TRUNC|O_WRONLY);
stderr.redirect(my_file);
    @endcode

    @param file the file that the current object will be redirected to

    @throw FILE-REDIRECT-ERROR the underlying call to \c dup2() failed
    @throw ILLEGAL-EXPRESSION this exception is only thrown if called on or with a system constant object (@ref stdin,
    @ref stdout, @ref stderr) when @ref no-terminal-io is set

    @note Once this method has been successfully executed, the file descriptor of the current object mirrors all
    operations of the argument's file descriptor and vice-versa, however the internal state and file path information
    is not kept synchronized between the linked objects in %Qore, which must be taken into consideration when
    performing operations on objects linked through calls to this method.

    @since Qore 0.9
*/
nothing File::redirect(Qore::File[File] file) {
    ReferenceHolder<File> holder(file, xsink);

    if (check_terminal_io(self, "File::redirect", xsink) || check_terminal_io(obj_file, "File::redirect", xsink))
        return QoreValue();

    f->redirect(*file, xsink);
}

//! Returns a hash with read and write files on an internal FIFO pipe
/** @return a hash with read and write files on an internal FIFO pipe

    @note the files returned in the return value are considered "special" files that cannot be reopened

    @since %Qore 1.12
*/
static hash<PipeInfo> File::getPipe() {
#ifndef _Q_WINDOWS
    int pipefd[2];
    if (pipe(pipefd)) {
        xsink->raiseErrnoException("PIPE-ERROR", errno, "failed to create pipe");
        return QoreValue();
    }

    ReferenceHolder<QoreHashNode> rv(new QoreHashNode(hashdeclPipeInfo, xsink), xsink);

    ReferenceHolder<File> read(new File(QCS_DEFAULT), xsink);
    read->makeSpecial(pipefd[0]);
    ReferenceHolder<QoreObject> readobj(new QoreObject(QC_READONLYFILE, getProgram(), read.release()), xsink);

    ReferenceHolder<File> write(new File(QCS_DEFAULT), xsink);
    write->makeSpecial(pipefd[1]);
    ReferenceHolder<QoreObject> writeobj(new QoreObject(QC_FILE, getProgram(), write.release()), xsink);

    rv->setKeyValue("read", readobj.release(), xsink);
    rv->setKeyValue("write", writeobj.release(), xsink);

    return rv.release();
#else
    return missing_method_error("File::pipe", "UNIX_FILEMGT", xsink);
#endif
}
