1 //===-- FDInterposing.cpp ---------------------------------------*- C++ -*-===//
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-exception
7 //===----------------------------------------------------------------------===//
9 // This file helps with catching double close calls on unix integer file
10 // descriptors by interposing functions for all file descriptor create and
11 // close operations. A stack backtrace for every create and close function is
12 // maintained, and every create and close operation is logged. When a double
13 // file descriptor close is encountered, it will be logged.
15 // To enable the interposing in a darwin program, set the DYLD_INSERT_LIBRARIES
16 // environment variable as follows:
18 // DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib /path/to/executable
20 // (setenv DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib ;
21 // /path/to/executable)
23 // Other environment variables that can alter the default actions of this
24 // interposing shared library include:
26 // "FileDescriptorStackLoggingNoCompact"
28 // With this environment variable set, all file descriptor create and
29 // delete operations will be permanantly maintained in the event map.
30 // The default action is to compact the create/delete events by removing
31 // any previous file descriptor create events that are matched with a
32 // corresponding file descriptor delete event when the next valid file
33 // descriptor create event is detected.
35 // "FileDescriptorMinimalLogging"
37 // By default every file descriptor create and delete operation is logged
38 // (to STDOUT by default, see the "FileDescriptorLogFile"). This can be
39 // suppressed to only show errors and warnings by setting this environment
40 // variable (the value in not important).
42 // "FileDescriptorLogFile=<path>"
44 // By default logging goes to STDOUT_FILENO, but this can be changed by
45 // setting FileDescriptorLogFile. The value is a path to a file that
46 // will be opened and used for logging.
47 //===----------------------------------------------------------------------===//
55 #include <mach-o/dyld-interposing.h>
56 #include <mach-o/dyld.h>
62 #include <sys/event.h>
64 #include <sys/socket.h>
66 #include <sys/types.h>
72 int accept$
NOCANCEL(int, struct sockaddr
*__restrict
, socklen_t
*__restrict
);
73 int close$
NOCANCEL(int);
74 int open$
NOCANCEL(const char *, int, ...);
75 int __open_extended(const char *, int, uid_t
, gid_t
, int,
76 struct kauth_filesec
*);
79 namespace fd_interposing
{
81 // String class so we can get formatted strings without having to worry
82 // about the memory storage since it will allocate the memory it needs.
85 String() : m_str(NULL
) {}
87 String(const char *format
, ...) : m_str(NULL
) {
89 va_start(args
, format
);
90 vprintf(format
, args
);
94 ~String() { reset(); }
96 void reset(char *s
= NULL
) {
102 const char *c_str() const { return m_str
; }
104 void printf(const char *format
, ...) {
106 va_start(args
, format
);
107 vprintf(format
, args
);
110 void vprintf(const char *format
, va_list args
) {
112 ::vasprintf(&m_str
, format
, args
);
115 void log(int log_fd
) {
116 if (m_str
&& log_fd
>= 0) {
117 const int len
= strlen(m_str
);
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);
131 String(const String
&) = delete;
132 const String
&operator=(const String
&) = delete;
136 typedef std::vector
<void *> Frames
;
138 typedef std::vector
<void *> Frames
;
139 typedef std::tr1::shared_ptr
<FDEvent
> FDEventSP
;
140 typedef std::tr1::shared_ptr
<String
> StringSP
;
144 // A class that describes a file descriptor event.
146 // File descriptor events fall into one of two categories: create events
147 // and delete events.
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
) {}
157 bool IsCreateEvent() const { return m_is_create
; }
159 bool IsDeleteEvent() const { return !m_is_create
; }
161 Frames
&GetFrames() { return m_frames
; }
163 const Frames
&GetFrames() const { return m_frames
; }
165 int GetFD() const { return m_fd
; }
167 int GetError() const { return m_err
; }
169 void Dump(int log_fd
) const;
171 void SetCreateEvent(FDEventSP
&create_event_sp
) {
172 m_create_event_sp
= create_event_sp
;
176 // A shared pointer to a String that describes this event in
177 // detail (all args and return and error values)
178 StringSP m_string_sp
;
179 // The frames for the stack backtrace for this event
181 // If this is a file descriptor delete event, this might contain
182 // the corresponding file descriptor create event
183 FDEventSP m_create_event_sp
;
184 // The file descriptor for this event
186 // The error code (if any) for this event
188 // True if this event is a file descriptor create event, false
189 // if it is a file descriptor delete event
193 // Templatized class that will save errno only if the "value" it is
194 // constructed with is equal to INVALID. When the class goes out of
195 // scope, it will restore errno if it was saved.
196 template <int INVALID
> class Errno
{
198 // Save errno only if we are supposed to
200 : m_saved_errno((value
== INVALID
) ? errno
: 0),
201 m_restore(value
== INVALID
) {}
203 // Restore errno only if we are supposed to
206 errno
= m_saved_errno
;
209 // Accessor for the saved value of errno
210 int get_errno() const { return m_saved_errno
; }
213 const int m_saved_errno
;
214 const bool m_restore
;
217 typedef Errno
<-1> InvalidFDErrno
;
218 typedef Errno
<-1> NegativeErrorErrno
;
219 typedef std::vector
<FDEventSP
> FDEventArray
;
220 typedef std::map
<int, FDEventArray
> FDEventMap
;
223 // Global event map that contains all file descriptor events. As file
224 // descriptor create and close events come in, they will get filled
225 // into this map (protected by g_mutex). When a file descriptor close
226 // event is detected, the open event will be removed and placed into
227 // the close event so if something tries to double close a file
228 // descriptor we can show the previous close event and the file
229 // descriptor event that created it. When a new file descriptor create
230 // event comes in, we will remove the previous one for that file
231 // descriptor unless the environment variable
232 // "FileDescriptorStackLoggingNoCompact"
233 // is set. The file descriptor history can be accessed using the
234 // get_fd_history() function.
235 static FDEventMap g_fd_event_map
;
236 // A mutex to protect access to our data structures in g_fd_event_map
237 // and also our logging messages
238 static pthread_mutex_t g_mutex
= PTHREAD_MUTEX_INITIALIZER
;
239 // Log all file descriptor create and close events by default. Only log
240 // warnings and errors if the "FileDescriptorMinimalLogging" environment
242 static int g_log_all_calls
= 1;
243 // We compact the file descriptor events by default. Set the environment
244 // varible "FileDescriptorStackLoggingNoCompact" to keep a full history.
245 static int g_compact
= 1;
246 // The current process ID
247 static int g_pid
= -1;
248 static bool g_enabled
= true;
249 // Mutex class that will lock a mutex when it is constructed, and unlock
250 // it when is goes out of scope
253 Locker(pthread_mutex_t
*mutex_ptr
) : m_mutex_ptr(mutex_ptr
) {
254 ::pthread_mutex_lock(m_mutex_ptr
);
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;
261 m_mutex_ptr
= mutex_ptr
;
266 ::pthread_mutex_unlock(m_mutex_ptr
);
270 pthread_mutex_t
*m_mutex_ptr
;
273 static void log(const char *format
, ...) __attribute__((format(printf
, 1, 2)));
275 static void log(int log_fd
, const FDEvent
*event
, const char *format
, ...)
276 __attribute__((format(printf
, 3, 4)));
278 static void backtrace_log(const char *format
, ...)
279 __attribute__((format(printf
, 1, 2)));
281 static void backtrace_error(const char *format
, ...)
282 __attribute__((format(printf
, 1, 2)));
284 static void log_to_fd(int log_fd
, const char *format
, ...)
285 __attribute__((format(printf
, 2, 3)));
287 static inline size_t get_backtrace(Frames
&frame_buffer
,
288 size_t frames_to_remove
) {
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
]);
294 frame_buffer
.assign(&frames
[0], &frames
[count
]);
295 while (frame_buffer
.back() < (void *)1024)
296 frame_buffer
.pop_back();
297 return frame_buffer
.size();
300 static int g_log_fd
= STDOUT_FILENO
;
301 static int g_initialized
= 0;
303 const 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
));
311 return g_process_fullpath
;
314 // Returns the current process ID, or -1 if inserposing not enabled for
316 static int get_interposed_pid() {
320 const pid_t pid
= getpid();
324 log("Interposing file descriptor create and delete functions for %s "
326 get_process_fullpath(true), pid
);
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
);
334 // Log when our process changes
339 static int get_logging_fd() {
343 if (!g_initialized
) {
346 const pid_t pid
= get_interposed_pid();
349 // Keep all stack info around for all fd create and delete calls.
350 // Otherwise we will remove the fd create call when a corresponding
351 // fd delete call is received
352 if (getenv("FileDescriptorStackLoggingNoCompact"))
355 if (getenv("FileDescriptorMinimalLogging"))
358 const char *log_path
= getenv("FileDescriptorLogFile");
360 g_log_fd
= ::creat(log_path
, 0660);
362 g_log_fd
= STDOUT_FILENO
;
364 // Only let this interposing happen on the first time this matches
365 // and stop this from happening so any child processes don't also
366 // log their file descriptors
367 ::unsetenv("DYLD_INSERT_LIBRARIES");
369 log("pid=%i: logging disabled\n", getpid());
375 void 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
);
380 write(log_fd
, buffer
, count
);
384 void log_to_fd(int log_fd
, const char *format
, ...) {
385 if (format
&& format
[0]) {
387 va_start(args
, format
);
388 log_to_fd(log_fd
, format
, args
);
393 void log(const char *format
, va_list args
) {
394 log_to_fd(get_logging_fd(), format
, args
);
397 void log(const char *format
, ...) {
398 if (format
&& format
[0]) {
400 va_start(args
, format
);
406 void log(int log_fd
, const FDEvent
*event
, const char *format
, ...) {
407 if (format
&& format
[0]) {
409 va_start(args
, format
);
410 log_to_fd(log_fd
, format
, args
);
417 void FDEvent::Dump(int log_fd
) const {
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
);
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");
431 void backtrace_log(const char *format
, ...) {
432 const int log_fd
= get_logging_fd();
434 if (format
&& format
[0]) {
436 va_start(args
, format
);
442 if (get_backtrace(frames
, 2))
443 ::backtrace_symbols_fd(frames
.data(), frames
.size(), log_fd
);
447 void backtrace_error(const char *format
, ...) {
448 const int pid
= get_interposed_pid();
450 const int log_fd
= get_logging_fd();
452 log("\nerror: %s (pid=%i): ", get_process_fullpath(), pid
);
454 if (format
&& format
[0]) {
456 va_start(args
, format
);
462 if (get_backtrace(frames
, 2))
463 ::backtrace_symbols_fd(frames
.data(), frames
.size(), log_fd
);
468 void save_backtrace(int fd
, int err
, const StringSP
&string_sp
,
471 get_backtrace(frames
, 2);
473 FDEventSP
fd_event_sp(new FDEvent(fd
, err
, string_sp
, is_create
, frames
));
475 FDEventMap::iterator pos
= g_fd_event_map
.find(fd
);
477 if (pos
!= g_fd_event_map
.end()) {
478 // We have history for this fd...
480 FDEventArray
&event_array
= g_fd_event_map
[fd
];
481 if (fd_event_sp
->IsCreateEvent()) {
482 // The current fd event is a function that creates
483 // a descriptor, check in case last event was
485 if (event_array
.back()->IsCreateEvent()) {
486 const int log_fd
= get_logging_fd();
487 // Two fd create functions in a row, we missed
488 // 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",
493 } else if (g_compact
) {
494 // We are compacting so we remove previous create event
495 // when we get the corresponding delete event
496 event_array
.pop_back();
499 // The current fd event is a function that deletes
500 // a descriptor, check in case last event for this
501 // 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 must
505 // have missed some function that opened a descriptor
506 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",
510 } else if (g_compact
) {
511 // Since this is a close event, we want to remember the open event
512 // that this close if for...
513 fd_event_sp
->SetCreateEvent(event_array
.back());
514 // We are compacting so we remove previous create event
515 // when we get the corresponding delete event
516 event_array
.pop_back();
520 event_array
.push_back(fd_event_sp
);
522 g_fd_event_map
[fd
].push_back(fd_event_sp
);
526 // socket() interpose function
527 extern "C" int socket$
__interposed__(int domain
, int type
, int protocol
) {
528 const int pid
= get_interposed_pid();
530 Locker
locker(&g_mutex
);
531 const int fd
= ::socket(domain
, type
, protocol
);
532 InvalidFDErrno
fd_errno(fd
);
533 StringSP
description_sp(new String
);
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());
540 description_sp
->printf(
541 "pid=%i: socket (domain = %i, type = %i, protocol = %i) => fd=%i",
542 pid
, domain
, type
, protocol
, fd
);
544 description_sp
->log(get_logging_fd());
546 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
549 return ::socket(domain
, type
, protocol
);
553 // socketpair() interpose function
554 extern "C" int socketpair$
__interposed__(int domain
, int type
, int protocol
,
556 const int pid
= get_interposed_pid();
558 Locker
locker(&g_mutex
);
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
));
568 description_sp
->log(get_logging_fd());
570 save_backtrace(fds
[0], err_errno
.get_errno(), description_sp
, true);
572 save_backtrace(fds
[1], err_errno
.get_errno(), description_sp
, true);
575 return socketpair(domain
, type
, protocol
, fds
);
579 // open() interpose function
580 extern "C" int open$
__interposed__(const char *path
, int oflag
, int mode
) {
581 const int pid
= get_interposed_pid();
583 Locker
locker(&g_mutex
);
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
);
592 fd
= ::open(path
, oflag
);
593 description_sp
->printf("pid=%i: open (path = '%s', oflag = %i) -> fd=%i",
594 pid
, path
, oflag
, fd
);
597 InvalidFDErrno
fd_errno(fd
);
599 description_sp
->log(get_logging_fd());
601 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
604 return ::open(path
, oflag
, mode
);
608 // open$NOCANCEL() interpose function
609 extern "C" int open$NOCANCEL$
__interposed__(const char *path
, int oflag
,
611 const int pid
= get_interposed_pid();
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
));
620 description_sp
->log(get_logging_fd());
622 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
625 return ::open$
NOCANCEL(path
, oflag
, mode
);
629 // __open_extended() interpose function
630 extern "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();
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
));
643 description_sp
->log(get_logging_fd());
645 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
648 return ::__open_extended(path
, oflag
, uid
, gid
, mode
, fsacl
);
652 // kqueue() interpose function
653 extern "C" int kqueue$
__interposed__(void) {
654 const int pid
= get_interposed_pid();
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
));
661 description_sp
->log(get_logging_fd());
663 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
670 // shm_open() interpose function
671 extern "C" int shm_open$
__interposed__(const char *path
, int oflag
, int mode
) {
672 const int pid
= get_interposed_pid();
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
));
681 description_sp
->log(get_logging_fd());
683 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
686 return ::shm_open(path
, oflag
, mode
);
690 // accept() interpose function
691 extern "C" int accept$
__interposed__(int socket
, struct sockaddr
*address
,
692 socklen_t
*address_len
) {
693 const int pid
= get_interposed_pid();
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
));
701 description_sp
->log(get_logging_fd());
703 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
706 return ::accept(socket
, address
, address_len
);
710 // accept$NOCANCEL() interpose function
711 extern "C" int accept$NOCANCEL$
__interposed__(int socket
,
712 struct sockaddr
*address
,
713 socklen_t
*address_len
) {
714 const int pid
= get_interposed_pid();
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
));
722 description_sp
->log(get_logging_fd());
724 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
727 return ::accept$
NOCANCEL(socket
, address
, address_len
);
731 // dup() interpose function
732 extern "C" int dup$
__interposed__(int fd2
) {
733 const int pid
= get_interposed_pid();
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
));
741 description_sp
->log(get_logging_fd());
743 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
750 // dup2() interpose function
751 extern "C" int dup2$
__interposed__(int fd1
, int fd2
) {
752 const int pid
= get_interposed_pid();
754 Locker
locker(&g_mutex
);
755 // If "fd2" is already opened, it will be closed during the
756 // dup2 call below, so we need to see if we have fd2 in our
757 // 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
,
762 if (pos
!= g_fd_event_map
.end() && pos
->second
.back()->IsCreateEvent())
763 save_backtrace(fd2
, 0, dup2_close_description_sp
, false);
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",
770 description_sp
->log(get_logging_fd());
773 save_backtrace(fd
, fd_errno
.get_errno(), description_sp
, true);
776 return ::dup2(fd1
, fd2
);
780 // close() interpose function
781 extern "C" int close$
__interposed__(int fd
) {
782 const int pid
= get_interposed_pid();
784 Locker
locker(&g_mutex
);
785 const int err
= close(fd
);
786 NegativeErrorErrno
err_errno(err
);
787 StringSP
description_sp(new String
);
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()));
793 description_sp
->printf("pid=%i: close (fd=%i) => %i", pid
, fd
, err
);
795 description_sp
->log(get_logging_fd());
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
);
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");
818 // close$NOCANCEL() interpose function
819 extern "C" int close$NOCANCEL$
__interposed__(int fd
) {
820 const int pid
= get_interposed_pid();
822 Locker
locker(&g_mutex
);
823 const int err
= close$
NOCANCEL(fd
);
824 NegativeErrorErrno
err_errno(err
);
825 StringSP
description_sp(new String
);
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()));
831 description_sp
->printf("pid=%i: close$NOCANCEL (fd=%i) => %i", pid
, fd
,
834 description_sp
->log(get_logging_fd());
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
);
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");
853 return close$
NOCANCEL(fd
);
857 // pipe() interpose function
858 extern "C" int pipe$
__interposed__(int fds
[2]) {
859 const int pid
= get_interposed_pid();
861 Locker
locker(&g_mutex
);
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
));
869 description_sp
->log(get_logging_fd());
871 save_backtrace(fds
[0], saved_errno
, description_sp
, true);
873 save_backtrace(fds
[1], saved_errno
, description_sp
, true);
883 // This function allows runtime access to the file descriptor history.
886 // The file descriptor to log to
889 // The file descriptor whose history should be dumped
890 extern "C" void get_fd_history(int log_fd
, int fd
) {
891 // "create" below needs to be outside of the mutex locker scope
893 bool got_lock
= false;
894 Locker
locker(&g_mutex
, 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
);
904 log_to_fd(log_fd
, "error: no file descriptor events found for fd=%i\n",
908 log_to_fd(log_fd
, "error: fd event mutex is locked...\n");
914 // FD creation routines
915 DYLD_INTERPOSE(accept$__interposed__
, accept
);
916 DYLD_INTERPOSE(accept$NOCANCEL$__interposed__
, accept$NOCANCEL
);
917 DYLD_INTERPOSE(dup$__interposed__
, dup
);
918 DYLD_INTERPOSE(dup2$__interposed__
, dup2
);
919 DYLD_INTERPOSE(kqueue$__interposed__
, kqueue
);
920 DYLD_INTERPOSE(open$__interposed__
, open
);
921 DYLD_INTERPOSE(open$NOCANCEL$__interposed__
, open$NOCANCEL
);
922 DYLD_INTERPOSE(__open_extended$__interposed__
, __open_extended
);
923 DYLD_INTERPOSE(pipe$__interposed__
, pipe
);
924 DYLD_INTERPOSE(shm_open$__interposed__
, shm_open
);
925 DYLD_INTERPOSE(socket$__interposed__
, socket
);
926 DYLD_INTERPOSE(socketpair$__interposed__
, socketpair
);
928 // FD deleting routines
929 DYLD_INTERPOSE(close$__interposed__
, close
);
930 DYLD_INTERPOSE(close$NOCANCEL$__interposed__
, close$NOCANCEL
);
932 } // namespace fd_interposing