Как прочитать открытый файловый дескриптор вне процесса записи

Как прочитать открытый файловый дескриптор вне процесса записи

Как открыть файловый дескриптор и вывести его на терминал, пока в него записывается информация из процесса?

У меня есть программа резервного копирования Duplicity, которая записывает свои логи в файловый дескриптор, указанный параметром --log-fd=16.

Конечно, если я побегу, lsof -p <duplicity PID>то увижу:

python2 9224 myuser    0r      CHR                1,3      0t0         6 /dev/null
python2 9224 myuser    1w      CHR                1,3      0t0         6 /dev/null
python2 9224 myuser    2w      CHR                1,3      0t0         6 /dev/null
python2 9224 myuser    3u  a_inode               0,11        0      7005 [eventfd]
python2 9224 myuser    4u     unix 0x0000000000000000      0t0    158199 type=STREAM
python2 9224 myuser    5u  a_inode               0,11        0      7005 [eventfd]
python2 9224 myuser    6u  a_inode               0,11        0      7005 [eventfd]
python2 9224 myuser    7r      DIR                8,3     4096  22414346 <some random file being accessed during the backup>
python2 9224 myuser    8r      CHR                1,9      0t0        11 /dev/urandom
python2 9224 myuser   15r     FIFO               0,10      0t0    157054 pipe
python2 9224 myuser   16w     FIFO               0,10      0t0    157054 pipe

Однако если я попытаюсь открыть дескриптор файла в Python, я получу ошибку:

>>> import os
>>> os.fdopen(16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 9] Bad file descriptor

Почему это? Как мне прочитать дескриптор файла?

решение1

Использование strace(отслеживание системных вызовов и сигналов).

Использование:

sudo strace -p <PID of writing process> -s 9999 -e write=<corresponding FD>

Из страницы руководства:

       -p pid      Attach to the process with the process ID pid and begin tracing.  The trace may be terminated
                   at any time by a keyboard interrupt signal (CTRL-C).  strace will respond by detaching itself
                   from  the  traced process(es) leaving it (them) to continue running.  Multiple -p options can
                   be used to attach to many processes in addition to command (which is optional if at least one
                   -p option is given).  -p "`pidof PROG`" syntax is supported.
    
       -s strsize  Specify the maximum string size to print (the default is 32).  Note that  filenames  are  not
                   considered strings and are always printed in full.
    
       -e read=set
              Perform a full hexadecimal and ASCII dump of all the data read from file descriptors listed in the
              specified set.  For example,  to  see  all  input  activity  on  file  descriptors  3  and  5  use
              -e read=3,5.   Note  that  this  is independent from the normal tracing of the read(2) system call
              which is controlled by the option -e trace=read.

       -e write=set
              Perform a full hexadecimal and ASCII dump of all the data written to file  descriptors  listed  in
              the  specified  set.   For  example,  to  see  all output activity on file descriptors 3 and 5 use
              -e write=3,5.  Note that this is independent from the normal tracing of the write(2)  system  call
              which is controlled by the option -e trace=write.

Ссылка:https://man7.org/linux/man-pages/man1/strace.1.html

решение2

Я считаю, что опция duplicity --log=fdпредназначена для сложных конвейеров, где вы хотите отделить stderrи stdoutот своего журнала.

Этот ответ наэтот вопросдает пример. Вот простой пример:

#!/bin/sh
# Generate output on three different fds
echo hello >&3
echo world >&2
echo today >&1

И когда это будет выполнено таким образом,

./foo 2> 2.log 3> 3.log 1> 1.log

Результаты в

$ cat 1.log 2.log 3.log
today
world
hello

решение3

Недавно в Linux появились системные вызовы именно для таких целей:

  1. Использоватьpidfd_openчтобы получить «PID FD» из PID.

  2. Использоватьpidfd_getfdдля получения файлового дескриптора из другого процесса через его PID FD.

Начиная с версии Python 3.9, pidfd_openдоступен какos.pidfd_open.

pidfd_getfdпока не представлен в стандартной библиотеке Python, но к счастьюctypesдавайте позвонимsyscallНомера системных вызовов Linux никогда не меняются, а API и ABI системных вызовов Linux изменяются только способами, обеспечивающими обратную совместимость.

Так!

from ctypes import CDLL, c_int, c_long, c_uint, get_errno
from functools import partial
from os import strerror


_syscall = CDLL(None, use_errno=True).syscall

# Non-variadic system call number argument:
_syscall.argtypes = [c_long]


def pidfd_getfd(pidfd, targetfd):
    fd = _syscall(
             438,  # system call number of pidfd_getfd
             c_int(pidfd),
             c_int(targetfd),
             c_uint(0),  # unused "flags" argument
         )
    if fd == -1:
        errno = get_errno()
        raise OSError(errno, strerror(errno))
    return fd

Таким образом, в вашем примере, где интересующий вас PID — 9224, вместо вызова os.fdopen(16)вы бы сделали os.fdopen(pidfd_getfd(os.pidfd_open(9224), 16)).

Обратите внимание, что это работает только в том случае, если у вас есть необходимые разрешения для доступа к целевому процессу, поэтому вам может потребоваться запустить этот код с повышенными привилегиями (например, sudo) в зависимости от того, как был запущен процесс и как настроена ваша система.

Связанный контент