Wie öffnen Sie einen Dateideskriptor und geben ihn an ein Terminal zurück, während er von einem Prozess beschrieben wird?
Ich habe ein Backup-Programm, Duplicity, das seine Protokolle in einen durch den Parameter angegebenen Dateideskriptor schreibt --log-fd=16
.
Und tatsächlich, wenn ich laufe, lsof -p <duplicity PID>
sehe ich:
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
Wenn ich jedoch versuche, den Dateideskriptor in Python zu öffnen, erhalte ich eine Fehlermeldung:
>>> import os
>>> os.fdopen(16)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 9] Bad file descriptor
Warum ist das so? Wie lese ich den Dateideskriptor?
Antwort1
Verwenden Sie strace
(Systemaufrufe und Signale verfolgen).
Verwendung:
sudo strace -p <PID of writing process> -s 9999 -e write=<corresponding FD>
Aus der Handbuchseite:
-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.
Referenz:https://man7.org/linux/man-pages/man1/strace.1.html
Antwort2
Ich glaube, die Option „Duplicity“ ist für komplexe Pipelines gedacht, bei denen Sie Ihr Protokoll trennen --log=fd
möchten .stderr
stdout
Diese Antwort aufdiese Fragegibt ein Beispiel. Hier ist ein einfaches Beispiel:
#!/bin/sh
# Generate output on three different fds
echo hello >&3
echo world >&2
echo today >&1
Und wenn es so ausgeführt wird,
./foo 2> 2.log 3> 3.log 1> 1.log
Ergebnisse in
$ cat 1.log 2.log 3.log
today
world
hello
Antwort3
Linux hat vor kurzem Systemaufrufe für genau diese Dinge erhalten:
Verwenden
pidfd_open
um ein „PID FD“ von einem PID zu erhalten.Verwenden
pidfd_getfd
um einen Dateideskriptor von einem anderen Prozess über seinen PID FD zu erhalten.
Ab Python 3.9 pidfd_open
ist verfügbar alsos.pidfd_open
.
pidfd_getfd
ist noch nicht in der Python-Standardbibliothek verfügbar, aber zum Glückctypes
lass uns anrufensyscall
, Linux-Systemaufrufnummern ändern sich nie, und die Linux-Systemaufruf-API und ABI ändern sich nur auf abwärtskompatible Weise.
Also!
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
In Ihrem Beispiel, bei dem die betreffende PID 9224 ist, os.fdopen(16)
würden Sie also statt aufrufen Folgendes tun os.fdopen(pidfd_getfd(os.pidfd_open(9224), 16))
: .
Beachten Sie, dass dies nur funktioniert, wenn Sie über die erforderlichen Berechtigungen verfügen, um auf den Zielprozess zuzugreifen. Daher müssen Sie diesen Code möglicherweise mit erhöhten Berechtigungen (z. B. sudo
) ausführen, je nachdem, wie der Prozess gestartet wurde und wie Ihr System konfiguriert ist.