O primeiro exercício do terceiro guião diz o seguinte: Implemente em C um programa com a funcionalidade do cat. O objectivo é fazer isso, mas sem usar a stdio.h (printf, scanf, etc). No lugar dessas funções temos de usar as chamadas ao sistema que estão no guião.
O que é o cat?
Vamos começar então por perceber exactamente o que temos de fazer:
$ whatis cat cat - concatenate files and print on the standard outputman cat
O cat concatena os ficheiros passados como argumento e imprime-os no standard output. Se não tiver argumentos, imprime o que ler do standard input. Simples.
Estrutura geral
Vamos ter então um ciclo que imprime os ficheiros passados como argumento um a um.
int main(int argc, char** argv)
{
int i;
if(argc == 1)
{
// ler do standard input para o standard output
}
// Começar em 1 porque argv[0] é o nome do executável ("cat")
for(i = 1; i < argc; i++)
{
// ler do ficheiro argv[i] para o standard output
}
return 0;
}
Uma visita ao manual
Vamos agora ver como funcionam as system calls que temos à nossa disposição.
$ man open
Resumidamente, abre um ficheiro e devolve um descritor para esse ficheiro. Um descritor é um número inteiro não negativo que representa um ficheiro. Para cada processo o kernel mantém uma lista de ficheiros abertos. Podem ver o descritor como o índice de um ficheiro nessa lista.
Os descritores 0, 1 e 2 são especiais e representam, respectivamente, o standard input, o standard output e o standard error.
O primeiro parâmetro é o caminho do ficheiro. O segundo, flags, dita o modo de acesso. Deve obrigatoriamente incluir uma destas três flags:
- O_RDONLY: para abrir só para leitura;
- O_WRONLY: para abrir só para escrita;
- O_RDWR: para abrir para leitura e escrita.
Há outras flags opcionais, das quais as mais relevantes são:
- O_APPEND: serve para acrescentar ao ficheiro;
- O_CREAT: cria o ficheiro se este não existir;
Para especificar mais do que uma flag usa-se o operador | (bitwise or), assim:
fd = open("file", O_WRONLY | O_APPEND);
Quando se usa O_CREAT para criar um ficheiro deve usar um terceiro parâmetro que diz o modo (aka, as permissões):
fd = open("file", O_WRONLY | O_CREAT, 0700);
O modo é o mesmo que se usa com o comando chmod, em octal (daí o 0 à esquerda). Se preferirem existem algumas constantes predefinidas para vários modos. Essas constantes são listadas na página do manual.
$ man close
Fecha um ficheiro dado o seu descritor. Devolve zero se tiver sucesso.
$ man read
Ooops... Página errada. Este read não é o que estamos à procura. O que nós queremos está na secção 2: system calls.
$ man 2 read
Agora sim. read lê uma quantidade de bytes de um ficheiro para um buffer e devolve o número de bytes lidos. Quando o ficheiro chegar ao fim, devolve 0.
read pode ler menos bytes do que foram pedidos porque podemos estar a ler do teclado, de um pipe, de um socket, etc. Se não souberem o que são pipes ou sockets, não faz mal. Basta que saibam que read nem sempre lê tantos bytes como pedimos.
$ man 2 write
Escreve uma quantidade de bytes de um buffer para um ficheiro e devolve o número de bytes escritos.
Tal como read, write também pode escrever menos bytes do que foram pedidos.
Todas estas system calls devolvem –1 em caso de erro.
Acabar a main
Para ler de um ficheiro para outro, temos de ler bytes de um com read e escrevê-los com write no outro. Vamos ter um ciclo até ao fim do ficheiro (quando read devolve 0) a fazer isto. Como vamos fazer esta operação mais que uma vez vamos escrever uma função para isso:
int copy(int fdin, int fdout);
Com isto já podemos acabar a função main:
int main(int argc, char** argv)
{
int i;
if(argc == 1)
{
if(copy(0, 1) == -1)
{
perror("copy");
exit(1);
}
}
// Começar em 1 porque argv[0] é o nome do executável ("cat")
for(i = 1; i < argc; i++)
{
int fd = open(argv[i], O_RDONLY);
if(fd == -1)
{
perror("open");
exit(1);
}
if(copy(fd, 1) == -1)
{
perror("copy");
exit(1);
}
if(close(fd) == -1)
{
perror("close");
exit(1);
}
}
return 0;
}
Porque é que usei tantos ifs? Deve-se verificar sempre o resultado das system calls. A função perror imprime a mensagem do último erro com o prefixo que passamos como parâmetro.
Mas estes ifs todos tornam o código difícil de ler. Vamos capturar este padrão numa função e tornar isto mais DRY:
void guard(int result, const char* message)
{
if(result == -1)
{
perror(message);
exit(1);
}
}
int main(int argc, char** argv)
{
int i;
if(argc == 1)
guard(copy(0, 1),"copy");
// Começar em 1 porque argv[0] é o nome do executável ("cat")
for(i = 1; i < argc; i++)
{
int fd;
guard(fd = open(argv[i], O_RDONLY), "open");
guard(copy(fd, 1), "copy");
guard(close(fd), "close");
}
return 0;
}
Falta agora apenas a função copy.
3 commentários:
Para quem não tem a secção 2 do manual a funcionar como eu, executem o seguinte comando na consola: sudo apt-get install manpages-dev ou então procurem no vosso gestor de aplicações (no meu caso synaptic) manpages-dev.
Isto é que é um blog à homem. lol Os meus parabéns.
Uma pequena correcção:
* O_RDONLY: para abrir só para leitura;
* O_RDWR: para abrir só para escrita;
* O_WRONLY: para abrir para leitura e escrita.
Tens a 2ª e 3ª linha trocadas. :)
Não estão trocadas...
Enviar um comentário