FDInterposing.cpp   [plain text]


//===-- FDInterposing.cpp ---------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file helps with catching double close calls on unix integer file 
// descriptors by interposing functions for all file descriptor create and 
// close operations. A stack backtrace for every create and close function is
// maintained, and every create and close operation is logged. When a double
// file descriptor close is encountered, it will be logged.
//
// To enable the interposing in a darwin program, set the DYLD_INSERT_LIBRARIES
// environment variable as follows:
// For sh:
//  DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib /path/to/executable
// For tcsh:
//  (setenv DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib ; /path/to/executable)
//
// Other environment variables that can alter the default actions of this
// interposing shared library include:
//
// "FileDescriptorStackLoggingNoCompact"
//
//      With this environment variable set, all file descriptor create and 
//      delete operations will be permanantly maintained in the event map. 
//      The default action is to compact the create/delete events by removing
//      any previous file descriptor create events that are matched with a 
//      corresponding file descriptor delete event when the next valid file
//      descriptor create event is detected.
// 
// "FileDescriptorMinimalLogging"
//
//      By default every file descriptor create and delete operation is logged
//      (to STDOUT by default, see the "FileDescriptorLogFile"). This can be
//      suppressed to only show errors and warnings by setting this environment
//      variable (the value in not important).
//
// "FileDescriptorLogFile=<path>"
//
//      By default logging goes to STDOUT_FILENO, but this can be changed by
//      setting FileDescriptorLogFile. The value is a path to a file that
//      will be opened and used for logging.
//===----------------------------------------------------------------------===//

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <execinfo.h>
#include <libgen.h>
#include <mach-o/dyld.h>
#include <mach-o/dyld-interposing.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/event.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <tr1/memory> // for std::tr1::shared_ptr
#include <unistd.h>
#include <string>
#include <vector>
#include <map>

//----------------------------------------------------------------------
/// @def DISALLOW_COPY_AND_ASSIGN(TypeName)
///     Macro definition for easily disallowing copy constructor and
///     assignment operators in C++ classes.
//----------------------------------------------------------------------
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
const TypeName& operator=(const TypeName&)

extern "C" {
    int accept$NOCANCEL (int, struct sockaddr * __restrict, socklen_t * __restrict);
    int close$NOCANCEL(int);
    int open$NOCANCEL(const char *, int, ...);
    int __open_extended(const char *, int, uid_t, gid_t, int, struct kauth_filesec *);
}

namespace fd_interposing {

//----------------------------------------------------------------------
// String class so we can get formatted strings without having to worry
// about the memory storage since it will allocate the memory it needs.
//----------------------------------------------------------------------
class String
{
public:
    String () : 
        m_str (NULL) 
    {}

    String (const char *format, ...) : 
        m_str (NULL)
    {
        va_list args;
        va_start (args, format);
        vprintf (format, args);
        va_end (args);
    }
    
    ~String()
    {
        reset();
    }
    
    void
    reset (char *s = NULL)
    {
        if (m_str)
            ::free (m_str);
        m_str = s;
    }
    
    const char *
    c_str () const
    {
        return m_str;
    }

    void
    printf (const char *format, ...)
    {
        va_list args;
        va_start (args, format);
        vprintf (format, args);
        va_end (args);        
    }
    void
    vprintf (const char *format, va_list args)
    {
        reset();
        ::vasprintf (&m_str, format, args);
    }

    void
    log (int log_fd)
    {
        if (m_str && log_fd >= 0)
        {
            const int len = strlen(m_str);
            if (len > 0)
            {
                write (log_fd, m_str, len);
                const char last_char = m_str[len-1];
                if (!(last_char == '\n' || last_char == '\r'))
                    write (log_fd, "\n", 1);
            }
        }
    }
protected:
    char *m_str;
    
private:
    DISALLOW_COPY_AND_ASSIGN (String);
};

//----------------------------------------------------------------------
// Type definitions
//----------------------------------------------------------------------
typedef std::vector<void *> Frames;
class FDEvent;
typedef std::vector<void *> Frames;
typedef std::tr1::shared_ptr<FDEvent> FDEventSP;
typedef std::tr1::shared_ptr<String> StringSP;


//----------------------------------------------------------------------
// FDEvent
//
// A class that describes a file desciptor event.
//
// File descriptor events fall into one of two categories: create events
// and delete events.
//----------------------------------------------------------------------
class FDEvent
{
public:
    FDEvent (int fd, int err, const StringSP &string_sp, bool is_create, const Frames& frames) :
        m_string_sp (string_sp),
        m_frames (frames.begin(), frames.end()),
        m_fd (fd),
        m_err (err),
        m_is_create (is_create)
    {}
    
    ~FDEvent () {}
    
    bool
    IsCreateEvent() const
    {
        return m_is_create;
    }

    bool
    IsDeleteEvent() const
    {
        return !m_is_create;
    }

    Frames &
    GetFrames ()
    {
        return m_frames;
    }

    const Frames &
    GetFrames () const
    {
        return m_frames;
    }
    
    int
    GetFD () const
    {
        return m_fd;
    }

    int
    GetError () const
    {
        return m_err;
    }
    
    void
    Dump (int log_fd) const;

    void
    SetCreateEvent (FDEventSP &create_event_sp)
    {
        m_create_event_sp = create_event_sp;
    }

private:
    // A shared pointer to a String that describes this event in 
    // detail (all args and return and error values)
    StringSP m_string_sp;
    // The frames for the stack backtrace for this event
    Frames m_frames;
    // If this is a file descriptor delete event, this might contain 
    // the correspoding file descriptor create event
    FDEventSP m_create_event_sp;    
    // The file descriptor for this event
    int m_fd;
    // The error code (if any) for this event
    int m_err;
    // True if this event is a file descriptor create event, false 
    // if it is a file descriptor delete event
    bool m_is_create;
};

//----------------------------------------------------------------------
// Templatized class that will save errno only if the "value" it is 
// constructed with is equal to INVALID. When the class goes out of 
// scope, it will restore errno if it was saved.
//----------------------------------------------------------------------
template <int INVALID>
class Errno
{
public:
    // Save errno only if we are supposed to
    Errno (int value) :
        m_saved_errno ((value == INVALID) ? errno : 0),
        m_restore (value == INVALID)
    {
    }
    
    // Restore errno only if we are supposed to
    ~Errno()
    {
        if (m_restore)
            errno = m_saved_errno;
    }

    // Accessor for the saved value of errno
    int
    get_errno() const
    {
        return m_saved_errno;
    }

protected:
    const int m_saved_errno;
    const bool m_restore;
};

typedef Errno<-1> InvalidFDErrno;
typedef Errno<-1> NegativeErrorErrno;
typedef std::vector<FDEventSP> FDEventArray;
typedef std::map<int, FDEventArray> FDEventMap;

//----------------------------------------------------------------------
// Globals
//----------------------------------------------------------------------
// Global event map that contains all file descriptor events. As file
// descriptor create and close events come in, they will get filled
// into this map (protected by g_mutex). When a file descriptor close
// event is detected, the open event will be removed and placed into
// the close event so if something tries to double close a file 
// descriptor we can show the previous close event and the file 
// desctiptor event that created it. When a new file descriptor create
// event comes in, we will remove the previous one for that file 
// desctiptor unless the environment variable "FileDescriptorStackLoggingNoCompact"
// is set. The file desctiptor history can be accessed using the 
// get_fd_history() function.
static FDEventMap g_fd_event_map;
// A mutex to protect access to our data structures in g_fd_event_map
// and also our logging messages
static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
// Log all file descriptor create and close events by default. Only log
// warnings and erros if the "FileDescriptorMinimalLogging" environment
// variable is set.
static int g_log_all_calls = 1;
// We compact the file descriptor events by default. Set the environment
// varible "FileDescriptorStackLoggingNoCompact" to keep a full history.
static int g_compact = 1;
// The current process ID
static int g_pid = -1;
static bool g_enabled = true;
//----------------------------------------------------------------------
// Mutex class that will lock a mutex when it is constructed, and unlock
// it when is goes out of scope
//----------------------------------------------------------------------
class Locker
{
public:
    Locker (pthread_mutex_t *mutex_ptr) :
        m_mutex_ptr(mutex_ptr)
    {
        ::pthread_mutex_lock (m_mutex_ptr);
    }

    // This allows clients to test try and acquire the mutex...
    Locker (pthread_mutex_t *mutex_ptr, bool &lock_acquired) :
        m_mutex_ptr(NULL)
    {
        lock_acquired = ::pthread_mutex_trylock(mutex_ptr) == 0;
        if (lock_acquired)
            m_mutex_ptr = mutex_ptr;
    }

    ~Locker ()
    {
        if (m_mutex_ptr)
            ::pthread_mutex_unlock (m_mutex_ptr);
    }
protected:
    pthread_mutex_t *m_mutex_ptr;
};

static void
log (const char *format, ...) __attribute__ ((format (printf, 1, 2)));

static void
log (int log_fd, const FDEvent *event, const char *format, ...) __attribute__ ((format (printf, 3, 4)));

static void
backtrace_log (const char *format, ...) __attribute__ ((format (printf, 1, 2)));

static void
backtrace_error (const char *format, ...) __attribute__ ((format (printf, 1, 2)));

static void
log_to_fd (int log_fd, const char *format, ...) __attribute__ ((format (printf, 2, 3)));

static inline size_t
get_backtrace (Frames &frame_buffer, size_t frames_to_remove)
{
    void *frames[2048];
    int count = ::backtrace (&frames[0], sizeof(frames)/sizeof(void*));
    if (count > frames_to_remove)
        frame_buffer.assign (&frames[frames_to_remove], &frames[count]);
    else
        frame_buffer.assign (&frames[0], &frames[count]);
    while (frame_buffer.back() < (void *)1024)
        frame_buffer.pop_back();
    return frame_buffer.size();
}

static int g_log_fd = STDOUT_FILENO;
static int g_initialized = 0;

const char *
get_process_fullpath (bool force = false)
{
    static char g_process_fullpath[PATH_MAX] = {0};
    if (force || g_process_fullpath[0] == '\0')
    {
        // If DST is NULL, then return the number of bytes needed.
        uint32_t len = sizeof(g_process_fullpath);
        if (_NSGetExecutablePath (g_process_fullpath, &len) != 0)
            strncpy (g_process_fullpath, "<error>", sizeof(g_process_fullpath));
    }
    return g_process_fullpath;
}

// Returns the current process ID, or -1 if inserposing not enabled for
// this process
static int
get_interposed_pid()
{
    if (!g_enabled)
        return -1;

    const pid_t pid = getpid();
    if (g_pid != pid)
    {
        if (g_pid == -1)
        {
            g_pid = pid;
            log ("Interposing file descriptor create and delete functions for %s (pid=%i)\n", get_process_fullpath (true), pid);
        }
        else
        {
            log ("pid=%i: disabling interposing file descriptor create and delete functions for child process %s (pid=%i)\n", g_pid, get_process_fullpath (true), pid);
            g_enabled = false;
            return -1;
        }
        // Log when our process changes
    }
    return g_pid;
}

static int
get_logging_fd ()
{
    if (!g_enabled)
        return -1;
    
    if (!g_initialized) 
    {
        g_initialized = 1;

        const pid_t pid = get_interposed_pid();

        if (g_enabled)
        {
            // Keep all stack info around for all fd create and delete calls.
            // Otherwise we will remove the fd create call when a corresponding
            // fd delete call is received
            if (getenv("FileDescriptorStackLoggingNoCompact"))
                g_compact = 0;

            if (getenv("FileDescriptorMinimalLogging"))
                g_log_all_calls = 0;

            const char *log_path = getenv ("FileDescriptorLogFile");
            if (log_path)
                g_log_fd = ::creat (log_path, 0660);
            else
                g_log_fd = STDOUT_FILENO;

            // Only let this interposing happen on the first time this matches
            // and stop this from happening so any child processes don't also
            // log their file descriptors
            ::unsetenv ("DYLD_INSERT_LIBRARIES");
        }
        else
        {
            log ("pid=%i: logging disabled\n", getpid());
        }
    }
    return g_log_fd;
}

void
log_to_fd (int log_fd, const char *format, va_list args)
{
    if (format && format[0] && log_fd >= 0)
    {
        char buffer[PATH_MAX];
        const int count = ::vsnprintf (buffer, sizeof(buffer), format, args);
        if (count > 0)
            write (log_fd, buffer, count);
    }
}

void
log_to_fd (int log_fd, const char *format, ...)
{
    if (format && format[0])
    {
        va_list args;
        va_start (args, format);
        log_to_fd (log_fd, format, args);
        va_end (args);
    }
}

void
log (const char *format, va_list args)
{
    log_to_fd (get_logging_fd (), format, args);
}

void
log (const char *format, ...)
{
    if (format && format[0])
    {
        va_list args;
        va_start (args, format);
        log (format, args);
        va_end (args);
    }
}

void
log (int log_fd, const FDEvent *event, const char *format, ...)
{
    if (format && format[0])
    {
        va_list args;
        va_start (args, format);
        log_to_fd (log_fd, format, args);
        va_end (args);
    }
    if (event)
        event->Dump(log_fd);
}

void
FDEvent::Dump (int log_fd) const
{
    if (log_fd >= 0)
    {
        log_to_fd (log_fd, "%s\n", m_string_sp->c_str());
        if (!m_frames.empty())
            ::backtrace_symbols_fd (m_frames.data(), m_frames.size(), log_fd);
        
        if (m_create_event_sp)
        {
            log_to_fd (log_fd, "\nfd=%i was created with this event:\n", m_fd);
            m_create_event_sp->Dump (log_fd);
            log_to_fd (log_fd, "\n");
        }
    }
}


void
backtrace_log (const char *format, ...)
{
    const int log_fd = get_logging_fd ();
    if (log_fd >= 0)
    {
        if (format && format[0])
        {
            va_list args;
            va_start (args, format);
            log (format, args);
            va_end (args);
        }
        
        Frames frames;
        if (get_backtrace(frames, 2))
            ::backtrace_symbols_fd (frames.data(), frames.size(), log_fd);
    }

}

void
backtrace_error (const char *format, ...)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        const int log_fd = get_logging_fd ();
        if (log_fd >= 0)
        {
            log ("\nerror: %s (pid=%i): ", get_process_fullpath (), pid);

            if (format && format[0])
            {
                va_list args;
                va_start (args, format);
                log (format, args);
                va_end (args);
            }
        
            Frames frames;
            if (get_backtrace(frames, 2))
                ::backtrace_symbols_fd (frames.data(), frames.size(), log_fd);
        }
    }
}

void
save_backtrace (int fd, int err, const StringSP &string_sp, bool is_create)
{
    Frames frames;
    get_backtrace(frames, 2);

    FDEventSP fd_event_sp (new FDEvent (fd, err, string_sp, is_create, frames));

    FDEventMap::iterator pos = g_fd_event_map.find (fd);
    
    if (pos != g_fd_event_map.end())
    {
        // We have history for this fd...
        
        FDEventArray &event_array = g_fd_event_map[fd];
        if (fd_event_sp->IsCreateEvent())
        {
            // The current fd event is a function that creates 
            // a descriptor, check in case last event was
            // a create event.
            if (event_array.back()->IsCreateEvent())
            {
                const int log_fd = get_logging_fd();
                // Two fd create functions in a row, we missed
                // a function that closes a fd...
                log (log_fd, fd_event_sp.get(), "\nwarning: unmatched file descriptor create event fd=%i (we missed a file descriptor close event):\n", fd);
            }
            else if (g_compact)
            {
                // We are compacting so we remove previous create event
                // when we get the correspinding delete event
                event_array.pop_back();
            }
        }
        else
        {
            // The current fd event is a function that deletes 
            // a descriptor, check in case last event for this
            // fd was a delete event (double close!)
            if (event_array.back()->IsDeleteEvent())
            {
                const int log_fd = get_logging_fd();
                // Two fd delete functions in a row, we must 
                // have missed some function that opened a descriptor
                log (log_fd, fd_event_sp.get(), "\nwarning: unmatched file descriptor close event for fd=%d (we missed the file descriptor create event):\n", fd);
            }
            else if (g_compact)
            {
                // Since this is a close event, we want to remember the open event 
                // that this close if for...
                fd_event_sp->SetCreateEvent(event_array.back());
                // We are compacting so we remove previous create event
                // when we get the correspinding delete event
                event_array.pop_back();
            }
        }
        
        event_array.push_back(fd_event_sp);
    }
    else
    {
        g_fd_event_map[fd].push_back(fd_event_sp);
    }
}

//----------------------------------------------------------------------
// socket() interpose function
//----------------------------------------------------------------------
extern "C" int
socket$__interposed__ (int domain, int type, int protocol)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::socket (domain, type, protocol);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String);
        if (fd == -1)
            description_sp->printf("pid=%i: socket (domain = %i, type = %i, protocol = %i) => fd=%i  errno = %i", pid, domain, type, protocol, fd, fd_errno.get_errno());
        else
            description_sp->printf("pid=%i: socket (domain = %i, type = %i, protocol = %i) => fd=%i", pid, domain, type, protocol, fd);
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::socket (domain, type, protocol);
    }
}

//----------------------------------------------------------------------
// socketpair() interpose function
//----------------------------------------------------------------------
extern "C" int
socketpair$__interposed__ (int domain, int type, int protocol, int fds[2])
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        fds[0] = -1;
        fds[1] = -1;
        const int err = socketpair (domain, type, protocol, fds);
        NegativeErrorErrno err_errno(err);
        StringSP description_sp(new String ("pid=%i: socketpair (domain=%i, type=%i, protocol=%i, {fd=%i, fd=%i}) -> err=%i", pid, domain, type, protocol, fds[0], fds[1], err));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fds[0] >= 0)
            save_backtrace (fds[0], err_errno.get_errno(), description_sp, true);
        if (fds[1] >= 0)
            save_backtrace (fds[1], err_errno.get_errno(), description_sp, true);
        return err;
    }
    else
    {
        return socketpair (domain, type, protocol, fds);
    }
}

//----------------------------------------------------------------------
// open() interpose function
//----------------------------------------------------------------------
extern "C" int	
open$__interposed__ (const char *path, int oflag, int mode)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        int fd = -2;
        StringSP description_sp(new String);
        if (oflag & O_CREAT)
        {
            fd = ::open (path, oflag, mode);
            description_sp->printf("pid=%i: open (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid, path, oflag, mode, fd);
        }
        else
        {
            fd = ::open (path, oflag);
            description_sp->printf("pid=%i: open (path = '%s', oflag = %i) -> fd=%i", pid, path, oflag, fd);
        }
    
        InvalidFDErrno fd_errno(fd);
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::open (path, oflag, mode);
    }
}

//----------------------------------------------------------------------
// open$NOCANCEL() interpose function
//----------------------------------------------------------------------
extern "C" int	
open$NOCANCEL$__interposed__ (const char *path, int oflag, int mode)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::open$NOCANCEL (path, oflag, mode);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: open$NOCANCEL (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid, path, oflag, mode, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;        
    }
    else
    {
        return ::open$NOCANCEL (path, oflag, mode);
    }
}


//----------------------------------------------------------------------
// __open_extended() interpose function
//----------------------------------------------------------------------
extern "C" int 
__open_extended$__interposed__ (const char *path, int oflag, uid_t uid, gid_t gid, int mode, struct kauth_filesec *fsacl)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::__open_extended (path, oflag, uid, gid, mode, fsacl);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: __open_extended (path='%s', oflag=%i, uid=%i, gid=%i, mode=%i, fsacl=%p) -> fd=%i", pid, path, oflag, uid, gid, mode, fsacl, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::__open_extended (path, oflag, uid, gid, mode, fsacl);
    }
}

//----------------------------------------------------------------------
// kqueue() interpose function
//----------------------------------------------------------------------
extern "C" int
kqueue$__interposed__ (void)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::kqueue ();
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: kqueue () -> fd=%i", pid, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::kqueue ();
    }
}

//----------------------------------------------------------------------
// shm_open() interpose function
//----------------------------------------------------------------------
extern "C" int	
shm_open$__interposed__ (const char *path, int oflag, int mode)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::shm_open (path, oflag, mode);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: shm_open (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid, path, oflag, mode, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::shm_open (path, oflag, mode);
    }
}

//----------------------------------------------------------------------
// accept() interpose function
//----------------------------------------------------------------------
extern "C" int
accept$__interposed__ (int socket, struct sockaddr *address, socklen_t *address_len)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::accept (socket, address, address_len);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: accept (socket=%i, ...) -> fd=%i", pid, socket, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::accept (socket, address, address_len);
    }
}


//----------------------------------------------------------------------
// accept$NOCANCEL() interpose function
//----------------------------------------------------------------------
extern "C" int
accept$NOCANCEL$__interposed__ (int socket, struct sockaddr *address, socklen_t *address_len)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::accept$NOCANCEL (socket, address, address_len);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: accept$NOCANCEL (socket=%i, ...) -> fd=%i", pid, socket, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::accept$NOCANCEL (socket, address, address_len);
    }
}

//----------------------------------------------------------------------
// dup() interpose function
//----------------------------------------------------------------------
extern "C" int	
dup$__interposed__ (int fd2)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int fd = ::dup (fd2);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: dup (fd2=%i) -> fd=%i", pid, fd2, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::dup (fd2);
    }
}

//----------------------------------------------------------------------
// dup2() interpose function
//----------------------------------------------------------------------
extern "C" int	
dup2$__interposed__ (int fd1, int fd2)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        // If "fd2" is already opened, it will be closed during the
        // dup2 call below, so we need to see if we have fd2 in our
        // open map and treat it as a close(fd2)
        FDEventMap::iterator pos = g_fd_event_map.find (fd2);
        StringSP dup2_close_description_sp(new String ("pid=%i: dup2 (fd1=%i, fd2=%i) -> will close (fd=%i)", pid, fd1, fd2, fd2));
        if (pos != g_fd_event_map.end() && pos->second.back()->IsCreateEvent())
            save_backtrace (fd2, 0, dup2_close_description_sp, false);

        const int fd = ::dup2(fd1, fd2);
        InvalidFDErrno fd_errno(fd);
        StringSP description_sp(new String ("pid=%i: dup2 (fd1=%i, fd2=%i) -> fd=%i", pid, fd1, fd2, fd));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());

        if (fd >= 0)
            save_backtrace (fd, fd_errno.get_errno(), description_sp, true);
        return fd;
    }
    else
    {
        return ::dup2(fd1, fd2);
    }
}

//----------------------------------------------------------------------
// close() interpose function
//----------------------------------------------------------------------
extern "C" int 
close$__interposed__ (int fd) 
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int err = close(fd);
        NegativeErrorErrno err_errno(err);
        StringSP description_sp (new String);
        if (err == -1)
            description_sp->printf("pid=%i: close (fd=%i) => %i errno = %i (%s))", pid, fd, err, err_errno.get_errno(), strerror(err_errno.get_errno()));
        else
            description_sp->printf("pid=%i: close (fd=%i) => %i", pid, fd, err);
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());

        if (err == 0)
        {
            if (fd >= 0)
                save_backtrace (fd, err, description_sp, false);
        }
        else if (err == -1)
        {
            if (err_errno.get_errno() == EBADF && fd != -1) 
            {
                backtrace_error ("close (fd=%d) resulted in EBADF:\n", fd);

                FDEventMap::iterator pos = g_fd_event_map.find (fd);
                if (pos != g_fd_event_map.end())
                {
                    log (get_logging_fd(), pos->second.back().get(), "\nfd=%d was previously %s with this event:\n", fd, pos->second.back()->IsCreateEvent() ? "opened" : "closed");
                }
            }
        }
        return err;
    }
    else
    {
        return close (fd);        
    }
}

//----------------------------------------------------------------------
// close$NOCANCEL() interpose function
//----------------------------------------------------------------------
extern "C" int
close$NOCANCEL$__interposed__ (int fd)
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        const int err = close$NOCANCEL(fd);
        NegativeErrorErrno err_errno(err);
        StringSP description_sp (new String);
        if (err == -1)
            description_sp->printf("pid=%i: close$NOCANCEL (fd=%i) => %i errno = %i (%s))", pid, fd, err, err_errno.get_errno(), strerror(err_errno.get_errno()));
        else
            description_sp->printf("pid=%i: close$NOCANCEL (fd=%i) => %i", pid, fd, err);
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
    
        if (err == 0)
        {
            if (fd >= 0)
                save_backtrace (fd, err, description_sp, false);
        }
        else if (err == -1)
        {
            if (err_errno.get_errno() == EBADF && fd != -1) 
            {
                backtrace_error ("close$NOCANCEL (fd=%d) resulted in EBADF\n:", fd);
            
                FDEventMap::iterator pos = g_fd_event_map.find (fd);
                if (pos != g_fd_event_map.end())
                {
                    log (get_logging_fd(), pos->second.back().get(), "\nfd=%d was previously %s with this event:\n", fd, pos->second.back()->IsCreateEvent() ? "opened" : "closed");
                }
            }
        }
        return err;
    }
    else
    {
        return close$NOCANCEL(fd);
    }    
}

//----------------------------------------------------------------------
// pipe() interpose function
//----------------------------------------------------------------------
extern "C" int
pipe$__interposed__ (int fds[2])
{
    const int pid = get_interposed_pid();
    if (pid >= 0)
    {
        Locker locker (&g_mutex);
        fds[0] = -1;
        fds[1] = -1;
        const int err = pipe (fds);
        const int saved_errno = errno;
        StringSP description_sp(new String ("pid=%i: pipe ({fd=%i, fd=%i}) -> err=%i", pid, fds[0], fds[1], err));
        if (g_log_all_calls)
            description_sp->log (get_logging_fd());
        if (fds[0] >= 0)
            save_backtrace (fds[0], saved_errno, description_sp, true);
        if (fds[1] >= 0)
            save_backtrace (fds[1], saved_errno, description_sp, true);
        errno = saved_errno;
        return err;
    }
    else
    {
        return pipe (fds);
    }
}

//----------------------------------------------------------------------
// get_fd_history()
//
// This function allows runtime access to the file descriptor history.
//
// @param[in] log_fd
//      The file descriptor to log to
//
// @param[in] fd
//      The file descriptor whose history should be dumped
//----------------------------------------------------------------------
extern "C" void
get_fd_history (int log_fd, int fd)
{
    // "create" below needs to be outside of the mutex locker scope
    if (log_fd >= 0)
    {
        bool got_lock = false;
        Locker locker (&g_mutex, got_lock);
        if (got_lock)
        {
            FDEventMap::iterator pos = g_fd_event_map.find (fd);
            log_to_fd (log_fd, "Dumping file descriptor history for fd=%i:\n", fd);
            if (pos != g_fd_event_map.end())
            {
                FDEventArray &event_array = g_fd_event_map[fd];
                const size_t num_events = event_array.size();
                for (size_t i=0; i<num_events; ++i)
                    event_array[i]->Dump (log_fd);
            }
            else
            {
                log_to_fd (log_fd, "error: no file descriptor events found for fd=%i\n", fd);
            }
        }
        else
        {
            log_to_fd (log_fd, "error: fd event mutex is locked...\n");
        }
    }
}

//----------------------------------------------------------------------
// Interposing
//----------------------------------------------------------------------
// FD creation routines
DYLD_INTERPOSE(accept$__interposed__, accept);
DYLD_INTERPOSE(accept$NOCANCEL$__interposed__, accept$NOCANCEL);
DYLD_INTERPOSE(dup$__interposed__, dup);
DYLD_INTERPOSE(dup2$__interposed__, dup2);
DYLD_INTERPOSE(kqueue$__interposed__, kqueue);
DYLD_INTERPOSE(open$__interposed__, open);
DYLD_INTERPOSE(open$NOCANCEL$__interposed__, open$NOCANCEL);
DYLD_INTERPOSE(__open_extended$__interposed__, __open_extended);
DYLD_INTERPOSE(pipe$__interposed__, pipe);
DYLD_INTERPOSE(shm_open$__interposed__, shm_open);
DYLD_INTERPOSE(socket$__interposed__, socket);
DYLD_INTERPOSE(socketpair$__interposed__, socketpair);

// FD deleting routines
DYLD_INTERPOSE(close$__interposed__, close);
DYLD_INTERPOSE(close$NOCANCEL$__interposed__, close$NOCANCEL);

} // namespace fd_interposing