933 lines · cpp
1//===-- FDInterposing.cpp ---------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file helps with catching double close calls on unix integer file10// descriptors by interposing functions for all file descriptor create and11// close operations. A stack backtrace for every create and close function is12// maintained, and every create and close operation is logged. When a double13// file descriptor close is encountered, it will be logged.14//15// To enable the interposing in a darwin program, set the DYLD_INSERT_LIBRARIES16// environment variable as follows:17// For sh:18// DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib /path/to/executable19// For tcsh:20// (setenv DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib ;21// /path/to/executable)22//23// Other environment variables that can alter the default actions of this24// interposing shared library include:25//26// "FileDescriptorStackLoggingNoCompact"27//28// With this environment variable set, all file descriptor create and29// delete operations will be permanantly maintained in the event map.30// The default action is to compact the create/delete events by removing31// any previous file descriptor create events that are matched with a32// corresponding file descriptor delete event when the next valid file33// descriptor create event is detected.34//35// "FileDescriptorMinimalLogging"36//37// By default every file descriptor create and delete operation is logged38// (to STDOUT by default, see the "FileDescriptorLogFile"). This can be39// suppressed to only show errors and warnings by setting this environment40// variable (the value in not important).41//42// "FileDescriptorLogFile=<path>"43//44// By default logging goes to STDOUT_FILENO, but this can be changed by45// setting FileDescriptorLogFile. The value is a path to a file that46// will be opened and used for logging.47//===----------------------------------------------------------------------===//48 49#include <assert.h>50#include <dirent.h>51#include <errno.h>52#include <execinfo.h>53#include <fcntl.h>54#include <libgen.h>55#include <mach-o/dyld-interposing.h>56#include <mach-o/dyld.h>57#include <map>58#include <stdio.h>59#include <stdlib.h>60#include <string.h>61#include <string>62#include <sys/event.h>63#include <sys/mman.h>64#include <sys/socket.h>65#include <sys/time.h>66#include <sys/types.h>67#include <tr1/memory>68#include <unistd.h>69#include <vector>70 71extern "C" {72int accept$NOCANCEL(int, struct sockaddr *__restrict, socklen_t *__restrict);73int close$NOCANCEL(int);74int open$NOCANCEL(const char *, int, ...);75int __open_extended(const char *, int, uid_t, gid_t, int,76 struct kauth_filesec *);77}78 79namespace fd_interposing {80 81// String class so we can get formatted strings without having to worry82// about the memory storage since it will allocate the memory it needs.83class String {84public:85 String() : m_str(NULL) {}86 87 String(const char *format, ...) : m_str(NULL) {88 va_list args;89 va_start(args, format);90 vprintf(format, args);91 va_end(args);92 }93 94 ~String() { reset(); }95 96 void reset(char *s = NULL) {97 if (m_str)98 ::free(m_str);99 m_str = s;100 }101 102 const char *c_str() const { return m_str; }103 104 void printf(const char *format, ...) {105 va_list args;106 va_start(args, format);107 vprintf(format, args);108 va_end(args);109 }110 void vprintf(const char *format, va_list args) {111 reset();112 ::vasprintf(&m_str, format, args);113 }114 115 void log(int log_fd) {116 if (m_str && log_fd >= 0) {117 const int len = strlen(m_str);118 if (len > 0) {119 write(log_fd, m_str, len);120 const char last_char = m_str[len - 1];121 if (!(last_char == '\n' || last_char == '\r'))122 write(log_fd, "\n", 1);123 }124 }125 }126 127protected:128 char *m_str;129 130private:131 String(const String &) = delete;132 const String &operator=(const String &) = delete;133};134 135// Type definitions136typedef std::vector<void *> Frames;137class FDEvent;138typedef std::vector<void *> Frames;139typedef std::tr1::shared_ptr<FDEvent> FDEventSP;140typedef std::tr1::shared_ptr<String> StringSP;141 142// FDEvent143//144// A class that describes a file descriptor event.145//146// File descriptor events fall into one of two categories: create events147// and delete events.148class FDEvent {149public:150 FDEvent(int fd, int err, const StringSP &string_sp, bool is_create,151 const Frames &frames)152 : m_string_sp(string_sp), m_frames(frames.begin(), frames.end()),153 m_fd(fd), m_err(err), m_is_create(is_create) {}154 155 ~FDEvent() {}156 157 bool IsCreateEvent() const { return m_is_create; }158 159 bool IsDeleteEvent() const { return !m_is_create; }160 161 Frames &GetFrames() { return m_frames; }162 163 const Frames &GetFrames() const { return m_frames; }164 165 int GetFD() const { return m_fd; }166 167 int GetError() const { return m_err; }168 169 void Dump(int log_fd) const;170 171 void SetCreateEvent(FDEventSP &create_event_sp) {172 m_create_event_sp = create_event_sp;173 }174 175private:176 // A shared pointer to a String that describes this event in177 // detail (all args and return and error values)178 StringSP m_string_sp;179 // The frames for the stack backtrace for this event180 Frames m_frames;181 // If this is a file descriptor delete event, this might contain182 // the corresponding file descriptor create event183 FDEventSP m_create_event_sp;184 // The file descriptor for this event185 int m_fd;186 // The error code (if any) for this event187 int m_err;188 // True if this event is a file descriptor create event, false189 // if it is a file descriptor delete event190 bool m_is_create;191};192 193// Templatized class that will save errno only if the "value" it is194// constructed with is equal to INVALID. When the class goes out of195// scope, it will restore errno if it was saved.196template <int INVALID> class Errno {197public:198 // Save errno only if we are supposed to199 Errno(int value)200 : m_saved_errno((value == INVALID) ? errno : 0),201 m_restore(value == INVALID) {}202 203 // Restore errno only if we are supposed to204 ~Errno() {205 if (m_restore)206 errno = m_saved_errno;207 }208 209 // Accessor for the saved value of errno210 int get_errno() const { return m_saved_errno; }211 212protected:213 const int m_saved_errno;214 const bool m_restore;215};216 217typedef Errno<-1> InvalidFDErrno;218typedef Errno<-1> NegativeErrorErrno;219typedef std::vector<FDEventSP> FDEventArray;220typedef std::map<int, FDEventArray> FDEventMap;221 222// Globals223// Global event map that contains all file descriptor events. As file224// descriptor create and close events come in, they will get filled225// into this map (protected by g_mutex). When a file descriptor close226// event is detected, the open event will be removed and placed into227// the close event so if something tries to double close a file228// descriptor we can show the previous close event and the file229// descriptor event that created it. When a new file descriptor create230// event comes in, we will remove the previous one for that file231// descriptor unless the environment variable232// "FileDescriptorStackLoggingNoCompact"233// is set. The file descriptor history can be accessed using the234// get_fd_history() function.235static FDEventMap g_fd_event_map;236// A mutex to protect access to our data structures in g_fd_event_map237// and also our logging messages238static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;239// Log all file descriptor create and close events by default. Only log240// warnings and errors if the "FileDescriptorMinimalLogging" environment241// variable is set.242static int g_log_all_calls = 1;243// We compact the file descriptor events by default. Set the environment244// varible "FileDescriptorStackLoggingNoCompact" to keep a full history.245static int g_compact = 1;246// The current process ID247static int g_pid = -1;248static bool g_enabled = true;249// Mutex class that will lock a mutex when it is constructed, and unlock250// it when is goes out of scope251class Locker {252public:253 Locker(pthread_mutex_t *mutex_ptr) : m_mutex_ptr(mutex_ptr) {254 ::pthread_mutex_lock(m_mutex_ptr);255 }256 257 // This allows clients to test try and acquire the mutex...258 Locker(pthread_mutex_t *mutex_ptr, bool &lock_acquired) : m_mutex_ptr(NULL) {259 lock_acquired = ::pthread_mutex_trylock(mutex_ptr) == 0;260 if (lock_acquired)261 m_mutex_ptr = mutex_ptr;262 }263 264 ~Locker() {265 if (m_mutex_ptr)266 ::pthread_mutex_unlock(m_mutex_ptr);267 }268 269protected:270 pthread_mutex_t *m_mutex_ptr;271};272 273static void log(const char *format, ...) __attribute__((format(printf, 1, 2)));274 275static void log(int log_fd, const FDEvent *event, const char *format, ...)276 __attribute__((format(printf, 3, 4)));277 278static void backtrace_log(const char *format, ...)279 __attribute__((format(printf, 1, 2)));280 281static void backtrace_error(const char *format, ...)282 __attribute__((format(printf, 1, 2)));283 284static void log_to_fd(int log_fd, const char *format, ...)285 __attribute__((format(printf, 2, 3)));286 287static inline size_t get_backtrace(Frames &frame_buffer,288 size_t frames_to_remove) {289 void *frames[2048];290 int count = ::backtrace(&frames[0], sizeof(frames) / sizeof(void *));291 if (count > frames_to_remove)292 frame_buffer.assign(&frames[frames_to_remove], &frames[count]);293 else294 frame_buffer.assign(&frames[0], &frames[count]);295 while (frame_buffer.back() < (void *)1024)296 frame_buffer.pop_back();297 return frame_buffer.size();298}299 300static int g_log_fd = STDOUT_FILENO;301static int g_initialized = 0;302 303const char *get_process_fullpath(bool force = false) {304 static char g_process_fullpath[PATH_MAX] = {0};305 if (force || g_process_fullpath[0] == '\0') {306 // If DST is NULL, then return the number of bytes needed.307 uint32_t len = sizeof(g_process_fullpath);308 if (_NSGetExecutablePath(g_process_fullpath, &len) != 0)309 strncpy(g_process_fullpath, "<error>", sizeof(g_process_fullpath));310 }311 return g_process_fullpath;312}313 314// Returns the current process ID, or -1 if inserposing not enabled for315// this process316static int get_interposed_pid() {317 if (!g_enabled)318 return -1;319 320 const pid_t pid = getpid();321 if (g_pid != pid) {322 if (g_pid == -1) {323 g_pid = pid;324 log("Interposing file descriptor create and delete functions for %s "325 "(pid=%i)\n",326 get_process_fullpath(true), pid);327 } else {328 log("pid=%i: disabling interposing file descriptor create and delete "329 "functions for child process %s (pid=%i)\n",330 g_pid, get_process_fullpath(true), pid);331 g_enabled = false;332 return -1;333 }334 // Log when our process changes335 }336 return g_pid;337}338 339static int get_logging_fd() {340 if (!g_enabled)341 return -1;342 343 if (!g_initialized) {344 g_initialized = 1;345 346 const pid_t pid = get_interposed_pid();347 348 if (g_enabled) {349 // Keep all stack info around for all fd create and delete calls.350 // Otherwise we will remove the fd create call when a corresponding351 // fd delete call is received352 if (getenv("FileDescriptorStackLoggingNoCompact"))353 g_compact = 0;354 355 if (getenv("FileDescriptorMinimalLogging"))356 g_log_all_calls = 0;357 358 const char *log_path = getenv("FileDescriptorLogFile");359 if (log_path)360 g_log_fd = ::creat(log_path, 0660);361 else362 g_log_fd = STDOUT_FILENO;363 364 // Only let this interposing happen on the first time this matches365 // and stop this from happening so any child processes don't also366 // log their file descriptors367 ::unsetenv("DYLD_INSERT_LIBRARIES");368 } else {369 log("pid=%i: logging disabled\n", getpid());370 }371 }372 return g_log_fd;373}374 375void log_to_fd(int log_fd, const char *format, va_list args) {376 if (format && format[0] && log_fd >= 0) {377 char buffer[PATH_MAX];378 const int count = ::vsnprintf(buffer, sizeof(buffer), format, args);379 if (count > 0)380 write(log_fd, buffer, count);381 }382}383 384void log_to_fd(int log_fd, const char *format, ...) {385 if (format && format[0]) {386 va_list args;387 va_start(args, format);388 log_to_fd(log_fd, format, args);389 va_end(args);390 }391}392 393void log(const char *format, va_list args) {394 log_to_fd(get_logging_fd(), format, args);395}396 397void log(const char *format, ...) {398 if (format && format[0]) {399 va_list args;400 va_start(args, format);401 log(format, args);402 va_end(args);403 }404}405 406void log(int log_fd, const FDEvent *event, const char *format, ...) {407 if (format && format[0]) {408 va_list args;409 va_start(args, format);410 log_to_fd(log_fd, format, args);411 va_end(args);412 }413 if (event)414 event->Dump(log_fd);415}416 417void FDEvent::Dump(int log_fd) const {418 if (log_fd >= 0) {419 log_to_fd(log_fd, "%s\n", m_string_sp->c_str());420 if (!m_frames.empty())421 ::backtrace_symbols_fd(m_frames.data(), m_frames.size(), log_fd);422 423 if (m_create_event_sp) {424 log_to_fd(log_fd, "\nfd=%i was created with this event:\n", m_fd);425 m_create_event_sp->Dump(log_fd);426 log_to_fd(log_fd, "\n");427 }428 }429}430 431void backtrace_log(const char *format, ...) {432 const int log_fd = get_logging_fd();433 if (log_fd >= 0) {434 if (format && format[0]) {435 va_list args;436 va_start(args, format);437 log(format, args);438 va_end(args);439 }440 441 Frames frames;442 if (get_backtrace(frames, 2))443 ::backtrace_symbols_fd(frames.data(), frames.size(), log_fd);444 }445}446 447void backtrace_error(const char *format, ...) {448 const int pid = get_interposed_pid();449 if (pid >= 0) {450 const int log_fd = get_logging_fd();451 if (log_fd >= 0) {452 log("\nerror: %s (pid=%i): ", get_process_fullpath(), pid);453 454 if (format && format[0]) {455 va_list args;456 va_start(args, format);457 log(format, args);458 va_end(args);459 }460 461 Frames frames;462 if (get_backtrace(frames, 2))463 ::backtrace_symbols_fd(frames.data(), frames.size(), log_fd);464 }465 }466}467 468void save_backtrace(int fd, int err, const StringSP &string_sp,469 bool is_create) {470 Frames frames;471 get_backtrace(frames, 2);472 473 FDEventSP fd_event_sp(new FDEvent(fd, err, string_sp, is_create, frames));474 475 FDEventMap::iterator pos = g_fd_event_map.find(fd);476 477 if (pos != g_fd_event_map.end()) {478 // We have history for this fd...479 480 FDEventArray &event_array = g_fd_event_map[fd];481 if (fd_event_sp->IsCreateEvent()) {482 // The current fd event is a function that creates483 // a descriptor, check in case last event was484 // a create event.485 if (event_array.back()->IsCreateEvent()) {486 const int log_fd = get_logging_fd();487 // Two fd create functions in a row, we missed488 // a function that closes a fd...489 log(log_fd, fd_event_sp.get(), "\nwarning: unmatched file descriptor "490 "create event fd=%i (we missed a file "491 "descriptor close event):\n",492 fd);493 } else if (g_compact) {494 // We are compacting so we remove previous create event495 // when we get the corresponding delete event496 event_array.pop_back();497 }498 } else {499 // The current fd event is a function that deletes500 // a descriptor, check in case last event for this501 // fd was a delete event (double close!)502 if (event_array.back()->IsDeleteEvent()) {503 const int log_fd = get_logging_fd();504 // Two fd delete functions in a row, we must505 // have missed some function that opened a descriptor506 log(log_fd, fd_event_sp.get(), "\nwarning: unmatched file descriptor "507 "close event for fd=%d (we missed the "508 "file descriptor create event):\n",509 fd);510 } else if (g_compact) {511 // Since this is a close event, we want to remember the open event512 // that this close if for...513 fd_event_sp->SetCreateEvent(event_array.back());514 // We are compacting so we remove previous create event515 // when we get the corresponding delete event516 event_array.pop_back();517 }518 }519 520 event_array.push_back(fd_event_sp);521 } else {522 g_fd_event_map[fd].push_back(fd_event_sp);523 }524}525 526// socket() interpose function527extern "C" int socket$__interposed__(int domain, int type, int protocol) {528 const int pid = get_interposed_pid();529 if (pid >= 0) {530 Locker locker(&g_mutex);531 const int fd = ::socket(domain, type, protocol);532 InvalidFDErrno fd_errno(fd);533 StringSP description_sp(new String);534 if (fd == -1)535 description_sp->printf("pid=%i: socket (domain = %i, type = %i, protocol "536 "= %i) => fd=%i errno = %i",537 pid, domain, type, protocol, fd,538 fd_errno.get_errno());539 else540 description_sp->printf(541 "pid=%i: socket (domain = %i, type = %i, protocol = %i) => fd=%i",542 pid, domain, type, protocol, fd);543 if (g_log_all_calls)544 description_sp->log(get_logging_fd());545 if (fd >= 0)546 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);547 return fd;548 } else {549 return ::socket(domain, type, protocol);550 }551}552 553// socketpair() interpose function554extern "C" int socketpair$__interposed__(int domain, int type, int protocol,555 int fds[2]) {556 const int pid = get_interposed_pid();557 if (pid >= 0) {558 Locker locker(&g_mutex);559 fds[0] = -1;560 fds[1] = -1;561 const int err = socketpair(domain, type, protocol, fds);562 NegativeErrorErrno err_errno(err);563 StringSP description_sp(564 new String("pid=%i: socketpair (domain=%i, type=%i, protocol=%i, "565 "{fd=%i, fd=%i}) -> err=%i",566 pid, domain, type, protocol, fds[0], fds[1], err));567 if (g_log_all_calls)568 description_sp->log(get_logging_fd());569 if (fds[0] >= 0)570 save_backtrace(fds[0], err_errno.get_errno(), description_sp, true);571 if (fds[1] >= 0)572 save_backtrace(fds[1], err_errno.get_errno(), description_sp, true);573 return err;574 } else {575 return socketpair(domain, type, protocol, fds);576 }577}578 579// open() interpose function580extern "C" int open$__interposed__(const char *path, int oflag, int mode) {581 const int pid = get_interposed_pid();582 if (pid >= 0) {583 Locker locker(&g_mutex);584 int fd = -2;585 StringSP description_sp(new String);586 if (oflag & O_CREAT) {587 fd = ::open(path, oflag, mode);588 description_sp->printf(589 "pid=%i: open (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid,590 path, oflag, mode, fd);591 } else {592 fd = ::open(path, oflag);593 description_sp->printf("pid=%i: open (path = '%s', oflag = %i) -> fd=%i",594 pid, path, oflag, fd);595 }596 597 InvalidFDErrno fd_errno(fd);598 if (g_log_all_calls)599 description_sp->log(get_logging_fd());600 if (fd >= 0)601 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);602 return fd;603 } else {604 return ::open(path, oflag, mode);605 }606}607 608// open$NOCANCEL() interpose function609extern "C" int open$NOCANCEL$__interposed__(const char *path, int oflag,610 int mode) {611 const int pid = get_interposed_pid();612 if (pid >= 0) {613 Locker locker(&g_mutex);614 const int fd = ::open$NOCANCEL(path, oflag, mode);615 InvalidFDErrno fd_errno(fd);616 StringSP description_sp(new String(617 "pid=%i: open$NOCANCEL (path = '%s', oflag = %i, mode = %i) -> fd=%i",618 pid, path, oflag, mode, fd));619 if (g_log_all_calls)620 description_sp->log(get_logging_fd());621 if (fd >= 0)622 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);623 return fd;624 } else {625 return ::open$NOCANCEL(path, oflag, mode);626 }627}628 629// __open_extended() interpose function630extern "C" int __open_extended$__interposed__(const char *path, int oflag,631 uid_t uid, gid_t gid, int mode,632 struct kauth_filesec *fsacl) {633 const int pid = get_interposed_pid();634 if (pid >= 0) {635 Locker locker(&g_mutex);636 const int fd = ::__open_extended(path, oflag, uid, gid, mode, fsacl);637 InvalidFDErrno fd_errno(fd);638 StringSP description_sp(639 new String("pid=%i: __open_extended (path='%s', oflag=%i, uid=%i, "640 "gid=%i, mode=%i, fsacl=%p) -> fd=%i",641 pid, path, oflag, uid, gid, mode, fsacl, fd));642 if (g_log_all_calls)643 description_sp->log(get_logging_fd());644 if (fd >= 0)645 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);646 return fd;647 } else {648 return ::__open_extended(path, oflag, uid, gid, mode, fsacl);649 }650}651 652// kqueue() interpose function653extern "C" int kqueue$__interposed__(void) {654 const int pid = get_interposed_pid();655 if (pid >= 0) {656 Locker locker(&g_mutex);657 const int fd = ::kqueue();658 InvalidFDErrno fd_errno(fd);659 StringSP description_sp(new String("pid=%i: kqueue () -> fd=%i", pid, fd));660 if (g_log_all_calls)661 description_sp->log(get_logging_fd());662 if (fd >= 0)663 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);664 return fd;665 } else {666 return ::kqueue();667 }668}669 670// shm_open() interpose function671extern "C" int shm_open$__interposed__(const char *path, int oflag, int mode) {672 const int pid = get_interposed_pid();673 if (pid >= 0) {674 Locker locker(&g_mutex);675 const int fd = ::shm_open(path, oflag, mode);676 InvalidFDErrno fd_errno(fd);677 StringSP description_sp(new String(678 "pid=%i: shm_open (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid,679 path, oflag, mode, fd));680 if (g_log_all_calls)681 description_sp->log(get_logging_fd());682 if (fd >= 0)683 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);684 return fd;685 } else {686 return ::shm_open(path, oflag, mode);687 }688}689 690// accept() interpose function691extern "C" int accept$__interposed__(int socket, struct sockaddr *address,692 socklen_t *address_len) {693 const int pid = get_interposed_pid();694 if (pid >= 0) {695 Locker locker(&g_mutex);696 const int fd = ::accept(socket, address, address_len);697 InvalidFDErrno fd_errno(fd);698 StringSP description_sp(new String(699 "pid=%i: accept (socket=%i, ...) -> fd=%i", pid, socket, fd));700 if (g_log_all_calls)701 description_sp->log(get_logging_fd());702 if (fd >= 0)703 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);704 return fd;705 } else {706 return ::accept(socket, address, address_len);707 }708}709 710// accept$NOCANCEL() interpose function711extern "C" int accept$NOCANCEL$__interposed__(int socket,712 struct sockaddr *address,713 socklen_t *address_len) {714 const int pid = get_interposed_pid();715 if (pid >= 0) {716 Locker locker(&g_mutex);717 const int fd = ::accept$NOCANCEL(socket, address, address_len);718 InvalidFDErrno fd_errno(fd);719 StringSP description_sp(new String(720 "pid=%i: accept$NOCANCEL (socket=%i, ...) -> fd=%i", pid, socket, fd));721 if (g_log_all_calls)722 description_sp->log(get_logging_fd());723 if (fd >= 0)724 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);725 return fd;726 } else {727 return ::accept$NOCANCEL(socket, address, address_len);728 }729}730 731// dup() interpose function732extern "C" int dup$__interposed__(int fd2) {733 const int pid = get_interposed_pid();734 if (pid >= 0) {735 Locker locker(&g_mutex);736 const int fd = ::dup(fd2);737 InvalidFDErrno fd_errno(fd);738 StringSP description_sp(739 new String("pid=%i: dup (fd2=%i) -> fd=%i", pid, fd2, fd));740 if (g_log_all_calls)741 description_sp->log(get_logging_fd());742 if (fd >= 0)743 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);744 return fd;745 } else {746 return ::dup(fd2);747 }748}749 750// dup2() interpose function751extern "C" int dup2$__interposed__(int fd1, int fd2) {752 const int pid = get_interposed_pid();753 if (pid >= 0) {754 Locker locker(&g_mutex);755 // If "fd2" is already opened, it will be closed during the756 // dup2 call below, so we need to see if we have fd2 in our757 // open map and treat it as a close(fd2)758 FDEventMap::iterator pos = g_fd_event_map.find(fd2);759 StringSP dup2_close_description_sp(760 new String("pid=%i: dup2 (fd1=%i, fd2=%i) -> will close (fd=%i)", pid,761 fd1, fd2, fd2));762 if (pos != g_fd_event_map.end() && pos->second.back()->IsCreateEvent())763 save_backtrace(fd2, 0, dup2_close_description_sp, false);764 765 const int fd = ::dup2(fd1, fd2);766 InvalidFDErrno fd_errno(fd);767 StringSP description_sp(new String("pid=%i: dup2 (fd1=%i, fd2=%i) -> fd=%i",768 pid, fd1, fd2, fd));769 if (g_log_all_calls)770 description_sp->log(get_logging_fd());771 772 if (fd >= 0)773 save_backtrace(fd, fd_errno.get_errno(), description_sp, true);774 return fd;775 } else {776 return ::dup2(fd1, fd2);777 }778}779 780// close() interpose function781extern "C" int close$__interposed__(int fd) {782 const int pid = get_interposed_pid();783 if (pid >= 0) {784 Locker locker(&g_mutex);785 const int err = close(fd);786 NegativeErrorErrno err_errno(err);787 StringSP description_sp(new String);788 if (err == -1)789 description_sp->printf("pid=%i: close (fd=%i) => %i errno = %i (%s))",790 pid, fd, err, err_errno.get_errno(),791 strerror(err_errno.get_errno()));792 else793 description_sp->printf("pid=%i: close (fd=%i) => %i", pid, fd, err);794 if (g_log_all_calls)795 description_sp->log(get_logging_fd());796 797 if (err == 0) {798 if (fd >= 0)799 save_backtrace(fd, err, description_sp, false);800 } else if (err == -1) {801 if (err_errno.get_errno() == EBADF && fd != -1) {802 backtrace_error("close (fd=%d) resulted in EBADF:\n", fd);803 804 FDEventMap::iterator pos = g_fd_event_map.find(fd);805 if (pos != g_fd_event_map.end()) {806 log(get_logging_fd(), pos->second.back().get(),807 "\nfd=%d was previously %s with this event:\n", fd,808 pos->second.back()->IsCreateEvent() ? "opened" : "closed");809 }810 }811 }812 return err;813 } else {814 return close(fd);815 }816}817 818// close$NOCANCEL() interpose function819extern "C" int close$NOCANCEL$__interposed__(int fd) {820 const int pid = get_interposed_pid();821 if (pid >= 0) {822 Locker locker(&g_mutex);823 const int err = close$NOCANCEL(fd);824 NegativeErrorErrno err_errno(err);825 StringSP description_sp(new String);826 if (err == -1)827 description_sp->printf(828 "pid=%i: close$NOCANCEL (fd=%i) => %i errno = %i (%s))", pid, fd, err,829 err_errno.get_errno(), strerror(err_errno.get_errno()));830 else831 description_sp->printf("pid=%i: close$NOCANCEL (fd=%i) => %i", pid, fd,832 err);833 if (g_log_all_calls)834 description_sp->log(get_logging_fd());835 836 if (err == 0) {837 if (fd >= 0)838 save_backtrace(fd, err, description_sp, false);839 } else if (err == -1) {840 if (err_errno.get_errno() == EBADF && fd != -1) {841 backtrace_error("close$NOCANCEL (fd=%d) resulted in EBADF\n:", fd);842 843 FDEventMap::iterator pos = g_fd_event_map.find(fd);844 if (pos != g_fd_event_map.end()) {845 log(get_logging_fd(), pos->second.back().get(),846 "\nfd=%d was previously %s with this event:\n", fd,847 pos->second.back()->IsCreateEvent() ? "opened" : "closed");848 }849 }850 }851 return err;852 } else {853 return close$NOCANCEL(fd);854 }855}856 857// pipe() interpose function858extern "C" int pipe$__interposed__(int fds[2]) {859 const int pid = get_interposed_pid();860 if (pid >= 0) {861 Locker locker(&g_mutex);862 fds[0] = -1;863 fds[1] = -1;864 const int err = pipe(fds);865 const int saved_errno = errno;866 StringSP description_sp(new String(867 "pid=%i: pipe ({fd=%i, fd=%i}) -> err=%i", pid, fds[0], fds[1], err));868 if (g_log_all_calls)869 description_sp->log(get_logging_fd());870 if (fds[0] >= 0)871 save_backtrace(fds[0], saved_errno, description_sp, true);872 if (fds[1] >= 0)873 save_backtrace(fds[1], saved_errno, description_sp, true);874 errno = saved_errno;875 return err;876 } else {877 return pipe(fds);878 }879}880 881// get_fd_history()882//883// This function allows runtime access to the file descriptor history.884//885// @param[in] log_fd886// The file descriptor to log to887//888// @param[in] fd889// The file descriptor whose history should be dumped890extern "C" void get_fd_history(int log_fd, int fd) {891 // "create" below needs to be outside of the mutex locker scope892 if (log_fd >= 0) {893 bool got_lock = false;894 Locker locker(&g_mutex, got_lock);895 if (got_lock) {896 FDEventMap::iterator pos = g_fd_event_map.find(fd);897 log_to_fd(log_fd, "Dumping file descriptor history for fd=%i:\n", fd);898 if (pos != g_fd_event_map.end()) {899 FDEventArray &event_array = g_fd_event_map[fd];900 const size_t num_events = event_array.size();901 for (size_t i = 0; i < num_events; ++i)902 event_array[i]->Dump(log_fd);903 } else {904 log_to_fd(log_fd, "error: no file descriptor events found for fd=%i\n",905 fd);906 }907 } else {908 log_to_fd(log_fd, "error: fd event mutex is locked...\n");909 }910 }911}912 913// Interposing914// FD creation routines915DYLD_INTERPOSE(accept$__interposed__, accept);916DYLD_INTERPOSE(accept$NOCANCEL$__interposed__, accept$NOCANCEL);917DYLD_INTERPOSE(dup$__interposed__, dup);918DYLD_INTERPOSE(dup2$__interposed__, dup2);919DYLD_INTERPOSE(kqueue$__interposed__, kqueue);920DYLD_INTERPOSE(open$__interposed__, open);921DYLD_INTERPOSE(open$NOCANCEL$__interposed__, open$NOCANCEL);922DYLD_INTERPOSE(__open_extended$__interposed__, __open_extended);923DYLD_INTERPOSE(pipe$__interposed__, pipe);924DYLD_INTERPOSE(shm_open$__interposed__, shm_open);925DYLD_INTERPOSE(socket$__interposed__, socket);926DYLD_INTERPOSE(socketpair$__interposed__, socketpair);927 928// FD deleting routines929DYLD_INTERPOSE(close$__interposed__, close);930DYLD_INTERPOSE(close$NOCANCEL$__interposed__, close$NOCANCEL);931 932} // namespace fd_interposing933