//===-- File.h --------------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_File_h_
#define liblldb_File_h_

#include "lldb/Host/PosixApi.h"
#include "lldb/Utility/IOObject.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-private.h"

#include <stdarg.h>
#include <stdio.h>
#include <sys/types.h>

namespace lldb_private {

//----------------------------------------------------------------------
/// @class File File.h "lldb/Host/File.h"
/// @brief A file class.
///
/// A file class that divides abstracts the LLDB core from host file
/// functionality.
//----------------------------------------------------------------------
class File : public IOObject {
public:
  static int kInvalidDescriptor;
  static FILE *kInvalidStream;

  enum OpenOptions {
    eOpenOptionRead = (1u << 0),  // Open file for reading
    eOpenOptionWrite = (1u << 1), // Open file for writing
    eOpenOptionAppend =
        (1u << 2), // Don't truncate file when opening, append to end of file
    eOpenOptionTruncate = (1u << 3),    // Truncate file when opening
    eOpenOptionNonBlocking = (1u << 4), // File reads
    eOpenOptionCanCreate = (1u << 5),   // Create file if doesn't already exist
    eOpenOptionCanCreateNewOnly =
        (1u << 6), // Can create file only if it doesn't already exist
    eOpenOptionDontFollowSymlinks = (1u << 7),
    eOpenOptionCloseOnExec =
        (1u << 8) // Close the file when executing a new process
  };

  static mode_t ConvertOpenOptionsForPOSIXOpen(uint32_t open_options);

  File()
      : IOObject(eFDTypeFile, false), m_descriptor(kInvalidDescriptor),
        m_stream(kInvalidStream), m_options(0), m_own_stream(false),
        m_is_interactive(eLazyBoolCalculate),
        m_is_real_terminal(eLazyBoolCalculate) {}

  File(FILE *fh, bool transfer_ownership)
      : IOObject(eFDTypeFile, false), m_descriptor(kInvalidDescriptor),
        m_stream(fh), m_options(0), m_own_stream(transfer_ownership),
        m_is_interactive(eLazyBoolCalculate),
        m_is_real_terminal(eLazyBoolCalculate) {}

  //------------------------------------------------------------------
  /// Constructor with path.
  ///
  /// Takes a path to a file which can be just a filename, or a full
  /// path. If \a path is not nullptr or empty, this function will call
  /// File::Open (const char *path, uint32_t options, uint32_t permissions).
  ///
  /// @param[in] path
  ///     The full or partial path to a file.
  ///
  /// @param[in] options
  ///     Options to use when opening (see File::OpenOptions)
  ///
  /// @param[in] permissions
  ///     Options to use when opening (see File::Permissions)
  ///
  /// @see File::Open (const char *path, uint32_t options, uint32_t permissions)
  //------------------------------------------------------------------
  File(const char *path, uint32_t options,
       uint32_t permissions = lldb::eFilePermissionsFileDefault);

  //------------------------------------------------------------------
  /// Constructor with FileSpec.
  ///
  /// Takes a FileSpec pointing to a file which can be just a filename, or a
  /// full
  /// path. If \a path is not nullptr or empty, this function will call
  /// File::Open (const char *path, uint32_t options, uint32_t permissions).
  ///
  /// @param[in] filespec
  ///     The FileSpec for this file.
  ///
  /// @param[in] options
  ///     Options to use when opening (see File::OpenOptions)
  ///
  /// @param[in] permissions
  ///     Options to use when opening (see File::Permissions)
  ///
  /// @see File::Open (const char *path, uint32_t options, uint32_t permissions)
  //------------------------------------------------------------------
  File(const FileSpec &filespec, uint32_t options,
       uint32_t permissions = lldb::eFilePermissionsFileDefault);

  File(int fd, bool transfer_ownership)
      : IOObject(eFDTypeFile, transfer_ownership), m_descriptor(fd),
        m_stream(kInvalidStream), m_options(0), m_own_stream(false),
        m_is_interactive(eLazyBoolCalculate),
        m_is_real_terminal(eLazyBoolCalculate) {}

  //------------------------------------------------------------------
  /// Destructor.
  ///
  /// The destructor is virtual in case this class is subclassed.
  //------------------------------------------------------------------
  ~File() override;

  bool IsValid() const override {
    return DescriptorIsValid() || StreamIsValid();
  }

  //------------------------------------------------------------------
  /// Convert to pointer operator.
  ///
  /// This allows code to check a File object to see if it
  /// contains anything valid using code such as:
  ///
  /// @code
  /// File file(...);
  /// if (file)
  /// { ...
  /// @endcode
  ///
  /// @return
  ///     A pointer to this object if either the directory or filename
  ///     is valid, nullptr otherwise.
  //------------------------------------------------------------------
  operator bool() const { return DescriptorIsValid() || StreamIsValid(); }

  //------------------------------------------------------------------
  /// Logical NOT operator.
  ///
  /// This allows code to check a File object to see if it is
  /// invalid using code such as:
  ///
  /// @code
  /// File file(...);
  /// if (!file)
  /// { ...
  /// @endcode
  ///
  /// @return
  ///     Returns \b true if the object has an empty directory and
  ///     filename, \b false otherwise.
  //------------------------------------------------------------------
  bool operator!() const { return !DescriptorIsValid() && !StreamIsValid(); }

  //------------------------------------------------------------------
  /// Get the file spec for this file.
  ///
  /// @return
  ///     A reference to the file specification object.
  //------------------------------------------------------------------
  Status GetFileSpec(FileSpec &file_spec) const;

  //------------------------------------------------------------------
  /// Open a file for read/writing with the specified options.
  ///
  /// Takes a path to a file which can be just a filename, or a full
  /// path.
  ///
  /// @param[in] path
  ///     The full or partial path to a file.
  ///
  /// @param[in] options
  ///     Options to use when opening (see File::OpenOptions)
  ///
  /// @param[in] permissions
  ///     Options to use when opening (see File::Permissions)
  //------------------------------------------------------------------
  Status Open(const char *path, uint32_t options,
              uint32_t permissions = lldb::eFilePermissionsFileDefault);

  Status Close() override;

  void Clear();

  int GetDescriptor() const;

  WaitableHandle GetWaitableHandle() override;

  void SetDescriptor(int fd, bool transfer_ownership);

  FILE *GetStream();

  void SetStream(FILE *fh, bool transfer_ownership);

  //------------------------------------------------------------------
  /// Read bytes from a file from the current file position.
  ///
  /// NOTE: This function is NOT thread safe. Use the read function
  /// that takes an "off_t &offset" to ensure correct operation in
  /// multi-threaded environments.
  ///
  /// @param[in] buf
  ///     A buffer where to put the bytes that are read.
  ///
  /// @param[in,out] num_bytes
  ///     The number of bytes to read form the current file position
  ///     which gets modified with the number of bytes that were read.
  ///
  /// @return
  ///     An error object that indicates success or the reason for
  ///     failure.
  //------------------------------------------------------------------
  Status Read(void *buf, size_t &num_bytes) override;

  //------------------------------------------------------------------
  /// Write bytes to a file at the current file position.
  ///
  /// NOTE: This function is NOT thread safe. Use the write function
  /// that takes an "off_t &offset" to ensure correct operation in
  /// multi-threaded environments.
  ///
  /// @param[in] buf
  ///     A buffer where to put the bytes that are read.
  ///
  /// @param[in,out] num_bytes
  ///     The number of bytes to write to the current file position
  ///     which gets modified with the number of bytes that were
  ///     written.
  ///
  /// @return
  ///     An error object that indicates success or the reason for
  ///     failure.
  //------------------------------------------------------------------
  Status Write(const void *buf, size_t &num_bytes) override;

  //------------------------------------------------------------------
  /// Seek to an offset relative to the beginning of the file.
  ///
  /// NOTE: This function is NOT thread safe, other threads that
  /// access this object might also change the current file position.
  /// For thread safe reads and writes see the following functions:
  /// @see File::Read (void *, size_t, off_t &)
  /// @see File::Write (const void *, size_t, off_t &)
  ///
  /// @param[in] offset
  ///     The offset to seek to within the file relative to the
  ///     beginning of the file.
  ///
  /// @param[in] error_ptr
  ///     A pointer to a lldb_private::Status object that will be
  ///     filled in if non-nullptr.
  ///
  /// @return
  ///     The resulting seek offset, or -1 on error.
  //------------------------------------------------------------------
  off_t SeekFromStart(off_t offset, Status *error_ptr = nullptr);

  //------------------------------------------------------------------
  /// Seek to an offset relative to the current file position.
  ///
  /// NOTE: This function is NOT thread safe, other threads that
  /// access this object might also change the current file position.
  /// For thread safe reads and writes see the following functions:
  /// @see File::Read (void *, size_t, off_t &)
  /// @see File::Write (const void *, size_t, off_t &)
  ///
  /// @param[in] offset
  ///     The offset to seek to within the file relative to the
  ///     current file position.
  ///
  /// @param[in] error_ptr
  ///     A pointer to a lldb_private::Status object that will be
  ///     filled in if non-nullptr.
  ///
  /// @return
  ///     The resulting seek offset, or -1 on error.
  //------------------------------------------------------------------
  off_t SeekFromCurrent(off_t offset, Status *error_ptr = nullptr);

  //------------------------------------------------------------------
  /// Seek to an offset relative to the end of the file.
  ///
  /// NOTE: This function is NOT thread safe, other threads that
  /// access this object might also change the current file position.
  /// For thread safe reads and writes see the following functions:
  /// @see File::Read (void *, size_t, off_t &)
  /// @see File::Write (const void *, size_t, off_t &)
  ///
  /// @param[in,out] offset
  ///     The offset to seek to within the file relative to the
  ///     end of the file which gets filled in with the resulting
  ///     absolute file offset.
  ///
  /// @param[in] error_ptr
  ///     A pointer to a lldb_private::Status object that will be
  ///     filled in if non-nullptr.
  ///
  /// @return
  ///     The resulting seek offset, or -1 on error.
  //------------------------------------------------------------------
  off_t SeekFromEnd(off_t offset, Status *error_ptr = nullptr);

  //------------------------------------------------------------------
  /// Read bytes from a file from the specified file offset.
  ///
  /// NOTE: This function is thread safe in that clients manager their
  /// own file position markers and reads on other threads won't mess
  /// up the current read.
  ///
  /// @param[in] dst
  ///     A buffer where to put the bytes that are read.
  ///
  /// @param[in,out] num_bytes
  ///     The number of bytes to read form the current file position
  ///     which gets modified with the number of bytes that were read.
  ///
  /// @param[in,out] offset
  ///     The offset within the file from which to read \a num_bytes
  ///     bytes. This offset gets incremented by the number of bytes
  ///     that were read.
  ///
  /// @return
  ///     An error object that indicates success or the reason for
  ///     failure.
  //------------------------------------------------------------------
  Status Read(void *dst, size_t &num_bytes, off_t &offset);

  //------------------------------------------------------------------
  /// Read bytes from a file from the specified file offset.
  ///
  /// NOTE: This function is thread safe in that clients manager their
  /// own file position markers and reads on other threads won't mess
  /// up the current read.
  ///
  /// @param[in,out] num_bytes
  ///     The number of bytes to read form the current file position
  ///     which gets modified with the number of bytes that were read.
  ///
  /// @param[in,out] offset
  ///     The offset within the file from which to read \a num_bytes
  ///     bytes. This offset gets incremented by the number of bytes
  ///     that were read.
  ///
  /// @param[in] null_terminate
  ///     Ensure that the data that is read is terminated with a NULL
  ///     character so that the data can be used as a C string.
  ///
  /// @param[out] data_buffer_sp
  ///     A data buffer to create and fill in that will contain any
  ///     data that is read from the file. This buffer will be reset
  ///     if an error occurs.
  ///
  /// @return
  ///     An error object that indicates success or the reason for
  ///     failure.
  //------------------------------------------------------------------
  Status Read(size_t &num_bytes, off_t &offset, bool null_terminate,
              lldb::DataBufferSP &data_buffer_sp);

  //------------------------------------------------------------------
  /// Write bytes to a file at the specified file offset.
  ///
  /// NOTE: This function is thread safe in that clients manager their
  /// own file position markers, though clients will need to implement
  /// their own locking externally to avoid multiple people writing
  /// to the file at the same time.
  ///
  /// @param[in] src
  ///     A buffer containing the bytes to write.
  ///
  /// @param[in,out] num_bytes
  ///     The number of bytes to write to the file at offset \a offset.
  ///     \a num_bytes gets modified with the number of bytes that
  ///     were read.
  ///
  /// @param[in,out] offset
  ///     The offset within the file at which to write \a num_bytes
  ///     bytes. This offset gets incremented by the number of bytes
  ///     that were written.
  ///
  /// @return
  ///     An error object that indicates success or the reason for
  ///     failure.
  //------------------------------------------------------------------
  Status Write(const void *src, size_t &num_bytes, off_t &offset);

  //------------------------------------------------------------------
  /// Flush the current stream
  ///
  /// @return
  ///     An error object that indicates success or the reason for
  ///     failure.
  //------------------------------------------------------------------
  Status Flush();

  //------------------------------------------------------------------
  /// Sync to disk.
  ///
  /// @return
  ///     An error object that indicates success or the reason for
  ///     failure.
  //------------------------------------------------------------------
  Status Sync();

  //------------------------------------------------------------------
  /// Get the permissions for a this file.
  ///
  /// @return
  ///     Bits logical OR'ed together from the permission bits defined
  ///     in lldb_private::File::Permissions.
  //------------------------------------------------------------------
  uint32_t GetPermissions(Status &error) const;

  static uint32_t GetPermissions(const FileSpec &file_spec, Status &error);

  //------------------------------------------------------------------
  /// Return true if this file is interactive.
  ///
  /// @return
  ///     True if this file is a terminal (tty or pty), false
  ///     otherwise.
  //------------------------------------------------------------------
  bool GetIsInteractive();

  //------------------------------------------------------------------
  /// Return true if this file from a real terminal.
  ///
  /// Just knowing a file is a interactive isn't enough, we also need
  /// to know if the terminal has a width and height so we can do
  /// cursor movement and other terminal manipulations by sending
  /// escape sequences.
  ///
  /// @return
  ///     True if this file is a terminal (tty, not a pty) that has
  ///     a non-zero width and height, false otherwise.
  //------------------------------------------------------------------
  bool GetIsRealTerminal();

  bool GetIsTerminalWithColors();

  //------------------------------------------------------------------
  /// Output printf formatted output to the stream.
  ///
  /// Print some formatted output to the stream.
  ///
  /// @param[in] format
  ///     A printf style format string.
  ///
  /// @param[in] ...
  ///     Variable arguments that are needed for the printf style
  ///     format string \a format.
  //------------------------------------------------------------------
  size_t Printf(const char *format, ...) __attribute__((format(printf, 2, 3)));

  size_t PrintfVarArg(const char *format, va_list args);

  void SetOptions(uint32_t options) { m_options = options; }

protected:
  bool DescriptorIsValid() const { return m_descriptor >= 0; }

  bool StreamIsValid() const { return m_stream != kInvalidStream; }

  void CalculateInteractiveAndTerminal();

  //------------------------------------------------------------------
  // Member variables
  //------------------------------------------------------------------
  int m_descriptor;
  FILE *m_stream;
  uint32_t m_options;
  bool m_own_stream;
  LazyBool m_is_interactive;
  LazyBool m_is_real_terminal;
  LazyBool m_supports_colors;

private:
  DISALLOW_COPY_AND_ASSIGN(File);
};

} // namespace lldb_private

#endif // liblldb_File_h_
