| //===-- 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 |
| |
| |