2009-06-27

Redireccionar as standard streams

Para adicionar à shell a possibilidade de redireccionar o standard input e o standard output de e para ficheiros temos de usar a system call dup2:

$ man dup2

Esta system call faz com que o descritor passado no segundo parâmetro aponte para o mesmo ficheiro que o primeiro. Se o segundo descritor já estava aberto, este é fechado primeiro. Depois de chamar esta função tanto faz usar o primeiro como o segundo descritor.

Para que é que isto nos serve? O programa que vamos lançar com exec() vai ler e escrever sempre nos mesmos descritores: 0 (stdin) e 1 (stdout). Para fazer com que ele escreva no ficheiro que queremos basta fazer com que estes descritores apontem não para a consola (por omissão) mas para esse ficheiro. Tendo um descritor aberto para o ficheiro chamado fd basta efectuar uma destas duas chamadas antes de lançar o exec():

// Redireccionar standard input
dup2(fd, STDIN_FILENO);

// Redireccionar standard output
dup2(fd, STDOUT_FILENO);
Cada uma destas duas instruções fecha a respectiva standard stream e faz com que o respectivo descritor aponte para o nosso ficheiro. Aqui eu usei as constante STDIN_FILENO e STDOUT_FILENO (que estão definidas em unistd.h) em vez dos números 0 e 1. Eu prefiro usar as constantes porque assim nunca me esqueço qual delas é o 0 e qual é o 1.
Voltando ao dup2. Ainda nos falta abrir o ficheiro. Vamos utilizar a já conhecida open para abrir o ficheiro para leitura (para redireccionar o stdin):
int fd = open(filename, O_RDONLY);
E creat para criar e abrir um ficheiro para escrita (para redireccionar o stdout):
int fd = creat(filename, 0644);
O segundo parâmetro é o modo, a.k.a. as permissões, do ficheiro. Pode ser especificado usando uma série de constantes que são explicadas no manual, ou directamente em octal. Se já usaram o comando chmod devem reconhecer esta notação. Não se esqueçam é de começar por 0, para dizer que é em octal[1]. 0644 coloca estas permissões: rw-r--r--, porque 0644 em binário é 110 100 100. Para o nosso caso necessitamos de permissões de escrita, por isso serve. Mesmo que não percebam nada de permissões em Unix, não faz mal. 0644 serve 99% das vezes. Não vamos perder mais tempo com curiosidades. Voltemos à shell.
Depois de abrir o ficheiro e chamar dup2 ficamos com dois descritores para o mesmo ficheiro: o standard input/output (0/1) e fd. O programa que vamos lançar com exec() não sabe sequer da existência de fd. Só conhece as streams standard (0, 1 e 2) e os seus próprios descritores. Quando terminar este só vai fechar os descritores standard e os que ele próprio abriu. É então da nossa responsabilidade fechar o descritor fd. Como este não vai ser mais necessário (o programa que vamos lançar vai usar os descritores standard) podemos fechá-lo já. Notem que assim não fechamos o ficheiro. Só fechamos o descritor fd. O ficheiro ficará aberto enquanto houver descritores abertos que apontem para ele. Neste caso existe mais um: o standard input/output que nós modificamos com dup2. Por isso o ficheiro ficará aberto para leitura ou escrita, respectivamente, até o programa decidir fechar o stdin/stdout.
Com isto podemos escrever as seguintes funções para efectuar redireccionamentos:
int redir_in(const char* file)
{
int fd = open(file, O_RDONLY);
if(dup2(fd, STDIN_FILENO) == -1)
return -1;
if(close(fd) == -1)
return -1;
}

int redir_out(const char* file)
{
int fd = creat(file, 0644);
if(dup2(fd, STDOUT_FILENO) == -1)
return -1;
if(close(fd) == -1)
return -1;
}

Agora na função run temos de fazer os redireccionamentos:

int run(char** args, int background, const char* command, char* in, char* out)
{
pid_t pid = fork();
if(pid == 0)
{
// Efectuar redireccionamento quando pedido
if(in)
redir_in(in);
if(out)
redir_out(out);

execvp(args[0], args);
perror(args[0]);
exit(1);
}
else
{
// ...
}
}

Já só falta alterar a função getArgs para ter em conta os redireccionamentos. Infelizmente a wordexp não aceita strings com |, &, ;, <, >, (, ), { ou }. Por isso eu usei ] e [ para os nossos redireccionamentos, para não perder muito tempo com parsing, já que a parte importante do exercício era usar dup2 correctamente.

getArgs agora vai receber como parâmetro apontadores para strings para “devolver” os nomes dos ficheiros para redireccionar. Se não houver redireccionamento essas strings ficar com NULL.

char** getArgs(const char* s, char** in, char** out);

E na função main:

char *in, *out;
in = out = NULL;
while(/*...*/)
{
char** args = getArgs(command, &in, &out);

// ...

run(args, background, command, in, out);

// ...
}

Se tiverem dificuldades a alterar getArgs, façam download do código e vejam como fiz.


[1] Era por isso que a camisola que o prof mostrou na primeira aula tinha ”Tirei 024 a Sistemas Operativos” escrito. Porque 24(8) = 20(10) e em C qualquer constante númerica que comece por 0 é octal:

#include <stdio.h>

void main()
{
if(024 == 24)
printf("They're equal!\n");
else
printf("WTF? 024 != 24?\n");

if(024 == 20)
printf("024 == 20! It's octal!\n");

printf("%d in octal is %o!\n", 20, 20);
}

0 commentários:

Enviar um comentário