Incomodação no Linux: / dev / stdin não funciona com soquetes

1

O Linux tem essa peculiaridade irritante que /dev/stdin não funciona com sockets - é codificado para retornar ENXIO. Tente isto:

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

Esse é um comando perfeitamente razoável que você esperaria que funcionasse, e basicamente faz parte de todos os sistemas, exceto do Linux. (Estou usando cat como um exemplo geral de qualquer ferramenta que abre um nome de arquivo como a única maneira de você especificar um fd específico para usar.)

O kernel do Linux é explicitamente escrito para proibir o uso sensato de /dev/stdin desta forma - veja link .

Se você precisar apenas de capacidade unidirecional, poderá armazenar os dados em um processo separado:

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

É um desperdício, e pior, é inútil se thingy estiver lendo e escrevendo para o mesmo fd, porque os pipes são unidirecionais no Linux.

O que devemos fazer? /dev/stdin simplesmente não pode ser usado no Linux para construir pipelines com pipes bidirecionais, até onde eu sei, porque soquetes são o único mecanismo subjacente no Linux que gera um fluxo bidirecional com um único fd para ler e escrever (ao contrário de um par de tubos).

    
por Nicholas Wilson 05.11.2013 / 16:38

1 resposta

4

Você sempre pode usar um truque LD_PRELOAD para o Linux imitar o modo 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);
  }
}

(talvez você precise envolver mais como freopen() ).

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

E então:

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

Note que o Linux e o BSD são fundamentalmente diferentes. Não é tanto que você não possa abrir /dev/fd/0 quando é um socket, mas /dev/fd/x é um link simbólico para o arquivo que está aberto no fd x. Você não pode fazer open() em uma tomada, isso não faria sentido. open("/dev/fd/x") não é de todo um dup(x) como no BSD. Parece que quando o arquivo é um pipe, mas não é mesmo assim, ele é na verdade o mesmo que abrir um pipe nomeado (você pode até mesmo abri-lo no outro modo (read vs write) para obter a outra extremidade do arquivo. o tubo).

Ambas as abordagens têm seus prós e contras. Parece-me que a sua aplicação deve ter números fd como argumentos, não use /dev/fd/x , que é um hack em primeiro lugar de qualquer maneira e, por exemplo, faria com que você desperdiçasse fds.

    
por 06.11.2013 / 10:23