Component of the Week #35: bdls_fdstreambuf

Summary:
  • Provides a bsl::streambuf that can be initialized with a file descriptor.

  • Supports both text and binary modes, with platform-appropriate line ending handling.

  • Can optionally take ownership of the file descriptor for automatic closing on destruction.

The bdls_fdstreambuf component provides a class, bdls::FdStreamBuf, derived from the C++ standard library’s bsl::streambuf that can be associated with a file descriptor, and then a bsl::stream can be created from the `bsl::streambuf, which allows the client to use C++ stream I/O operations with file descriptors obtained from system calls or from bdls::FilesystemUtil. The file descriptor can represent a file, pipe, or other device.

Key Features

  • File descriptor integration: Works with file descriptors from bdls::FilesystemUtil or system calls

  • Stream interface: Provides familiar C++ stream operations through bsl::streambuf interface

  • Ownership management: Optional ownership of file descriptors for automatic closing on destruction

  • Platform-aware text mode: Handles line ending translation on Windows (\r\n) vs Unix (\n)

  • Binary mode support: Raw byte access without line ending translation

  • Seeking operations: Supports random access within the file

  • Efficient I/O: Direct operations like sputn and sgetn for bulk data transfer

Common Use Cases

bdls::FdStreamBuf is particularly useful for:

  • Allowing a file, socket, or pipe to be dealt with as a C++ stream by creating a bdls::FdStreamBuf out of a file descriptor, then creating an std::stream initialized with the FdStreamBuf.

  • Cross-platform file I/O with appropriate text/binary handling

  • Performance-critical applications requiring direct stream buffer operations

Basic Usage

The most common usage is to initialize a stream with a bdls::FdStreamBuf for either input or output:

#include <bdls_fdstreambuf.h>
#include <bdls_filesystemutil.h>
#include <bsl_iostream.h>
#include <bsl_fstream.h>

using namespace BloombergLP;
using bsl::cout;
using bsl::cerr;
using bsl::endl;

int main() {
    const char* filename = "/tmp/example.txt";

    // Create and open a file descriptor

    typedef bdls::FilesystemUtil::FileDescriptor FdType;
    FdType fd = bdls::FilesystemUtil::open(
        filename,
        bdls::FilesystemUtil::e_CREATE,
        bdls::FilesystemUtil::e_READ_WRITE);

    if (bdls::FilesystemUtil::k_INVALID_FD == fd) {
        cerr << "Failed to open file" << endl;
        return -1;
    }

    // Write to the file using a stream buffer

    {
        bdls::FdStreamBuf streamBuffer(fd,
                                       true,    // writable
                                       false);  // don't close fd on
                                                // destruction

        bsl::ostream os(&streamBuffer);
        os << "Hello, World!" << endl;
        os << "Line 2: " << 42 << endl;
    }

    // Read back from the file

    {
        bdls::FdStreamBuf streamBuffer(fd,
                                       false,   // not writable
                                       true);   // close fd on
                                                // destruction

        streamBuffer.pubseekpos(0);  // seek to beginning

        bsl::istream is(&streamBuffer);
        bsl::string line;
        while (bsl::getline(is, line)) {
            cout << "Read: " << line << endl;
        }
    }

    // fd is automatically closed when the last streamBuffer is destroyed

    bdls::FilesystemUtil::remove(filename);
    return 0;
}

Direct Stream Buffer Operations

You can also use the stream buffer’s public methods directly for more precise control over I/O operations:

#include <bdls_fdstreambuf.h>
#include <bdls_filesystemutil.h>
#include <bsl_iostream.h>
#include <bsl_string_view.h>
#include <assert.h>

using namespace BloombergLP;
using bsl::cout;
using bsl::endl;

int main() {
    const char* filename = "/tmp/direct_io.txt";
    const bsl::string_view line1("First line of text\n");
    const bsl::string_view line2("Second line of text\n");
    const bsl::string_view line3("Third line of text\n");

    // Open file descriptor

    typedef bdls::FilesystemUtil::FileDescriptor FdType;
    FdType fd = bdls::FilesystemUtil::open(
        filename,
        bdls::FilesystemUtil::e_CREATE,
        bdls::FilesystemUtil::e_READ_WRITE);

    bdls::FdStreamBuf streamBuffer(fd, true);  // writable, owns fd

    // Write data using sputn

    streamBuffer.sputn(line1.data(), line1.length());
    streamBuffer.sputn(line2.data(), line2.length());
    streamBuffer.sputn(line3.data(), line3.length());

    // Seek back to the beginning

    bsl::streamoff pos = streamBuffer.pubseekpos(0);
    assert(0 == pos);

    // Read specific amounts of data

    char buffer[100];

    bsl::streamsize bytesRead = streamBuffer.sgetn(buffer,
                                                   line1.length());
    const bsl::string_view read1(buffer, bytesRead);
    assert(read1 == line1);

    cout << "Read: " << read1;

    // Read the rest of the file

    bsl::memset(buffer, 0, sizeof(buffer));
    bytesRead = streamBuffer.sgetn(buffer, sizeof(buffer) - 1);
    const bsl::string_view remaining(buffer, bytesRead);

    cout << "Remaining: " << remaining;

    streamBuffer.clear();  // closes fd and disconnects
    bdls::FilesystemUtil::remove(filename);

    return 0;
}

The above example produces the output:
Read: First line of text
Remaining: Second line of text
Third line of text

Text vs Binary Mode

The stream buffer supports both text and binary modes, which affects how line endings are handled on different platforms:

#include <bdls_fdstreambuf.h>
#include <bdls_filesystemutil.h>

#include <bsl_algorithm.h>
#include <bsl_iostream.h>

#include <assert.h>

using namespace BloombergLP;
using bsl::cout;
using bsl::endl;

int main() {
    const char* filename = "/tmp/mode_test.txt";

    typedef bdls::FilesystemUtil::FileDescriptor FdType;
    FdType fd = bdls::FilesystemUtil::open(
        filename,
        bdls::FilesystemUtil::e_CREATE,
        bdls::FilesystemUtil::e_READ_WRITE);

    // Write in text mode (default on Windows, binary on Unix)

    {
        bdls::FdStreamBuf streamBuffer(fd,
                                       true,    // writable
                                       false,   // don't close fd
                                       false);  // non-binary CRLF mode
                                                // on Windows, this
                                                // argument ignored on
                                                // Unix

        bsl::ostream os(&streamBuffer);
        os << "Line with newline" << endl;
    }

    // Read back in binary mode to see exact bytes written, end of line
    // will be "\r\n" on Windows, just "\n" on Unix.

    {
        bdls::FdStreamBuf streamBuffer(fd,
                                       false,   // not writable
                                       false,   // don't close fd
                                       true);   // binary mode

        streamBuffer.pubseekpos(0);

        char buffer[100];    char *end = buffer + sizeof(buffer);

        bsl::istream is(&streamBuffer);
        char *pc = buffer;
        while (pc < end && is.get(*pc) && *pc != '\n') {
            ++pc;
        }
        assert(pc < end && '\n' == *pc);

        cout << "Binary read: ";
        for (char *p = buffer; p <= pc; ++p) {
            if (*p == '\r') {
                cout << "\\r";
            } else if (*p == '\n') {
                cout << "\\n";
            } else {
                cout << *p;
            }
        }
        cout << endl;
    }

    bdls::FilesystemUtil::close(fd);
    bdls::FilesystemUtil::remove(filename);

    return 0;
}

// Output on Unix:    "Line with newline\n"
// Output on Windows: "Line with newline\r\n"
For more details, see: