Linux 麻烦:/dev/stdin 不适用于套接字

Linux 麻烦:/dev/stdin 不适用于套接字

Linux 有一个令人讨厌的特性,它/dev/stdin不适用于套接字 - 它被硬编码为返回 ENXIO。尝试这个:

socat TCP-OPEN:localhost:1234 EXEC:cat\ /dev/stdin,nofork

这是一个完全合理的命令,您希望它能工作,而且基本上在除 Linux 之外的所有系统上都可以工作。 (我使用cat任何打开文件名的工具作为一般示例,作为您指定要使用的特定 fd 的唯一方法。)

Linux 内核被明确编写为禁止/dev/stdin以这种方式合理使用 — 请参阅http://marc.info/?l=ast-users&m=120978595414993

如果只需要单向能力,可以在单独的进程中缓冲数据:

socat TCP-OPEN:localhost:1234 SYSTEM:'cat | thingy /dev/stdin'

这是浪费的,更糟糕​​的是,如果thingy要读取和写入同一个 fd,则毫无用处,因为 Linux 中的管道是单向的。

我们要做什么?/dev/stdin据我所知,根本不能在 Linux 上使用双向管道构建管道,因为套接字是 Linux 上唯一产生具有单个 fd 的双向流以供读取和写入的底层机制(与一对管道)。

答案1

你总是可以使用一个LD_PRELOAD技巧来让 Linux 模仿 BSD 的方式。fddup.c:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int whichfd(const char *pathname)
{
  unsigned int fd;
  if (sscanf(pathname, "/dev/fd/%u", &fd) == 1)
    return fd;
  else
    return -1;
}

int open(const char *pathname, int flags, mode_t mode)
{
  static int (*orig)(const char *, int, mode_t) = 0;
  int fd = whichfd(pathname);
  if (fd >= 0)
    return dup(fd);
  else {
    if (!orig)
      orig = dlsym(RTLD_NEXT,"open");
    if (!orig) abort();
    return orig(pathname, flags, mode);
  }
}

FILE *fopen(const char *path, const char *mode)
{
  static FILE *(*orig)(const char *, const char *) = 0;
  int fd = whichfd(path);
  if (fd >= 0)
    return fdopen(dup(fd), mode);
  else {
    if (!orig)
      orig = dlsym(RTLD_NEXT,"fopen");
    if (!orig) abort();
    return orig(path, mode);
  }
}

(你可能需要包裹得更像freopen())。

gcc -Wall -fPIC -shared -o fddup.so fddup.c -ldl

进而:

socat TCP:localhost:22 'EXEC:env LD_PRELOAD=./ddup.so cat /dev/fd/0,nofork'

请注意,Linux 和 BSD 有着根本的不同。/dev/fd/0当它是一个套接字时,并不是说你无法打开它,而是它/dev/fd/x是一个在 fd x 上打开的文件的符号链接。你不能open()在套接字上做,那没有意义。与 BSDopen("/dev/fd/x")完全不同。dup(x)当文件是管道时感觉就像这样,但事实并非如此,它实际上与打开命名管道相同(您甚至可以以其他模式(读与写)打开它以获得另一端管)。

两种方法都有其优点和缺点。在我看来,你的应用程序应该将 fd 数字作为参数,而不是使用/dev/fd/x它,这首先是一种黑客行为,例如会导致你浪费 fd。

相关内容