// bdls_fdstreambuf.h                                                 -*-C++-*-

// ----------------------------------------------------------------------------
//                                   NOTICE
//
// This component is not up to date with current BDE coding standards, and
// should not be used as an example for new development.
// ----------------------------------------------------------------------------

#ifndef INCLUDED_BDLS_FDSTREAMBUF
#define INCLUDED_BDLS_FDSTREAMBUF

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a stream buffer initialized with a file descriptor.
//
//@CLASSES:
//   bdls::FdStreamBuf: stream buffer constructed with file descriptor
//
//@SEE_ALSO: <bsl::streambuf>
//
//@DESCRIPTION: This component implements a class, 'bdls::FdStreamBuf', derived
// from the C++ standard library's 'bsl::streambuf' that can be associated with
// a file descriptor.  Except for the 'pubimbue' function, all of the actions
// that can be performed on an 'bsl::streambuf' can be performed on a
// 'bdls::FdStreamBuf'.  An 'bsl::streambuf' provides public methods for
// reading from and writing to a stream of data, which are implemented in terms
// of protected virtual functions.  A 'bdls::FdStreamBuf' provides an
// implementation of these protected virtual members such that they operate on
// a given file descriptor.  The file descriptor can represent a file, a pipe,
// or other device, and it can be associated with the 'bdls::FdStreamBuf' at
// construction, or by calling the 'reset' method.  Note that a 'bsl::stream'
// can be initialized with a 'bdls::FdStreamBuf', making it possible to
// associate the stream with a file descriptor.
//
// Note that the 'pubimbue' function may be called, but not with any value
// other than 'bsl::locale()'.  Furthermore, when called with this value, it
// has no effect.
//
// The file descriptor type 'bdls::FilesystemUtil::FileDescriptor' used in this
// component is, on Unix, an 'int' type returned by 'open', and on Windows, a
// 'HANDLE' type returned by 'CreateFile'.  Ideally, a user would open the file
// and obtain the platform-independent 'bdls::FilesystemUtil::FileDescriptor'
// by calling 'bdls::FilesystemUtil::open', which will call the appropriate
// routine for the platform and return a
// 'bdls::FilesystemUtil::FileDescriptor'.  A value of
// 'bdls::FilesystemUtil::k_INVALID_FD' is used to represent an invalid file
// handle on both platforms.
//
// On Windows for a file in text mode, the byte '0x1a' (ctrl-Z) is recognized
// as an end of file marker.  If it is encountered, it is not returned in the
// buffer and subsequent reads will indicate that no more input is available.
// The behavior is undefined if it is not the last byte in the file.  Other
// types of files are not required to end with '0x1a'.  For files on Unix and
// files opened in binary mode on Windows, '0x1a' is treated like any other
// byte.
//
// Note that the public methods of the 'bsl::streambuf' class used in the usage
// example are not described here.  See documentation in
// "The C++ Programming Language, Third Edition", by Bjarne Stroustrup,
// Section 21.6.4, and on the web at:
//..
// http://www.cplusplus.com/reference/iostream/streambuf
//..
// Note that the 'bdls::FdStreamBuf' and 'bdls::FdStreamBuf_FileHandler'
// classes here are based on STLPort's implementation of 'filebuf' and
// '_Filebuf_Base' respectively, with copyright notice as follows:
//..
// ----------------------------------------------------------------------------
// Copyright (c) 1999
// Silicon Graphics Computer Systems, Inc.
//
// Copyright (c) 1999
// Boris Fomitchev
//
// This material is provided "as is", with absolutely no warranty expressed
// or implied.  Any use is at your own risk.
//
// Permission to use or copy this software for any purpose is hereby granted
// without fee, provided the above notices are retained on all copies.
// Permission to modify the code and to distribute modified code is granted,
// provided the above notices are retained, and a notice that the code was
// modified is included with the above copyright notice.
// ----------------------------------------------------------------------------
//..
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Stream Initialization
/// - - - - - - - - - - - - - - - -
// The most common usage of this component is to initialize a stream.  In this
// case, the 'bdls::FdStreamBuf' will be used for either input or output, but
// not both.
//
// First we create a suitable file name, and make sure that no file of that
// name already exists:
//..
//  char fileNameBuffer[100];
//  bsl::sprintf(fileNameBuffer,
//#ifdef BSLS_PLATFORM_OS_UNIX
//               "/tmp/bdls_FdStreamBuf.usage.1.%d.txt",
//#else // windows
//               "C:\\TEMP\\bdls_FdStreamBuf.usage.1.%d.txt";
//#endif
//               bdls::ProcessUtil::getProcessId());
//
//  bdls::FilesystemUtil::remove(fileNameBuffer);
//  assert(0 == bdls::FilesystemUtil::exists(fileNameBuffer));
//..
// Then we create the file and open a file descriptor to it; the boolean
// flags indicate that the file is to be writable, and not previously existing
// (and therefore must be created):
//..
//  typedef bdls::FilesystemUtil::FileDescriptor FdType;
//
//  FdType fd = bdls::FilesystemUtil::open(fileNameBuffer,
//                                         bdls::FilesystemUtil::e_CREATE,
//                                         bdls::FilesystemUtil::e_READ_WRITE);
//  assert(bdls::FilesystemUtil::k_INVALID_FD != fd);
//..
// Next we create a 'bdls::FdStreamBuf' associated with file descriptor
// 'fd'; the 'false' argument indicates that 'streamBuffer' will not assume
// ownership of 'fd', meaning that when 'streamBuffer' is destroyed 'fd' will
// remain open:
//
// Note also that the stream buffer defaults to being in text mode on Windows,
// and binary mode on Unix.
//..
//  {
//      bdls::FdStreamBuf streamBuffer(fd,
//                                     true,    // writable
//                                     false);  // 'fd' won't be closed
//                                              // when 'streamBuffer' is
//                                              // destroyed
//
//      bsl::ostream os(&streamBuffer);
//
//      os << "Five times nine point five = " << 5 * 9.5 << bsl::endl;
//  }
//..
// Note also that the stream buffer defaults to being in text mode on
// Windows, and binary mode on Unix.
//
// Now create a new stream buffer to read the file back, in this case
// using binary mode so we can see exactly what was written.  The new
// stream buf is used to initialize an input stream.
//..
//  {
//      // read it in binary mode
//
//      bdls::FdStreamBuf streamBuffer(fd,
//                                     false,  // not writable
//                                     false,  // 'streamBuffer' does not
//                                             // own 'fd'
//                                     true);  // binary mode
//
//      streamBuffer.pubseekpos(0);
//
//      char buf[100];
//      bsl::memset(buf, 0, sizeof(buf));
//
//      bsl::istream is(&streamBuffer);
//      char *pc = buf;
//      do {
//          is >> bsl::noskipws >> *pc++;
//      } while ('\n' != pc[-1]);
//
//#ifdef BSLS_PLATFORM_OS_UNIX
//      assert(!bsl::strcmp("Five times nine point five = 47.5\n", buf));
//#else
//      //On Windows we see a CRLF ('\r\n') instead of a simple LF '\n'
//      assert(!bsl::strcmp("Five times nine point five = 47.5\r\n", buf));
//#endif
//  }
//..
// Finally, read the file back a second time, this time in text mode.  Note
// how, on Windows, the '\r\n' is translated back to '\n'
//..
//  {
//      // read it back in text mode
//
//      bdls::FdStreamBuf streamBuffer(fd,
//                                     false);  // not writable
//                                              // 'fd' will be closed when
//                                              // streamBuffer is destroyed.
//                                              // Mode will be binary on
//                                              // Unix, text on Dos.
//      streamBuffer.pubseekpos(0);
//
//      char buf[100];
//      bsl::memset(buf, 0, sizeof(buf));
//
//      bsl::istream is(&streamBuffer);
//      char *pc = buf;
//      do {
//          is >> bsl::noskipws >> *pc++;
//      } while ('\n' != pc[-1]);
//
//      assert(!bsl::strcmp("Five times nine point five = 47.5\n", buf));
//  }
//..
// And finally, we clean up:
//..
//  bdls::FilesystemUtil::remove(fileNameBuffer);
//..
//
///Example 2: Streambuf
/// - - - - - - - - - -
// For our second example we will create a 'bdls::FdStreamBuf' associated with
// a temporary file, and then use the public methods of the base class
// interface, including 'sputn', 'sgetn' and 'pubseekpos', to do some I/O and
// seeking on it.
//..
//  const char line1[] = "To be or not to be, that is the question.\n";
//  const char line2[] =
//                     "There are more things in heaven and earth,\n"
//                     "Horatio, than are dreamt of in your philosophy.\n";
//  const char line3[] = "Wherever you go, there you are.  B Banzai\n";
//
//  const int lengthLine1 = sizeof(line1) - 1;
//  const int lengthLine2 = sizeof(line2) - 1;
//  const int lengthLine3 = sizeof(line3) - 1;
//..
// We start by selecting a file name for our (temporary) file.
//..
//  char fileNameBuffer[100];
//  bsl::sprintf(fileNameBuffer,
//#ifdef BSLS_PLATFORM_OS_UNIX
//               "/tmp/bdls_FdStreamBuf.usage.2.%d.txt",
//#else // windows
//               "C:\\TEMP\\bdls_FdStreamBuf.usage.2.%d.txt",
//#endif
//               bdls::ProcessUtil::getProcessId());
//..
// Then, make sure the file does not already exist:
//..
//  bdls::FilesystemUtil::remove(fileNameBuffer);
//  assert(false == bdls::FilesystemUtil::exists(fileNameBuffer));
//..
// Next, Create the file and open a file descriptor to it.  The boolean
// flags indicate that the file is writable, and not previously
// existing (and therefore must be created):
//..
//  typedef bdls::FilesystemUtil::FileDescriptor FdType;
//
//  FdType fd = bdls::FilesystemUtil::open(fileNameBuffer,
//                                         bdls::FilesystemUtil::e_CREATE,
//                                         bdls::FilesystemUtil::e_READ_WRITE);
//  assert(bdls::FilesystemUtil::k_INVALID_FD != fd);
//..
// Now, we create a 'bdls::FdStreamBuf' object named 'streamBuffer'
// associated with the file descriptor 'fd'.  Note that 'streamBuffer'
// defaults to assuming ownership of 'fd', meaning that when
// 'streamBuffer' is cleared, reset, or destroyed, 'fd' will be closed.
// Note that 'FdStreamBuf' implements 'streambuf', which provides the
// public methods used in this example:
//..
//  bdls::FdStreamBuf streamBuffer(fd, true);
//
//  assert(streamBuffer.fileDescriptor() == fd);
//  assert(streamBuffer.isOpened());
//..
// Next we use the 'sputn' method to write two lines to the file:
//..
//  streamBuffer.sputn(line1, lengthLine1);
//  streamBuffer.sputn(line2, lengthLine2);
//..
// Then we seek back to the start of the file.
//..
//  bsl::streamoff status = streamBuffer.pubseekpos(0);
//  assert(0 == status);
//..
// Next, we read the first 'lengthLine1' characters of the file
// into 'buf', with the method 'sgetn'.
//..
//  char buf[1000];
//  bsl::memset(buf, 0, sizeof(buf));
//  status = streamBuffer.sgetn(buf, lengthLine1);
//  assert(lengthLine1 == status);
//  assert(!bsl::strcmp(line1, buf));
//..
// Next we try to read '2 * lengthLine2' characters when only
// 'lengthLine2' characters are available in the file to read, so
// the 'sgetn' method will stop after reading 'lengthLine2' characters.
// The 'sgetn' method will return the number of chars successfully
// read:
//..
//  bsl::memset(buf, 0, sizeof(buf));
//  status =  streamBuffer.sgetn(buf, 2 * lengthLine2);
//  assert(lengthLine2 == status);
//  assert(!bsl::strcmp(line2, buf));
//..
// Trying to read past the end of the file invalidated the current
// cursor position in the file, so we must seek from the end or the
// beginning of the file in order to establish a new cursor position.
// Note the 'pubseekpos' method always seeks relative to the beginning.
// We seek back to the start of the file:
//..
//  status = streamBuffer.pubseekpos(0);
//  assert(0 == status);
//..
// Note that line1 and line3 are the same length:
//..
//  assert(lengthLine1 == lengthLine3);
//..
// Then we write, replacing 'line1' in the file with 'line3':
//..
//  status = streamBuffer.sputn(line3, lengthLine3);
//  assert(lengthLine3 == status);
//..
// Now we seek back to the beginning of the file:
//..
//  status = streamBuffer.pubseekpos(0);
//..
// Next we verify we were returned to the start of the file:
//..
//  assert(0 == status);
//..
// Then we read and verify the first line, which now contains the text
// of 'line3':
//..
//  bsl::memset(buf, 0, sizeof(buf));
//  status = streamBuffer.sgetn(buf, lengthLine3);
//  assert(lengthLine3 == status);
//  assert(!bsl::strcmp(line3, buf));
//..
// Now we read and verify the second line, still 'line2':
//..
//  bsl::memset(buf, 0, sizeof(buf));
//  status = streamBuffer.sgetn(buf, lengthLine2);
//  assert(lengthLine2 == status);
//  assert(!bsl::strcmp(line2, buf));
//..
// Next we close 'fd' and disconnect 'streamBuffer' from 'fd':
//..
//  status = streamBuffer.clear();
//  assert(0 == status);
//..
// Note that 'streamBuffer' is now no longer open, and is not
// associated with a file descriptor:
//..
//  assert(!streamBuffer.isOpened());
//  assert(bdls::FilesystemUtil::k_INVALID_FD ==
//         streamBuffer.fileDescriptor());
//..
// Finally, we clean up the file:
//..
//  bdls::FilesystemUtil::remove(fileNameBuffer);
//..

#include <bdlscm_version.h>

#include <bdls_filesystemutil.h>

#include <bslma_allocator.h>

#include <bsls_assert.h>
#include <bsls_atomicoperations.h>
#include <bsls_platform.h>
#include <bsls_review.h>
#include <bsls_types.h>

#include <bsl_algorithm.h>
#include <bsl_cstddef.h>
#include <bsl_cstring.h>    // 'size_t'
#include <bsl_ios.h>
#include <bsl_iosfwd.h>
#include <bsl_locale.h>
#include <bsl_streambuf.h>  // 'char_type', 'int_type', 'pos_type', 'off_type',
                            // 'traits_type' are within the 'bsl::streambuf'
                            // class

namespace BloombergLP {
namespace bdls {

                    // ====================================
                    // helper class FdStreamBuf_FileHandler
                    // ====================================

class FdStreamBuf_FileHandler {
    // This private helper class isolates direct operations on files from the
    // 'FdStreamBuf' class; it is a thin wrapper around 'FilesystemUtil'.  One
    // service this class provides is converting between an in-process '\n' and
    // its corresponding on-file '\r\n' when writing to or reading from a
    // Windows text file.  On 'reset' an object of this type is associated with
    // a supplied file descriptor, after which it can do simple operations on
    // that file descriptor in the service of a 'FdStreamBuf'.

  private:
    // CLASS DATA
    static bsls::AtomicOperations::AtomicTypes::Int
           s_pageSize;             // page size associated with this operating
                                   // system

    // DATA
    FilesystemUtil::FileDescriptor
           d_fileId;               // file descriptor, which is owned if
                                   // 'd_willCloseOnResetFlag' is 'true',
                                   // otherwise not owned

    bool   d_openedFlag;           // 'true' if this object is associated with
                                   // a valid file descriptor, and 'false'
                                   // otherwise

    bool   d_regularFileFlag;      // 'true' if the file descriptor represents
                                   // a plain file (and not a directory or
                                   // other device), and 'false' otherwise

    bsl::ios_base::openmode
           d_openModeFlags;        // 'ios_base'-style flags with which the
                                   // file or device was opened

    bool   d_willCloseOnResetFlag; // 'true' if the file descriptor should be
                                   // closed when this file handler is reset,
                                   // cleared or destroyed, and 'false'
                                   // otherwise

    char   d_peekBuffer;           // buffer used when looking one byte ahead
                                   // to complete a '\r\n' in text mode

    bool   d_peekBufferFlag;       // 'true' if peek buffer contains a
                                   // character, 'false' otherwise.  Note this
                                   // is never true on Unix or in binary mode
                                   // on Windows.

  private:
    // PRIVATE MANIPULATORS
#ifdef BSLS_PLATFORM_OS_WINDOWS
    int windowsWriteText(const char *buffer, int numChars);
        // Write the specified 'numChars' characters from the specified
        // 'buffer' to this object's file descriptor.  Return the number of
        // characters successfully written on success, and a negative value
        // otherwise.  Note that '\n's in the specified 'buffer' will be
        // translated to '\r\n' sequences on output.  Also note that this
        // method does not exist and is not called except on Windows.
#endif

  private:
    // NOT IMPLEMENTED
    FdStreamBuf_FileHandler(const FdStreamBuf_FileHandler &);
    FdStreamBuf_FileHandler &operator=(const FdStreamBuf_FileHandler &);

  public:
    // CLASS METHODS
    static bsl::size_t pageSize();
        // Return the operating system's page size.

    // CREATORS
    FdStreamBuf_FileHandler();
        // Create a file handler that is not associated with any file
        // descriptor.  Note that 'isOpened' will be 'false' on the newly
        // created object.

    ~FdStreamBuf_FileHandler();
        // Destroy this file handler.  If 'willCloseOnReset' is 'true', close
        // any file descriptor associated with this object.

    // MANIPULATORS
    int reset(FilesystemUtil::FileDescriptor fileDescriptor,
              bool                           writableFlag,
              bool                           willCloseOnResetFlag = true,
              bool                           binaryModeFlag = false);
        // Associate this object with the specified 'fileDescriptor', and
        // record the state of the specified 'writableFlag' which, if 'true',
        // indicates that the 'fileDescriptor' is writable, otherwise it is
        // not.  Before making this association, if, prior to this call,
        // 'willCloseOnReset' is true, close any file descriptor previously
        // associated with this object, otherwise leave it open but
        // disassociate this object from it.  The optionally specified
        // 'willCloseOnResetFlag' will set 'willCloseOnReset', which, if
        // 'true', indicates that 'fileDescriptor' is to be closed when this
        // object is cleared, reset, or destroyed, otherwise no action will be
        // taken on 'fileDescriptor' at that time.  Optionally specify a
        // 'binaryModeFlag', which is ignored on Unix; if 'false' on Windows,
        // it indicates that '\n's internally are to be translated to and from
        // '\r\n' sequences on the device; on Unix or if 'binaryModeFlag' is
        // 'true' no such translation is to occur.  Return 0 on success, and a
        // non-zero value otherwise.  Note that if
        // 'FilesystemUtil::k_INVALID_FD' is passed as 'fileDescriptor', no
        // file descriptor is to be associated with this object.  Also note
        // that the state of 'fileDescriptor' is unchanged by this call, there
        // is no implicit seek.

    void release();
        // Disassociate this file handler from any file descriptor with which
        // it may be associated without closing that file descriptor.  This
        // method succeeds with no effect if 'isOpened' was 'false'.  Note that
        // 'fileDescriptor' is 'FilesystemUtil::k_INVALID_FD' after this call.

    int clear();
        // Release any file descriptor that may be associated with this file
        // handler.  If 'isOpened' and 'willCloseOnReset' are both 'true', the
        // file descriptor will be closed, otherwise it will be left unchanged.
        // Return 0 on success and a non-zero value if the close fails.  This
        // method succeeds with no effect if 'isOpened' was 'false'.  Note that
        // 'fileDescriptor' is always 'FilesystemUtil::k_INVALID_FD' after this
        // call.

    int read(char *buffer, int numBytes);
        // Read the specified 'numBytes' bytes from the current position of the
        // file descriptor into the specified 'buffer'.  Return the number of
        // characters successfully read.  The behavior is undefined unless
        // '0 <= numBytes' and 'buffer' is at least 'numBytes' long.  Note that
        // on Windows in text mode, '\r\n' is read as a single character and
        // stored in the buffer as '\n'.

    int write(const char *buffer, int numBytes);
        // Write the specified 'buffer', containing the specified 'numBytes',
        // to the file descriptor starting at the current position.  Return 0
        // on success, and a non-zero value otherwise.  The behavior is
        // undefined unless '0 <= numBytes'.  Note that on Windows in text
        // mode, a '\n' is written as '\r\n' and counts as one character.

    bsl::streampos seek(bsl::streamoff offset, FilesystemUtil::Whence dir);
        // Set the file position associated with this object according to the
        // specified 'offset' and 'dir' behavior.
        //..
        //   * If 'dir' is 'FilesystemUtil::e_SEEK_FROM_BEGINNING', set the
        //     position to 'offset' bytes from the beginning of the file.
        //   * If 'dir' is 'FilesystemUtil::e_SEEK_FROM_CURRENT', advance the
        //     position by 'offset' bytes
        //   * If 'dir' is 'FilesystemUtil::e_SEEK_FROM_END', set the position
        //     to 'offset' bytes beyond the end of the file.
        //..
        // Return the new location of the file position, in bytes from the
        // beginning of the file on success; and 'FilesystemUtil::k_INVALID_FD'
        // otherwise.  The effect on the file position is undefined unless the
        // file descriptor represents a device capable of seeking.  Note that
        // 'seek' does not change the size of the file if the position advances
        // beyond the end of the file; instead, the next write at the pointer
        // will increase the file size.  Also note that on Windows in text
        // mode, 'offset' will be the number of bytes on disk passed over,
        // including '\r's in '\r\n' sequences.

    void *mmap(bsl::streamoff offset, bsl::streamoff length);
        // Map to memory a section of the file starting at the specified
        // 'offset' from the start of the file and return a pointer to that
        // memory.  The section mapped is to be of the specified 'length'.
        // The behavior is undefined unless 'offset' is a multiple of
        // 'pageSize'.  Note that the memory is mapped for readonly access.

    void unmap(void *mappedMemory, bsl::streamoff length);
        // Unmap the section of memory beginning at the specified
        // 'mappedMemory', having the specified 'length'.  The behavior is
        // undefined unless 'mappedMemory' is an address returned by a previous
        // call to the 'mmap' method and 'length' was the 'length' specified in
        // that call.

    void setWillCloseOnReset(bool booleanValue);
        // Set 'willCloseOnReset' (the flag determining whether this file
        // handler will close the file descriptor on the next reset, clear, or
        // destruction) to the specified 'booleanValue'.  If 'willCloseOnReset'
        // is 'true', the next reset, clear, or destruction will result in the
        // file descriptor being closed, otherwise, it will remain open.

    // ACCESSORS
    FilesystemUtil::FileDescriptor fileDescriptor() const;
        // Return the file descriptor associated with this object, if
        // 'isOpened' is 'true', and -1 otherwise.

    bsl::streamoff fileSize() const;
        // Return the size of the file associated with this file handler, or
        // 0 if it is associated with a device other than a regular file (e.g.,
        // a device or directory).

    bsl::streamoff getOffset(char *first, char *last) const;
        // Return the number of bytes that the data in the range specified by
        // '[first, last)' will fill when written to the file descriptor.  Note
        // that on Unix, or for a binary file on Windows, this value will be
        // 'last - first', but for on Windows in text mode, extra bytes are
        // added when '\n' would be written to the file descriptor as '\r\n'.

    bool isInBinaryMode() const;
        // Return 'false' if on Windows and the file is opened in text mode,
        // and 'true' otherwise.

    bool isOpened() const;
        // Return 'true' if this file handler is currently associated with a
        // file descriptor, and 'false' otherwise.

    bool isRegularFile() const;
        // Return 'true' if the file descriptor associated with this file
        // handler is associated with a regular file and 'false' otherwise.
        // Note that directories and pipes are not regular files.

    int openMode() const;
        // Return the 'bsl::ios_base' mode bits corresponding to this file
        // handler.  Note that this will be a union (bitwise-OR) of a subset of
        // the 'bsl::ios_base' constants 'in', 'out', and 'binary'.

    bool willCloseOnReset() const;
        // Return 'true' if the associated file descriptor will be closed the
        // next time this file handler is reset, cleared, or destroyed, and
        // 'false' otherwise.  Note that this value is determined by the value
        // of 'willCloseOnResetFlag' that was passed to the most recent call to
        // 'reset' or 'setWillCloseOnReset'.
};

                             // =================
                             // class FdStreamBuf
                             // =================

class FdStreamBuf : public bsl::streambuf {
    // This class, derived from the C++ standard library class
    // 'bsl::streambuf', is a mechanism that can be associated with an opened
    // file descriptor, and, except for changing the locale, enables the caller
    // to invoke all the standard 'bsl::streambuf' operations on that file
    // descriptor.  Note that objects of this class are always in exactly one
    // of the 5 modes outlined in the enum 'FdStreamBuf::FdStreamBufMode'.

  private:
    // PRIVATE TYPES
    enum { k_PBACK_BUF_SIZE = 8 }; // size of d_pBackBuf

    enum FdStreamBufMode  {
        e_NULL_MODE          = 0,  // empty state, when not in any other mode;
                                   // the object is constructed in this state

        e_INPUT_MODE         = 1,  // doing input

        e_INPUT_PUTBACK_MODE = 2,  // input putback mode is a form of input
                                   // mode where chars that have been stuffed
                                   // back into the input buffer are kept in
                                   // 'd_pBackBuf'.

        e_OUTPUT_MODE        = 3,  // doing output

        e_ERROR_MODE         = 4   // An error has occurred.  Note that error
                                   // mode is sticky -- subsequent I/O won't
                                   // work until error mode is cleared by a
                                   // 'reset' or a seek.
    };

  private:
    // DATA
                        // data members used in all modes

    FdStreamBuf_FileHandler
                      d_fileHandler;      // file handler, holds the file
                                          // descriptor, used for doing low
                                          // level operations on the file
                                          // descriptor

                        // mode information

    FdStreamBufMode   d_mode;

    bool              d_dynamicBufferFlag;// 'true' if the buffer 'd_buf_p' is
                                          // heap allocated, 'false' if it was
                                          // supplied by the user.

                        // putback buffer

    char              d_pBackBuf[k_PBACK_BUF_SIZE];
                                          // for putback mode (see above)

                        // input/output buffer

    char             *d_buf_p;            // buffer
    char             *d_bufEOS_p;         // end of buffer space, allocated or
                                          // otherwise
    char             *d_bufEnd_p;         // end of data that's been read in
                                          // input mode, not used in output
                                          // mode.

                        // data members saved when entering putback mode --
                        // these elements are for saving fields from the base
                        // class while we are in putback mode

    char             *d_savedEback_p;     // saved value of 'eback'
    char             *d_savedGptr_p;      // saved value of 'gptr'
    char             *d_savedEgptr_p;     // saved value of 'egptr'

                        // fields relevant to mapping the file while in input
                        // mode

    char             *d_mmapBase_p;       // pointer to the 'mmap'ed input
                                          // area, 0 if we are not in 'mmap'
                                          // input mode

    bsl::streamoff    d_mmapLen;          // length of mapped area

                        // memory allocator

    bslma::Allocator *d_allocator_p;      // allocator (held, not owned)

  private:
    // PRIVATE MANIPULATORS
    void exitPutbackMode();
        // Exit putback mode (leaving this object in input mode) and restore
        // the get buffer to the state it was in just prior to entering putback
        // mode.  The behavior is undefined unless this object is in putback
        // mode.

    int switchToInputMode();
        // Switch this object to input mode.  Return 0 on success, and a
        // non-zero value otherwise.  If this method is called while in input
        // putback mode, exit input putback mode.  Note that this function is
        // called when doing the first input, after a seek, or after writing.
        // Also note that this method has no effect if called when this object
        // is in input mode.

    int exitInputMode(bool correctSeek);
        // Change from input mode to null mode.  If the specified 'correctSeek'
        // is 'true', seek to position the file pointer at the point from
        // which the next input would have come; otherwise don't do the seek.
        // If the input file is currently mapped, unmap it.  Return 0 on
        // success, and non-zero otherwise.  The behavior is undefined unless
        // this object is in input or input_putback mode.  Note that
        // performing a corrective seek corrects the discrepancy between the
        // client's perception of the file pointer location and the actual
        // file pointer location, caused by buffering.

    int switchToOutputMode();
        // Switch this object to output mode.  Return 0 on success, and a
        // non-zero value otherwise.  Note that this method has no effect if
        // this object is already in output mode.  Also note that this method
        // is called when performing the first output, or when performing the
        // first output after a seek or read.

    int underflowRead();
        // Use 'read' to get some data from the file descriptor, and add it to
        // the input buffer.  Return the first character of input.  Note that
        // this method is called only by 'underflow', and only as a last
        // resort, when additional data can't be provided by mapping.

    int inputError();
        // Put this object into error mode, clearing the get area.  Always
        // return 'traits_type::eof()'.  Note that error mode is sticky and is
        // cleared only by a 'reset', 'clear' or seek.

    int outputError();
        // Put this object into error mode, clearing the put area.  Always
        // return 'traits_type::eof()'.  Note that error mode is sticky and is
        // cleared only by a 'reset', 'clear', 'seekoff', or 'seekpos'.

    int allocateBuffer(char *buffer, int numBytes);
        // Set the buffer to be used by this object.  If the specified 'buffer'
        // is 0, dynamically allocate a buffer of specified 'numBytes' length,
        // or if 'buffer' is a non-zero value, use the first 'numBytes' bytes
        // of 'buffer'.  Return 0 on success, and a non-zero value otherwise.
        // The behavior is undefined unless '1 <= numBytes', 'buffer' (if
        // non-zero) is at least 'numBytes' long, a buffer has not previously
        // been allocated or provided, and no I/O has occurred prior to this
        // call.

    int allocateBuffer();
        // Dynamically allocate an input/output buffer of a default size.
        // Return 0 on success, and a non-zero value otherwise.  The behavior
        // is undefined unless no buffer has previously been allocated or
        // provided, and no I/O has occurred prior to this call.

    void deallocateBuffer();
        // If the buffer is dynamically allocated by 'allocateBuffer', free
        // it.  The behavior is undefined unless the buffer was previously
        // allocated or provided.

    int seekInit();
        // Prepare this 'FdStreamBuf' for a subsequent seek operation by
        // setting various mode information into an appropriate state.  If in
        // output mode, flush the buffer.  If in putback input mode or error
        // mode, exit that mode.  If in regular input mode, leave that mode
        // unaffected.  Return 0 on success, and a non-zero value otherwise.

    pos_type seekReturn(pos_type offset);
        // Finish a seek by putting this object into null mode and nulling out
        // pointers to the buffer to reflect the fact that a seek has occurred.
        // Return the specified 'offset' on success, and a negative value
        // otherwise.

    int flush();
        // Flush any output data to the file descriptor, and reset the state of
        // this object.  Return 0 on success, and a non-zero value otherwise.

  protected:
    // PROTECTED MEMBER FUNCTIONS

    // The following member functions override protected virtual functions
    // inherited from the base class, and are specified to be protected as part
    // of the standard library 'bsl::streambuf' interface.

    // PROTECTED MANIPULATORS
    virtual int_type underflow();
        // Replenish the input buffer with data obtained from the file
        // descriptor, and return the next character of input (or eof if no
        // input is available).  Note that in windows text mode, '\r\n'
        // sequences on the device will be translated to '\n's.

    virtual int_type pbackfail(int_type c = traits_type::eof());
        // If the optionally specified 'c' is not given, move the current input
        // position back one character and return the character at that
        // position.  Otherwise specify a value for 'c' other than
        // 'traits_type::eof'.  If 'c' is equal to the previous character in
        // the read buffer, the behavior is the same as if 'eof()' was passed.
        // If 'c' is not eof and is not equal to the previous character in the
        // putback buffer push the character 'c' is back into the input buffer,
        // if possible.  Return the backed up character on success and
        // 'traits_type::eof()' otherwise.  If the input buffer is readonly, or
        // 'gptr()' is already at the beginning of the input buffer, this
        // object enters 'INPUT_PUTBACK_MODE' and 'c' is stuffed back into the
        // putback buffer.  Note that only 'PBACK_BUF_SIZE' characters can be
        // backed up into the putback buffer, if this limit is exceeded,
        // 'traits_type::eof()' will be returned.  Also note that this method
        // is called by public methods 'sputbackc' or 'sungetc' in the base
        // class, and only when simply decrementing the current position in the
        // input buffer won't satisfy the request, either because 'c' doesn't
        // match the previously input character, or because the input position
        // is already at the beginning of the input buffer.

    virtual int_type overflow(int_type c = traits_type::eof());
        // If in output mode, write the contents of the buffer to output.
        // Return 'traits_type::eof()' on failure, and any other value on
        // success.  Optionally specify a character 'c' to be appended to the
        // buffer prior to the flush.  If no character is specified, no
        // character is appended to the buffer.  If not in output mode, switch
        // to output mode.  Note that the write will translate '\n's to
        // '\r\n's.

    virtual FdStreamBuf *setbuf(char_type *buffer, bsl::streamsize numBytes);
        // Use the specified 'buffer' of the specified 'numBytes' capacity as
        // the input/output buffer for this 'streambuf'.  If 'buffer == 0', the
        // buffer is dynamically allocated with a default size.  If both
        // 'buffer' and 'numBytes' are zero a 1-byte buffer is dynamically
        // allocated.  The behavior is undefined if any I/O has preceded this
        // call, and unless the buffer is uninitialized before this call.

    virtual pos_type seekoff(
        off_type                offset,
        bsl::ios_base::seekdir  whence,
        bsl::ios_base::openmode mode = bsl::ios_base::in | bsl::ios_base::out);
        // Set the file pointer associated with the file descriptor according
        // to the specified 'offset' and 'whence':
        //..
        // * If 'whence' is 'bsl::ios_base::beg', set the pointer to 'offset'
        //   bytes from the beginning of the file.
        // * If 'whence' is 'bsl::ios_base::cur', advance the pointer by
        //   'offset' bytes
        // * If 'whence' is 'bsl::ios_base::end', set the pointer to 'offset'
        //   bytes beyond the end of the file.
        //..
        // Optionally specify 'mode', which is ignored.  Return the new
        // location of the file position, in bytes from the beginning of the
        // file, on success, and -1 otherwise.  The behavior is undefined
        // unless the file descriptor is on a device capable of seeking.  Note
        // that seeking does not change the size of the file if the pointer
        // advances beyond the end of the file; instead, the next write at the
        // pointer will increase the file size.  Also note that seeks are
        // always in terms of bytes on the device, meaning that in Windows
        // text mode, seeking past a '\n' perceived by the caller will count as
        // 2 bytes since it has to seek over a '\r\n' sequence on the device.

    virtual pos_type seekpos(
        pos_type                offset,
        bsl::ios_base::openmode mode = bsl::ios_base::in | bsl::ios_base::out);
        // Seek to the specified 'offset' relative to the beginning of the
        // file.  Return the resulting absolute position in the file relative
        // to the beginning.  Optionally specify 'mode' which is ignored.  Also
        // note that seeks are always in terms of bytes on the device, meaning
        // that on a Windows text file, seeking past a '\r\n' sequence on the
        // disk will count as two bytes, though if it is read in it will be a
        // single '\n' byte.

    virtual int sync();
        // If in output mode, flush the buffer to the associated file
        // descriptor; otherwise do nothing.  Return 0 on success, -1
        // otherwise.

    virtual void imbue(const bsl::locale& locale);
        // Set the locale for this object.  This method has no effect.  The
        // behavior is undefined unless the specified 'locale' is the same as
        // 'bsl::locale()'.

    virtual bsl::streamsize showmanyc();
        // If this object is in putback mode, return the number of characters
        // remaining to be read in the putback buffer, and otherwise the
        // number of characters remaining in the file to be read.  Return a
        // non-negative number of characters on success and a negative value
        // otherwise.  The behavior is undefined unless this object is in input
        // mode and the file descriptor is associated with a regular file.

    virtual bsl::streamsize xsgetn(char *buffer, bsl::streamsize numBytes);
        // Read up to the specified 'numBytes' characters from the file
        // descriptor into the specified 'buffer' and return the number of
        // characters successfully read.  The behavior is undefined unless
        // 'buffer' is at least 'numBytes' bytes long.  Note that on a Windows
        // text file, a '\r\n' in the file will be read as '\n' (counting as a
        // single character).

    virtual bsl::streamsize xsputn(const char      *buffer,
                                   bsl::streamsize  numBytes);
        // Write up to the specified 'numBytes' characters from the specified
        // 'buffer' and return the number of characters successfully written.
        // Note that this method does not necessarily modify the file: this
        // method may simply write the characters to a buffer to be flushed to
        // the file at a later time.  Also note that on a Windows text file, a
        // '\n' will be written to the file as '\r\n' (counted as a single
        // character).

  private:
    // NOT IMPLEMENTED
    FdStreamBuf(           const FdStreamBuf&);
    FdStreamBuf& operator=(const FdStreamBuf&);

  public:
    // CREATORS
    explicit FdStreamBuf(
                   FilesystemUtil::FileDescriptor  fileDescriptor,
                   bool                            writableFlag,
                   bool                            willCloseOnResetFlag = true,
                   bool                            binaryModeFlag = false,
                   bslma::Allocator               *basicAllocator = 0);
        // Create a 'FdStreamBuf' associated with the specified
        // 'fileDescriptor' that refers to an already opened file or device,
        // and specify 'writableFlag' which, if 'true', indicates that
        // 'fileDescriptor' is writable, otherwise it is not.  The optionally
        // specified 'willCloseOnResetFlag', if 'true', indicates that
        // 'fileDescriptor' is to be closed the next time this object is reset,
        // cleared or destroyed, or if 'false' the file descriptor is to be
        // left open.  Optionally specify a 'binaryModeFlag' which is ignored
        // on Unix; if 'false' on Windows, it indicates that '\n's are to be
        // translated to and from '\r\n' sequences on the device.  Optionally
        // specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.  Note that if 'FilesystemUtil::k_INVALID_FD' is passed to
        // 'fileDescriptor', no file descriptor is to be associated with this
        // object.  Also note that the state of the 'fileDescriptor' is
        // unchanged by this call (i.e., there is no implicit seek).

    ~FdStreamBuf();
        // Destroy this object and, if 'willCloseOnReset' is 'true', close the
        // file descriptor associated with this object, if any.

    // MANIPULATORS
    int reset(FilesystemUtil::FileDescriptor fileDescriptor,
              bool                           writableFlag,
              bool                           willCloseOnResetFlag = true,
              bool                           binaryModeFlag = false);
        // Associate this object with the specified 'fileDescriptor', and
        // record the state of the specified 'writableFlag' which, if 'true',
        // indicates that 'fileDescriptor' is writable, otherwise it is not.
        // Before making this association, if, prior to this call,
        // 'willCloseOnReset' is true, close any file descriptor previously
        // associated with this object, otherwise leave it open but
        // disassociate this object from it.  The Optionally specified
        // 'willCloseOnResetFlag' which will set 'willCloseOnReset', which, if
        // 'true', indicates that the specified file descriptor is to be closed
        // when this object is cleared, reset, or destroyed, otherwise no
        // action will be taken on 'fileDescriptor' at that time.  Optionally
        // specify a 'binaryModeFlag', which is ignored on Unix; if 'false' on
        // Windows, it indicates that '\n's internally are to be translated to
        // and from '\r\n' sequences on the device; on Unix or if
        // 'binaryModeFlag' is 'true' no such translation is to occur.  Return
        // 0 on success, and a non-zero value otherwise.  Note that if
        // 'FilesystemUtil::k_INVALID_FD' is passed as 'fileDescriptor', no
        // file descriptor is to be associated with this object.  Also note
        // that the state of the 'fileDescriptor' is unchanged by this call,
        // there is no implicit seek.

    void release();
        // Disassociate this file handler from any file descriptor with which
        // it may be associated without closing that file descriptor.  This
        // method succeeds with no effect is 'isOpened' was false.  Note that
        // 'fileDescriptor' is 'FilesystemUtil::k_INVALID_FD' after this call.

    int clear();
        // Release any file descriptor that may be associated with this file
        // handler.  If 'isOpened' and 'willCloseOnReset' are both 'true', the
        // file descriptor will be closed, otherwise it will not.  Return 0 on
        // success, and a non-zero value if the close fails.  This method
        // succeeds with no effect if 'isOpened' was false.  Note that
        // 'fileDescriptor' is 'FilesystemUtil::k_INVALID_FD' after this call.

    // ACCESSORS
    FilesystemUtil::FileDescriptor fileDescriptor() const;
        // Return the file descriptor associated with this object, or
        // 'FilesystemUtil::k_INVALID_FD' if this object is not currently
        // associated with a file descriptor.

    bool isOpened() const;
        // Return 'true' if this object is currently associated with a file
        // descriptor, and 'false' otherwise.

    bool willCloseOnReset() const;
        // Return 'true' if this object will close the associated file
        // descriptor the next time it is reset, cleared, or destroyed, and
        // 'false' otherwise.
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                       // -----------------------------
                       // class FdStreamBuf_FileHandler
                       // -----------------------------

// CLASS METHODS
inline
size_t FdStreamBuf_FileHandler::pageSize()
{
    return bsls::AtomicOperations::getIntRelaxed(&s_pageSize);
}

// MANIPULATORS
inline
void FdStreamBuf_FileHandler::release()
{
    d_willCloseOnResetFlag = false;
    reset(FilesystemUtil::k_INVALID_FD, false);
}

inline
int FdStreamBuf_FileHandler::clear()
{
    return reset(FilesystemUtil::k_INVALID_FD, false);
}

inline
void FdStreamBuf_FileHandler::setWillCloseOnReset(bool booleanValue)
{
    d_willCloseOnResetFlag = booleanValue;
}

inline
bsl::streamoff
FdStreamBuf_FileHandler::getOffset(char *first, char *last) const
{
    BSLS_ASSERT(first <= last);

    return d_openModeFlags & bsl::ios_base::binary
           ? last - first
           : bsl::count(first, last, '\n') + last - first;
}

inline
bool FdStreamBuf_FileHandler::isInBinaryMode() const
{
#if defined(BSLS_PLATFORM_OS_UNIX)
    return true;
# else
    // Windows

    return (d_openModeFlags & bsl::ios_base::binary) != 0;
# endif
}

inline
bool FdStreamBuf_FileHandler::isOpened() const
{
    return d_openedFlag;
}

inline
bool FdStreamBuf_FileHandler::isRegularFile() const
{
    return d_regularFileFlag;
}

inline
int FdStreamBuf_FileHandler::openMode() const
{
    return (int) d_openModeFlags;
}

inline
bool FdStreamBuf_FileHandler::willCloseOnReset() const
{
    return d_willCloseOnResetFlag;
}

inline
FilesystemUtil::FileDescriptor
FdStreamBuf_FileHandler::fileDescriptor() const
{
    return d_fileId;
}

                             // -----------------
                             // class FdStreamBuf
                             // -----------------

// PRIVATE MANIPULATORS
inline
void FdStreamBuf::exitPutbackMode()
{
    setg(d_savedEback_p, d_savedGptr_p, d_savedEgptr_p);
    d_mode = e_INPUT_MODE;
}

inline
FdStreamBuf::pos_type
FdStreamBuf::seekReturn(pos_type offset)
    // Only called by 'seekoff' and 'seekpos', returns the value about to be
    // returned by the calling routine.
{
    if (e_INPUT_MODE == d_mode || e_INPUT_PUTBACK_MODE == d_mode) {
        if (0 != exitInputMode(false)) {
            // error

            return (pos_type) - 1;                                    // RETURN
        }
    }
    setg(0, 0, 0);
    setp(0, 0);

    d_mode = e_NULL_MODE;

    return offset;
}

// MANIPULATORS
inline
int FdStreamBuf::reset(FilesystemUtil::FileDescriptor fileDescriptor,
                       bool                           writableFlag,
                       bool                           willCloseOnResetFlag,
                       bool                           binaryModeFlag)
{
    bool ok = 0 == flush();

    if (ok || FilesystemUtil::k_INVALID_FD == fileDescriptor) {
        // note we reset() whether flush succeeded or not

        ok &= (0 == d_fileHandler.reset(fileDescriptor,
                                        writableFlag,
                                        willCloseOnResetFlag,
                                        binaryModeFlag));
    }

    return ok ? 0 : -1;
}

inline
void FdStreamBuf::release()
{
    d_fileHandler.setWillCloseOnReset(false);
    reset(FilesystemUtil::k_INVALID_FD, false);
}

inline
int FdStreamBuf::clear()
{
    return reset(FilesystemUtil::k_INVALID_FD, false);
}

// ACCESSORS
inline
FilesystemUtil::FileDescriptor FdStreamBuf::fileDescriptor() const
{
    return d_fileHandler.fileDescriptor();
}

inline
bool FdStreamBuf::isOpened() const
{
    return d_fileHandler.isOpened();
}

inline
bool FdStreamBuf::willCloseOnReset() const
{
    return d_fileHandler.willCloseOnReset();
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------