捕获写入打开文件描述符的数据及FD复用转发技术问询
Great question—this is a super common scenario for monitoring or piping data between apps, and while it’s absolutely possible, there are some OS-specific rules and tradeoffs you need to keep in mind. Let’s break this down step by step:
First: Can You "Grab" Another Process's FD?
Short answer: Not directly in the way you might think, but you can get access to the underlying file/resource that the FD points to—with the right permissions.
On Linux (the most common system for this kind of work), every process has a /proc/<pid>/fd directory where you’ll find symbolic links to every file/resource the process has open. For example, if app A has PID 1234 and is writing to FD 5, /proc/1234/fd/5 will link directly to the disk file (or pipe/socket) it’s using.
To access this:
- You need the same UID/GID as the target process, or root privileges.
- Opening that symbolic link in your own process will give you a new FD in your process that points to the same underlying resource. Important note: this new FD has its own independent file offset—so if app A writes to position 1000, your FD will still start at position 0 unless you explicitly adjust it with
lseek().
On macOS or Windows, the approach is different (macOS uses proc_pidinfo and fcntl with F_DUPFD_FROM; Windows uses DuplicateHandle), but the core idea is the same: you can duplicate the resource handle, not the FD number itself.
How to Forward Writes from App A to Other Apps
Once you have access to the resource, there are two main ways to capture and forward the data:
1. File System-Level Monitoring (Simplest for Disk Files)
If app A is writing to a regular disk file, you can use OS-level file change notifications to detect when the file is modified, then read the new content and forward it.
- On Linux: Use
inotifyto watch forIN_MODIFYevents on the file. After each modification, read from the current end of the file (uselseek(fd, 0, SEEK_END)to track the position) and send the data to your target apps (via sockets, pipes, or even stdout). - On macOS: Use
FSEventsorkqueuefor similar behavior.
Here’s a quick C example of this approach on Linux:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/inotify.h> #include <sys/stat.h> #include <fcntl.h> #define BUF_SIZE 1024 int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <file-path>\n", argv[0]); exit(EXIT_FAILURE); } // Open the file and jump to its end to capture new writes int file_fd = open(argv[1], O_RDONLY); if (file_fd == -1) { perror("open"); exit(EXIT_FAILURE); } lseek(file_fd, 0, SEEK_END); // Set up inotify to watch for file modifications int inotify_fd = inotify_init(); if (inotify_fd == -1) { perror("inotify_init"); exit(EXIT_FAILURE); } int watch_fd = inotify_add_watch(inotify_fd, argv[1], IN_MODIFY); if (watch_fd == -1) { perror("inotify_add_watch"); exit(EXIT_FAILURE); } char buf[BUF_SIZE]; ssize_t bytes_read; struct inotify_event *event; while (1) { // Wait for a modification event int len = read(inotify_fd, buf, BUF_SIZE); if (len == -1) { perror("read"); exit(EXIT_FAILURE); } // Process each event in the buffer for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) { event = (struct inotify_event *)ptr; if (event->mask & IN_MODIFY) { // Read new content and forward it (here we write to stdout) while ((bytes_read = read(file_fd, buf, BUF_SIZE)) > 0) { write(STDOUT_FILENO, buf, bytes_read); } } } } // Cleanup (we'll never reach this in the loop) close(file_fd); close(inotify_fd); exit(EXIT_SUCCESS); }
2. Process Interception (For Precise Control)
If you need to capture every single write() call from app A (even for non-disk resources like pipes or sockets), you’ll need to intercept the process’s system calls:
- Linux LD_PRELOAD Injection: Create a shared library that overrides the
write()function, then force app A to load it via theLD_PRELOADenvironment variable. Your customwrite()can send a copy of the data to your forwarding service before calling the realwrite(). This works for dynamically linked apps. - Ptrace: Use the
ptrace()system call to attach to app A, pause it when it makes awrite()call, extract the data being written, then resume the process. This is more heavyweight and requires root privileges, but works for any app.
Reusing the FD for Read/Write
As mentioned earlier, you can’t reuse the exact FD number from app A—FDs are process-specific identifiers. But when you open the /proc symbolic link (or duplicate the handle on other OSes), you get a perfectly valid FD in your own process that you can read from and write to just like any other FD.
Just be aware of:
- File offsets: Your FD’s offset is independent of app A’s. If you want to read the latest data, you’ll need to seek to the end before reading.
- Concurrent writes: If app A is writing while you’re reading, you might get partial data—make sure your code handles partial reads/writes gracefully.
Key Limitations to Keep in Mind
- Permissions: You’ll need elevated privileges (root) for most interception methods, or matching UID/GID for
/procaccess. - Non-disk resources: For pipes, sockets, or anonymous files, file system monitoring won’t work—you’ll need process interception instead.
- Performance: Ptrace and LD_PRELOAD can add overhead to app A’s execution; file system monitoring is much lighter but has slight latency.
内容的提问来源于stack exchange,提问作者user1610950

