Linux 中的文件描述符及其示例

有一次,在一次采访中,有人问我,如果你发现某个服务由于磁盘空间不足而无法运行,你会怎么做?

当然,我回答说我会看看这个地方住了什么,如果可能的话,我会清理这个地方。
然后面试官问,如果分区上没有可用空间,但你也没有看到任何占用所有空间的文件怎么办?

对此我说你可以随时查看打开的文件描述符,例如使用 lsof 命令,了解哪个应用程序占用了所有可用空间,然后你可以根据情况采取行动,具体取决于是否需要数据。

面试官打断了我的最后一句话,并补充了他的问题:“假设我们不需要数据,它只是一个调试日志,但应用程序无法工作,因为它无法编写调试”?

“好吧,”我回答道,“我们可以在应用程序配置中关闭调试并重新启动它。”
面试官反对道:“不,我们不能重新启动应用程序,我们仍然有重要的数据存储在内存中,并且重要的客户端连接到服务本身,我们不能强制再次重新连接。”

“好吧,”我说,“如果我们无法重新启动应用程序并且数据对我们来说并不重要,那么我们可以简单地通过文件描述符清除这个打开的文件,即使我们在 ls 命令中没有看到它在文件系统上。”

面试官很高兴,但我不高兴。

然后我想,为什么测试我知识的人不深入挖掘呢? 但如果数据毕竟很重要怎么办? 如果我们无法重新启动进程,并且该进程写入没有可用空间的分区上的文件系统怎么办? 如果我们不仅不能丢失已写入的数据,而且不能丢失此进程写入或尝试写入的数据怎么办?

小艇

在我职业生涯的早期,我尝试创建一个需要存储用户信息的小型应用程序。 然后我想,如何将用户与其数据相匹配。 比如我有伊万诺维奇伊万诺维奇,他有一些信息,但是我怎样才能和他们交朋友呢? 我可以直接指出,这只名叫“Tuzik”的狗就是这个伊万的。 但是,如果他改了名字,不再是伊万,而是变成了奥利亚(Olya)呢? 那么我们的奥利亚·伊万诺夫娜·伊万诺娃将不再有狗,而我们的图兹克仍然属于不存在的伊万。 为每个用户提供唯一标识符 (ID) 的数据库帮助解决了这个问题,我的 Tuzik 与这个 ID 绑定在一起,实际上,它只是一个序列号。 因此,王牌的所有者的 ID 号为 2,并且在某个时间点 Ivan 处于该 ID 下,然后 Olya 也处于同一 ID 下。 人类和畜牧业问题得到切实解决。

文件描述符

该文件以及处理该文件的程序的问题与我们的狗和人的问题大致相同。 假设我打开了一个名为 ivan.txt 的文件,并开始向其中写入单词 tuzik,但只成功写入了文件中的第一个字母“t”,并且该文件被某人重命名,例如,重命名为 olya.txt。 但文件还是一样,我仍然想在里面记录我的王牌。 每次系统调用打开文件时 打开 在任何编程语言中,我都会收到一个指向文件的唯一 ID,该 ID 就是文件描述符。 接下来谁对这个文件做什么根本不重要,它可以被删除,可以重命名,可以更改所有者,或者可以剥夺读写权限,我仍然可以访问因为在打开文件时,我有权读取和/或写入它,并且我设法开始使用它,这意味着我必须继续这样做。

在Linux中,libc库为每个正在运行的应用程序(进程)打开3个描述符文件,编号为0,1,2、XNUMX、XNUMX。 更多信息可以在链接中找到 男人工作室 и 人标准输出

  • 文件描述符 0 称为 STDIN,与应用程序输入相关联
  • 文件描述符1称为STDOUT,应用程序使用它来输出数据,例如打印命令
  • 文件描述符 2 称为 STDERR,应用程序使用它来输出错误消息。

如果在您的程序中您打开任何文件进行读取或写入,那么您很可能会获得第一个空闲 ID,并且它将是数字 3。

如果您知道任何进程的 PID,则可以查看该进程的文件描述符列表。

例如,让我们打开bash控制台并查看我们进程的PID

[user@localhost ]$ echo $$
15771

在第二个控制台中运行

[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

出于本文的目的,您可以安全地忽略文件描述符编号 255;它是由 bash 本身而不是链接库根据需要打开的。

现在所有 3 个描述符文件都与伪终端设备关联 /开发/分,但我们仍然可以操纵它们,例如,在第二个控制台中运行它们

[user@localhost ]$ echo "hello world" > /proc/15771/fd/0

在第一个控制台中我们将看到

[user@localhost ]$ hello world

重定向和管道

您可以在任何进程中轻松覆盖这 3 个描述符文件,包括在 bash 中,例如通过连接两个进程的管道,请参阅

[user@localhost ]$ cat /dev/zero | sleep 10000

您可以自己运行此命令 strace-f 看看里面发生了什么,但我会简单地告诉你。

我们的 PID 为 15771 的父 bash 进程解析我们的命令并准确地理解我们想要运行多少个命令,在我们的例子中有两个命令:cat 和 sleep。 Bash 知道它需要创建两个子进程,并将它们合并到一个管道中。 bash 总共需要 2 个子进程和 XNUMX 个管道。

Bash 在创建子进程之前运行系统调用 并在临时管道缓冲区上接收新的文件描述符,但该缓冲区尚未连接我们的两个子进程。

对于父进程,看起来已经有一个管道,但还没有子进程:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

然后使用系统调用 克隆 bash 创建两个子进程,我们的三个进程将如下所示:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
PID    command
9004  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21
PID    command
9005  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21

不要忘记克隆会克隆进程以及所有文件描述符,因此它们在父进程和子进程中将是相同的。 PID 15771 的父进程的工作是监视子进程,因此它只是等待子进程的响应。

因此,它不需要管道,并且它关闭编号为3和4的文件描述符。

在第一个 PID 为 9004 的 bash 子进程中,系统调用 重复2,将 STDOUT 文件描述符编号 1 更改为指向管道的文件描述符,在我们的示例中为编号 3。因此,PID 为 9004 的第一个子进程写入 STDOUT 的所有内容都将自动结束在管道缓冲区中。

在 PID 9005 的第二个子进程中,bash 使用 dup2 更改文件描述符 STDIN 编号 0。现在 PID 9005 的第二个 bash 将读取的所有内容都将从管道中读取。

此后,子进程中编号为 3 和 4 的文件描述符也会被关闭,因为它们不再被使用。

我故意忽略文件描述符 255;它由 bash 本身用于内部目的,并且也会在子进程中关闭。

接下来,在 PID 为 9004 的第一个子进程中,bash 开始使用系统调用 EXEC 我们在命令行上指定的可执行文件,在我们的例子中是 /usr/bin/cat。

在 PID 为 9005 的第二个子进程中,bash 运行我们指定的第二个可执行文件,在我们的例子中是 /usr/bin/sleep。

exec 系统调用不会关闭文件句柄,除非在进行 open 调用时使用 O_CLOEXEC 标志打开它们。 在我们的例子中,启动可执行文件后,所有当前文件描述符将被保存。

检查控制台:

[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Oct  7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Oct  7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lr-x------ 1 user user 64 Oct  7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
[user@localhost ]$ ps -up 9004
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9004  0.0  0.0 107972   620 pts/21   S+   15:57   0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9005  0.0  0.0 107952   360 pts/21   S+   15:57   0:00 sleep 10000

正如您所看到的,我们的管道的唯一编号在两个进程中是相同的。 因此,我们在具有相同父进程的两个不同进程之间建立了连接。

对于那些不熟悉 bash 使用的系统调用的人,我强烈建议通过 strace 运行命令并查看内部发生的情况,例如如下所示:

strace -s 1024 -f bash -c "ls | grep hello"

让我们回到磁盘空间不足并尝试在不重新启动进程的情况下保存数据的问题。 让我们编写一个小程序,每秒向磁盘写入大约 1 MB 的数据。 此外,如果由于某种原因我们无法将数据写入磁盘,我们将简单地忽略这一点并尝试在一秒钟内再次写入数据。 在我使用 Python 的示例中,您可以使用任何其他编程语言。

[user@localhost ]$ cat openforwrite.py 
import datetime
import time

mystr="a"*1024*1024+"n"
with open("123.txt", "w") as f:
    while True:
        try:
            f.write(str(datetime.datetime.now()))
            f.write(mystr)
            f.flush()
            time.sleep(1)
        except:
            pass

让我们运行程序并查看文件描述符

[user@localhost ]$ python openforwrite.py &
[1] 3762
[user@localhost ]$ ps axuf | grep [o]penforwrite
user  3762  0.0  0.0 128600  5744 pts/22   S+   16:28   0:00  |   _ python openforwrite.py
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt

正如您所看到的,我们有 3 个标准文件描述符和另外一个打开的文件描述符。 让我们检查一下文件大小:

[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 117M Oct  7 16:30 123.txt

正在写入数据,我们尝试更改文件的权限:

[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 168M Oct  7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 172M Oct  7 16:31 123.txt

我们看到数据仍在写入,尽管我们的用户没有写入文件的权限。 让我们尝试删除它:

[user@localhost ]$ sudo rm 123.txt 
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory

数据写在哪里? 它们到底有没有被写下来? 我们检查:

[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt (deleted)

是的,我们的文件描述符仍然存在,我们可以像处理旧文件一样对待这个文件描述符,我们可以读取、清除和复制它。

我们来看看文件大小:

[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5   19923457   2621522 /home/user/123.txt

文件大小为19923457。让我们尝试清除该文件:

[user@localhost ]$ truncate -s 0 /proc/31083/fd/3
[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5  136318390   2621522 /home/user/123.txt

正如您所看到的,文件大小只会增加,而我们的主干不起作用。 我们看一下系统调用文档 打开。 如果我们在打开文件时使用 O_APPEND 标志,则每次写入时,操作系统都会检查文件大小并将数据写入文件的最末尾,并以原子方式执行此操作。 这允许多个线程或进程写入同一个文件。 但在我们的代码中我们不使用这个标志。 仅当我们打开文件进行额外写入时,我们才能在 trunk 之后看到不同的文件大小,这意味着在我们的代码中

with open("123.txt", "w") as f:

我们必须把

with open("123.txt", "a") as f:

使用“w”标志检查

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

并带有“a”标志

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

对已经运行的进程进行编程

程序员在创建和测试程序时通常会使用调试器(例如 GDB)或应用程序中的各种级别的日志记录。 Linux提供了实际编写和更改已经运行的程序的能力,例如更改变量的值、设置断点等。

回到最初的问题,即磁盘空间不足,无法写入文件,让我们尝试模拟该问题。

让我们为分区创建一个文件,我们将其作为单独的磁盘安装:

[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

让我们创建一个文件系统:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd is not a block special device.
Proceed anyway? (y,n) y
...
Writing superblocks and filesystem accounting information: done
[user@localhost ~]$

挂载文件系统:

[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] password for user: 
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  172K  7.9M   3% /mnt

我们与所有者一起创建一个目录:

[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs

让我们在程序中打开仅用于写入的文件:

with open("/mnt/logs/123.txt", "w") as f:

我们启动

[user@localhost ]$ python openforwrite.py 

我们等几秒钟

[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

因此,我们遇到了本文开头描述的问题。 可用空间 0,已占用 100%。

我们记得根据任务的条件,我们正在努力记录非常重要且不能丢失的数据。 同时,我们需要修复服务而不重新启动进程。

假设我们仍然有磁盘空间,但在不同的分区中,例如在 /home 中。

让我们尝试“动态重新编程”我们的代码。

让我们看一下进程的 PID,它已经耗尽了所有磁盘空间:

[user@localhost ~]$ ps axuf | grep [o]penfor
user 10078 27.2  0.0 128600  5744 pts/22   R+   11:06   0:02  |   _ python openforwrite.py

通过 gdb 连接到进程

[user@localhost ~]$ gdb -p 10078
...
(gdb) 

让我们看看打开的文件描述符:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt

我们查看有关我们感兴趣的文件描述符号 3 的信息

(gdb) shell cat /proc/10078/fdinfo/3
pos:    8189952
flags:  0100001
mnt_id: 482

请记住 Python 进行的系统调用(请参阅上面我们运行 strace 并找到 open 调用的位置),在处理打开文件的代码时,我们自己代表我们的进程执行相同的操作,但我们需要 O_WRONLY|O_CREAT| O_TRUNC 位替换为数值。 为此,请打开内核源代码,例如 这里 看看哪些标志负责什么

#定义O_WRONLY 00000001
#定义O_CREAT 00000100
#定义O_TRUNC 00001000

我们将所有值合并为一个,我们得到00001101

我们从 gdb 运行调用

(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4

所以我们得到了一个新的文件描述符,编号为 4,并且在另一个分区上有一个新的打开文件,我们检查:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

我们还记得管道的示例 - bash 如何更改文件描述符,并且我们已经学习了 dup2 系统调用。

我们尝试用一个文件描述符替换另一个文件描述符

(gdb) call dup2(4,3)
$2 = 3

检查:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

我们关闭文件描述符 4,因为我们不需要它:

(gdb) call close (4)
$1 = 0

并退出gdb

(gdb) quit
A debugging session is active.

    Inferior 1 [process 10078] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 10078

检查新文件:

[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Oct  8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Oct  8 11:18 /home/user/123.txt

正如你所看到的,数据被写入了一个新文件,让我们检查一下旧文件:

[user@localhost ~]$ ls -lah /mnt/logs/123.txt 
-rw-rw-r-- 1 user user 7.9M Oct  8 11:08 /mnt/logs/123.txt

不会丢失任何数据,应用程序可以正常运行,日志会写入新位置。

让我们把任务变得复杂一点

假设数据对我们很重要,但我们的任何分区都没有磁盘空间,并且无法连接磁盘。

我们能做的就是将数据重定向到某个地方,例如管道,然后通过某些程序(例如 netcat)将数据从管道重定向到网络。
我们可以使用 mkfifo 命令创建命名管道。 即使文件系统上没有可用空间,它也会在文件系统上创建一个伪文件。

重新启动应用程序并检查:

[user@localhost ]$ python openforwrite.py 
[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 72.9  0.0 128600  5744 pts/22   R+   11:27   0:20  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

没有磁盘空间,但我们成功地创建了一个命名管道:

[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe 
prw-rw-r-- 1 user user 0 Oct  8 11:28 /mnt/logs/megapipe

现在我们需要以某种方式将进入该管道的所有数据通过网络包装到另一台服务器;相同的 netcat 适合于此。

在服务器remote-server.example.com上我们运行

[user@localhost ~]$ nc -l 7777 > 123.txt 

在有问题的服务器上,我们在单独的终端中启动

[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

现在,最终进入管道的所有数据都会自动发送到 netcat 中的 stdin,后者会将其发送到端口 7777 上的网络。

我们所要做的就是开始将数据写入这个命名管道。

我们已经运行了该应用程序:

[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 99.8  0.0 128600  5744 pts/22   R+   11:27 169:27  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt

在所有标志中,我们只需要 O_WRONLY,因为文件已经存在并且我们不需要清除它

[user@localhost ~]$ gdb -p 5946
...
(gdb) call open("/mnt/logs/megapipe", 00000001,0666)
$1 = 4
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call dup2(4,3)
$2 = 3
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call close(4)
$3 = 0
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
A debugging session is active.

    Inferior 1 [process 5946] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 5946

检查远程服务器remote-server.example.com

[user@localhost ~]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 38M Oct  8 14:21 123.txt

数据来了,我们检查问题服务器

[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Oct  8 11:28 .
drwxr-xr-x 4 root     root     1.0K Oct  8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Oct  8 14:17 123.txt
prw-rw-r-- 1 user user    0 Oct  8 14:22 megapipe

数据保存,问题解决。

我借此机会向德吉罗的同事们问好。
收听 T 电台播客。

一切都好。

作为家庭作业,我建议您考虑一下如果运行以下命令,进程文件描述符 cat 和 sleep 中将包含什么:

[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000

来源: habr.com

添加评论