2009-04-06

Ficheiros, parte II

Na primeira parte ficou por fazer a função copy:

int copy(int fdin, int fdout);

Esta função lê todos os bytes do ficheiro indicado pelo primeiro descritor e escreve-os no segundo.

Leitura

A função read necessita de um parâmetro que é o número de bytes para ler e outro que é um buffer para guardar esses bytes. Vamos começar por declarar esse buffer:

#define BUFFER_SIZE 256

int copy(int fdin, int fdout)
{
char buffer[BUFFER_SIZE];

// ...
}

Agora vamos ler BUFFER_SIZE bytes de cada vez.

    int bytes_read = read(fdin, buffer, BUFFER_SIZE);
while(bytes_read != 0)
{
if(bytes_read == -1)
return -1;

// escrever...

bytes_read = read(fdin, buffer, BUFFER_SIZE);
}

Neste ciclo lemos um buffer de cada vez, até ao fim do ficheiro (quando read devolver zero). Reparem que fazemos a verificação do valor de retorno e, caso seja indique um error (-1) a função devolve um erro. Esse erro vai ser tratado na função main com a função guard que já definimos.

Escrita

Agora vamos escrever os bytes que lemos no ficheiro de saída. Lembrem-se que nem sempre o número de bytes lidos é o que nós pedimos. O mesmo se passa com o número de bytes escritos. Temos de garantir que escrevemos todos os bytes que lemos. Precisamos de um ciclo que chama a função write até todos os bytes lidos serem escritos:

        int bytes_written = 0;
do {
bytes_written += write(fdout, ???, ???);
} while(bytes_written < bytes)

O que temos de passar nos segundo e terceiro parâmetros? Não podemos passar BUFFER_SIZE no terceiro porque podemos não ter lido esses bytes todos. Deve ser então bytes_read.

Mas assim surge outro problema. Se tivermos lido 100 bytes (bytes_read = 100) e escrevermos 40 (bytes_written = 40) na primeira iteração, na segunda temos de escrever 60 e não 100. Temos então de escrever bytes_read – bytes_written, isto é, o número de bytes lidos que ainda não foram escritos.

E quanto ao segundo parâmetro, o buffer? Não podemos passar sempre buffer. Vejam o cenário do parágrafo anterior. Se usarmos buffer no segundo parâmetro estariamos a escrever sempre o início do buffer. Não é isso que queremos. Na segunda iteração queremos escrever os bytes que estão a partir do último que foi escrito. Ou seja, os bytes a partir de buf + bytes_written.

Ficamos então com isto:

#define BUFFER_SIZE 256

int copy(int fdin, int fdout)
{
char buffer[BUFFER_SIZE];

int bytes_read = read(fdin, buffer, BUFFER_SIZE);
while(bytes_read != 0)
{
int bytes_written = 0;

if(bytes_read == -1)
return -1;

do {
bytes_written += write(fdout,
buf + bytes_written,
bytes_read - bytes_written);
} while(bytes_written < bytes)
bytes_read = read(fdin, buffer, BUFFER_SIZE);
}
}

Teste

Agora podemos compilar o nosso cat e testar. Para testar podemos usar dois ficheiros que temos à mão: o Makefile e o código fonte do mycat:

$ make
$ ./mycat Makefile mycat.c

Como o output não cabe todo no ecrã podemos redirecioná-lo para o less para confirmarmos que esta correcto, assim:

$ ./mycat Makefile mycat.c | less

Mas podemos fazer ainda melhor: comparar com o output do verdadeiro cat, usando o diff:

$ ./mycat Makefile mycat.c >mycat.out
$ cat Makefile mycat.c >cat.out
$ diff mycat.out cat.out

Não há diferenças entre os outputs do mycat e do cat. Isto significa que cumprímos os objectivos deste guião.

Como sempre, podem fazer download do código aqui. Podem correr o teste com o diff assim:

$ make test

4 commentários:

Anónimo disse...

Boas,

Mais uma vez obrigado por te dares a este trablaho mas tenho umas dúvidas e observações. Primeiro as observações...

1) O código disponibilizado aqui contém erros, tens duas variáveis (buff e bytes) que não estão declaradas em lado nenhum. Estas variáveis correspondem a buffer e bytes_read respectivamente. O código para download já não contém estes erros.

2) Reparei no código para download (não o compilei a testar) que fazes include do stdlib.h. Na minha máquina esse include não serviu de nada, não preciso dele, tive antes de fazer include ao unistd.h. Consegues explicar-me porquê?

Agora a dúvida...

3) Porque não usar a macro BUFSIZ incluída no stdio.h em vez de de definirmos nós? Talvez devido ao tamanho? Vi que no teu código usas um buffer de 32bytes, isto talvez seja bom para ficheiros pequenos, mas a ficheiros grandes, vamos fazer demasiadas chamadas ao sistema não? Eu usei o BUFSIZ e funcionou bem e reparei que o valor dele na minha máquina é 8192, será de mais? Talvez para ficheiros pequenos, mas para grandes se calhar não?

Pronto, é só...

Martinho Fernandes disse...

Quanto aos erros no post, deve-se ao facto de eu escrever os posts antes do código que compilo. Há sempre um ou outro erro menor que passa...

Uso a stdlib.h por causa da função exit que uso nos guardas.

É porreiro referires a macro BUFSIZ. Não me lembrava que isso existia. O melhor é mesmo usar isso.
O 32 não é uma boa escolha, porque é muito pequeno e como disseste, obriga a mais sistem calls, o que é mau para a performance. No entanto é mais fácil de fazer debug a buffers de 32 bytes do que buffers de 8k.

Anónimo disse...

Boas companheiro, na semana passado levei a lavagem da minha vida quando o pseudo-prof tentou explicar o guião dos processos.

Será que nos podes ajudar, a nós pequenos estudantes, seguidores dos teus conhecimentos essenciais e cruciais para o nosso desenvolvimento académico, a resolver o que é pedido nesse guião?

Que fique desde já implicito que, e falando apenas por mim, não estou a pedir isto para me aproveitar de ti mas apenas para aprender algo mais do que aprendo nas aulas. Acredita que tens conseguido fazer com que eu perceba o que é pedido ao contrário dos nossos pseudo-professores

Obrigado por tudo. Acredita que nós estámos agradecidos;)

Um abraço[[]]

Martinho Fernandes disse...

A segunda fase do trabalho de LI4 não nos correu como estavamos à espera e estes últimos dias foram bastante hécticos. Juntando o trabalho de CG fiquei com pouquíssimo tempo para isto. Eu não vou às aulas de SO, por isso também não posso "aproveitar" aquelas duas horitas das aulas práticas para fazer isto.

Mas agora os prazos terminaram (cumpridos à justa) e as coisas já acalmaram um pouco. Vou saltar o segundo exercício dos ficheiros (nl) e passar logo para os processos, e assim já não vou atrasado em relação às aulas.

Enviar um comentário