#+title: Curso Básico da Linguagem C
#+subtitle: Aula 14: Abertura de arquivos para leitura
#+author: Blau Araujo
#+startup: show2levels
#+options: toc:3

*  Aula 14: Abertura de arquivos para leitura

[[https://www.youtube.com/watch?v=uh3UdYyzXRM][Vídeo desta aula]]

Em sistemas /Unix-like/, um /arquivo/ é qualquer abstração de recursos de entrada
e saída representada como /fluxos de dados/ (/streams/) acessados por meio de
operações padronizadas, independentemente de seu conteúdo ou finalidade.

Neste sentido, existem sete tipos de arquivos:

- Diretórios
- Arquivos regulares (ou comuns)
- Ligações simbólicas
- Dispositivos especiais caractere
- Dispositivos especiais de bloco
- Arquivos de pipe (ou FIFO)
- Sockets

Quando queremos acessar um arquivo, seja de que tipo for, nós precisamos que
o sistema disponibilize vários recursos, como:

- Um /descritor de arquivo/ (/file descriptor/): um número inteiro que identifica
  de forma única o arquivo aberto no contexto do processo.
- Uma tabela de descritores do processo: uma estrutura que associa os descritores
  de arquivos a entradas na tabela global de arquivos abertos.
- A tabela global de arquivos abertos (/file table/): que armazena informações
  como a posição atual de leitura/gravação e as flags de acesso.
- A tabela de /inodes/: que contém os metadados do arquivo, como permissões,
  o "dono" do arquivo, o tipo do arquivo e o ponteiro para os dados no disco.
- Os /buffers/ do sistema: usados para otimizar acesso ao conteúdo do arquivo
  acumulando dados temporariamente na memória.

Na linguagem C, tudo isso é abstraído como o tipo =FILE *=, que é um ponteiro para
a estrutura de dados (uma /struct/) disponibilizada para cada operação de acesso
ao /fluxo de dados/ (/stream/) que irá representar o arquivo. 

Resumindo:

- No sistema, arquivos são abstrações de recursos de entrada e saída
  representados como /fluxos de dados/;
- Na linguagem C, os fluxos de dados, bem como os recursos que eles representam,
  são abstraídos com o tipo =FILE *= (ponteiro para =FILE=).

#+begin_quote
O tipo =FILE *= é definido na biblioteca padrão (cabeçalho =stdio.h=).
#+end_quote

** Diferença entre streams e descritores de arquivos

*** Descritores de arquivos (tipo =int=)

- São expressos como inteiros (tipo =int=).
- Visíveis em =/proc/PID/fd=.
- Identificam arquivos em chamadas de sistema (=open=, =close=, =read=, =write=, etc).
- Representam diretamente uma entrada na tabela de arquivos abertos do processo.

*** Streams (tipo ponteiro para =FILE=)

- São abstrações de todos os recursos abertos para operações com arquivos.
- Utilizados em funções da biblioteca padrão (=fopen=, =fclose=, =fgets=, =fprintf=, etc).
- Utilizam descritores de arquivos para interagir com o sistema.
- Envolvem acumulação (/buffering/) na memória do processo.

** Procedimento geral de acesso a arquivos para leitura

Geralmente, as operações com arquivos envolvem três etapas: abertura,
processamento e fechamento.

*** Abertura

- Disponibilização dos recursos para acesso ao arquivo.
- Definição do tipo de operação (escrita, /append/ e/ou leitura).
- Com a chamada de sistema =open=, recebemos um descritor de arquivos.
- Com a função =fopen=, recebemos um ponteiro para =FILE= (/stream/).
- Com =fopen=, um ponteiro nulo (=NULL=) será retornado no caso de erros.
- Com =open=, o retorno será =-1= no caso de erros.
- Com ambas as funções, a variável =errno= receberá a identificação do erro.

#+begin_quote
Neste curso, nós utilizaremos apenas a função =fopen=.
#+end_quote

Exemplo:

#+begin_src c
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r");
#+end_src

Aqui, o arquivo =arquivo.txt=, se existir no diretório corrente, será aberto
para leitura (modo de abertura ="r"=). Se tudo correr bem, nós teremos o
fluxo de dados =stream= para manipular o arquivo.

Para tratar eventuais erros, nós podemos fazer algo assim:

#+begin_src c
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r");
if (!stream) {
    perror("Erro ao abrir o arquivo");
    return EXIT_FAILURE;
 }
#+end_src

#+begin_quote
A função =perror= imprime, na saída padrão de erros (=stderr=), a descrição
do último erro atribuído à variável =errno=. A string, passada como argumento,
será utilizada como prefixo da mensagem impressa.
#+end_quote

*** Processamento

É quando nós realizamos alguma operação com o arquivo, como ler seu conteúdo,
escrever algo nele, obter seu tamanho em bytes, reposicionar seu ponteiro
interno, etc. Por exemplo, nós podemos abrir o arquivo =/etc/shells= para ler
e imprimir todo seu conteúdo:

#+begin_src c
char *file = "/etc/shells";

// Abertura do arquivo...
FILE *stream = fopen(file, "r");
if (!stream) {
    perror("Erro ao abrir o arquivo");
    return EXIT_FAILURE;
}

// Processamento...
char line[BUFSIZ];   // Buffer para acumular cada linha lida
while (fgets(line, BUFSIZ, stream)) {
    printf("%s", line);
}
#+end_src

Aqui, =fgets= lerá o conteúdo do arquivo associado a =stream= (=/etc/shells=) até
encontrar uma quebra de linha ou chagar a =BUFSIZ-1= bytes.

#+begin_quote
A macro =BUFSIZ= é definida em =stdio.h= e expande o valor =8192= (8kB).
#+end_quote

De um modo ou de outro, a estrutura de dados associada a =stream= registrará
a quantidade de bytes que já foram lidos para que, no caso de um acesso
subsequente, a leitura continue a partir do byte seguinte -- isso é o que
chamamos de /ponteiro interno do arquivo/.

#+begin_quote
O ponteiro interno do arquivo pode ser obtido e manipulado com as funções
=fseek=, =ftell= e =rewind=.
#+end_quote

Consequentemente, enquanto =fgets= não retornar =NULL= (no caso de um erro ou
do fim do arquivo), o conteúdo do buffer =line= será impresso e o arquivo
será lido novamente a partir da nova posição de seu ponteiro interno.

*** Fechamento

- Liberação dos recursos disponibilizados na abertura.
- Tratamento adequado para os dados pendentes.
- Possibilita a verificação de erros de fechamento.
- Com =close= ou =fclose=, o retorno será =0= em caso de sucesso.
- Com =close=, o retorno será =-1= em caso de erro.
- Com =fclose=, o retorno será =EOF= em caso de erro (que também tem valor =-1=).
- Com ambas as funções, a variável =errno= receberá a identificação do erro.

#+begin_quote
Os arquivos abertos são fechados com o fim do processo, mas é sempre
preferível fechá-los explicitamente para garantir a integridade dos
dados e evitar vulnerabilidades (como o vazamento de recursos).
#+end_quote

Para fechar o arquivo do nosso exemplo:

#+begin_src c
char *file = "/etc/shells";

// Abertura do arquivo...
FILE *stream = fopen(file, "r");
if (!stream) {
    perror("Erro ao abrir o arquivo");
    return EXIT_FAILURE;
}

// Processamento...
char line[BUFSIZ];   // Buffer para acumular cada linha lida
while (fgets(line, BUFSIZ, stream)) {
    printf("%s", line);
}

// Fechamento do arquivo...
fclose(stream);
#+end_src


