/**
 * @file   lltempredirect.h
 * @author Nat Goodspeed
 * @date   2019-10-31
 * @brief  RAII low-level file-descriptor redirection
 *
 * $LicenseInfo:firstyear=2019&license=viewerlgpl$
 * Copyright (c) 2019, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_LLTEMPREDIRECT_H)
#define LL_LLTEMPREDIRECT_H

// Functions in this namespace are intended to insulate the caller from the
// aggravating distinction between ::close() and Microsoft _close().
namespace llfd
{

int close(int fd);
int dup(int target);
int dup2(int target, int reference);
FILE* open(int fd, const char* mode);
int fileno(FILE* stream);

} // namespace llfd

/**
 * LLTempRedirect is an RAII class that performs file redirection on low-level
 * file descriptors, expressed as ints. (Use llfd::fileno() to obtain the file
 * descriptor from a classic-C FILE*. There is no portable way to obtain the
 * file descriptor from a std::fstream.)
 *
 * Instantiate LLTempRedirect with a target file descriptor (e.g. for some
 * open file) and a reference file descriptor (e.g. for stderr). From that
 * point until the LLTempRedirect instance is destroyed, all OS-level writes
 * to the reference file descriptor will be redirected to the target file.
 *
 * Because dup2() is used for redirection, the original passed target file
 * descriptor remains open. If you want LLTempRedirect's destructor to close
 * the target file, close() the target file descriptor after passing it to
 * LLTempRedirect's constructor.
 *
 * LLTempRedirect's constructor saves the original target of the reference
 * file descriptor. Its destructor restores the reference file descriptor to
 * point once again to its original target.
 */
class LLTempRedirect
{
public:
    LLTempRedirect();
    /**
     * For the lifespan of this LLTempRedirect instance, all writes to
     * 'reference' will be redirected to 'target'. When this LLTempRedirect is
     * destroyed, the original target for 'reference' will be restored.
     *
     * Pass 'target' as NULL if you simply want to save and restore
     * 'reference' against possible redirection in the meantime.
     */
    LLTempRedirect(FILE* target, FILE* reference);
    /**
     * For the lifespan of this LLTempRedirect instance, all writes to
     * 'reference' will be redirected to 'target'. When this LLTempRedirect is
     * destroyed, the original target for 'reference' will be restored.
     *
     * Pass 'target' as -1 if you simply want to save and restore
     * 'reference' against possible redirection in the meantime.
     */
    LLTempRedirect(int target,   int reference);
    LLTempRedirect(const LLTempRedirect&) = delete;
    LLTempRedirect(LLTempRedirect&& other);

    ~LLTempRedirect();

    LLTempRedirect& operator=(const LLTempRedirect&) = delete;
    LLTempRedirect& operator=(LLTempRedirect&& other);

    /// returns (duplicate file descriptor for) the original target of the
    /// 'reference' file descriptor passed to our constructor
    int getOriginalTarget() const { return mOrigTarget; }
    /// returns the original 'reference' file descriptor passed to our
    /// constructor
    int getReference()      const { return mReference; }

private:
    void reset();

    int mOrigTarget, mReference;
};

#endif /* ! defined(LL_LLTEMPREDIRECT_H) */