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

*  Aula 16: Abertura de arquivos para leitura e escrita

[[https://youtu.be/B42KIZfivsg][Vídeo desta aula]]

** Modos de abertura para leitura e escrita

As strings de modos ="r"=, ="w"= e ="a"=, da função =fopen=, quando sucedidas do
caractere =+=, indicam a abertura do arquivo também para suas respectivas
operações contrarias:

| Modo | Leitura | Escrita | Truncar | Offset | Criar |
|------+---------+---------+---------+--------+-------|
| ="r"=  | Sim     | Não     | Não     | Início | Não   |
| ="r+"= | Sim     | Sim     | Não     | Início | Não   |
| ="w"=  | Não     | Sim     | Sim     | Início | Sim   |
| ="w+"= | Sim     | Sim     | Sim     | Início | Sim   |
| ="a"=  | Não     | Sim     | Não     | Fim    | Sim   |
| ="a+"= | Sim     | Sim     | Não     | Fim    | Sim   |

De acordo com a tabela acima, nós precisamos observar alguns pontos
importantes:

- Se a string de modo iniciar com =w=, o arquivo sempre será truncado
  na sua abertura.
- Se a string de modo iniciar com =a=, o ponteiro interno sempre será
  posicionado no fim do arquivo na sua abertura.
- Se a string de modo iniciar com =r=, o ponteiro interno sempre será
  posicionado no início do arquivo na sua abertura.
- Se não existir, o arquivo só será criado se a string de modo
  iniciar com =w= ou =a=.

** Manipulação do ponteiro interno

O ponteiro interno do arquivo representa o número do byte que será lido
ou escrito no próximo acesso: ou seja, seu /offset/ (ou /ponto de partida/).
Isso é particularmente importante quando abrimos arquivos para ambas as
operações com as strings de modo ="r+"= ou ="a+"=, visto que o modo ="w+"=
truncará o conteúdo do arquivo.

*** A função =fseek=

As funções da família =fseek= (=stdio.h=) são utilizadas para posicionar
ou obter informações sobre o ponteiro interno do arquivo:

| Função  | Descrição                                                                               | Retorno                        |
|---------+-----------------------------------------------------------------------------------------+--------------------------------|
| =fseek=   | Define a posição do ponteiro interno.                                                   | Sucesso: =0= (=int=)               |
| =ftell=   | Obtém a posição corrente do ponteiro interno.                                           | Sucesso: número do byte (=long=) |
| =rewind=  | Posiciona o ponteiro interno no byte =0=.                                                 | =void=                           |
| =fsetpos= | Interface para =fseek= referenciando a definição do /offset/ a partir do início do arquivo. | Sucesso: =0= (=int=)               |
| =fgetpos= | Interface para =ftell= escrevendo o /offset/ obtido em um dado endereço.                    | Sucesso: =0= (=int=)             |

*Notas:*

- Com a função =fseek=, o novo /offset/ é definido com uma expressão do tipo =long=.
- Com a função =fsetpos=, o novo /offset/ é definido em um ponteiro do tipo =fpos_t=.
- Com a função =fgetpos=, o /offset/ corrente é escrito em um ponteiro do tipo =fpos_t=.

#+begin_quote
A função =fseek= trabalha com /streams/, mas é possível manipular o ponteiro interno
a partir de descritores de arquivos com a chamada de sistema =lseek=.
#+end_quote

*** Pontos de origem de =fseek=

A função =fseek= recebe 3 argumentos:

- O /stream/ associado ao arquivo;
- A expressão do valor do /offset/ (tipo =long=);
- Um inteiro determinando o ponto de origem para a definição do novo /offset/.

Este é o seu protótipo na =glibc= (=stdio.h=):

#+begin_src c
int fseek(FILE *stream, long offset, int whence);
#+end_src

#+begin_quote
O termo /whence/ significa /"a partir de onde"/ e representa o ponto de origem
do deslocamento do ponteiro interno do arquivo.
#+end_quote

O terceiro argumento (=whence=) pode receber qualquer valor inteiro, mas
a =glibc= oferece três macros para os pontos de origem mais comuns:

- =SEEK_SET=: o início do arquivo;
- =SEEK_END=: o fim do arquivo;
- =SEEK_CUR=: o /offset/ corrente.

*** Abertura de arquivos com "r+"

Como o /offset/ inicial será o byte =0=, se a primeira operação for uma
escrita, os primeiros bytes do arquivo serão sobrescritos. Caso esta
não seja a intenção, será preciso deslocar o ponteiro interno para o
fim do arquivo:

#+begin_src c
// Abertura do arquivo para leitura e escrita...
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r+");

// Posicionar offset no fim do arquivo...
fseek(stream, 0, SEEK_END);

// Rotinas de escrita...
#+end_src

Aqui, nós deslocamos o ponteiro interno em zero bytes a partir do fim do
arquivo (origem em =SEEK_END=).

*** Abertura de arquivos com "a+"

O /offset/ inicial será o fim do arquivo e, se quisermos começar a leitura
a partir de seu início, será preciso deslocar o ponteiro interno para lá:

#+begin_src c
// Abertura do arquivo para leitura e escrita...
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r+");

// Posicionar offset no início do arquivo...
fseek(stream, 0, SEEK_SET);

// Rotinas de leitura...
#+end_src

Desta vez, nós deslocamos o ponteiro interno em zero bytes a partir do
início do arquivo (origem em =SEEK_SET=).

** Procedimentos úteis

Para encerrar esta aula (e o nosso curso), aqui estão alguns exemplos de
procedimentos gerais úteis envolvendo a manipulação de arquivos.

*** Quantidade de linhas


#+begin_src c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    // Abertura do arquivo para leitura...
    char *file = "meuarquivo.txt";
    FILE *stream = fopen(file, "r");
    if (!stream) {
        fprintf(stderr, "Erro ao abrir o arquivo!\n");
        return EXIT_FAILURE;
    }

    // Contagem de linhas...
    char buf[BUFSIZ]; // Buffer para ler blocos do arquivo (8192 bytes).
    int lines = 0;    // Total de linhas encontradas.

    // Lê uma linha até encontrar \n, até BUFSIZ-1 ou até EOF...
    while (fgets(buf, BUFSIZ, stream) != NULL) {
        // Só conta uma linha se houver \n no buffer...
        if (buf[strlen(buf) - 1] == '\n' ) lines++;
    }
    // Se a última linha não contiver \n, ela também será contada...
    if (lines > 0 && buf[strlen(buf) - 1] != '\n') lines++;
    
    // Fechamento do arquivo...
    fclose(stream);

    // Impressão do resultado...
    printf("%d linhas", lines);

    return EXIT_SUCCESS;
}
#+end_src

*** Obter o tamanho do arquivo

#+begin_src c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // Abertura do arquivo...
    char *file = "meuarquivo.txt";
    FILE *stream = fopen(file, "rb");
    if (!stream) {
        perror("Erro ao abrir o arquivo");
        return EXIT_FAILURE;
    }

    // Posiciona o ponteiro de leitura no final do arquivo...
    if (fseek(stream, 0, SEEK_END) != 0) {
        perror("Erro ao deslocar o offset para o final");
        fclose(stream);
        return EXIT_FAILURE;
    }

    // Obtém o offset corrente (tamanho do arquivo em bytes)...
    long fsize = ftell(stream);
    if (fsize == -1L) {
        perror("Erro ao obter o offset corrente");
        fclose(stream);
        return EXIT_FAILURE;
    }

    // Exibe o tamanho do arquivo...
    printf("Tamanho do arquivo: %ld bytes\n", fsize);

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

*** Testar se um arquivo está vazio (0 bytes)

#+begin_src c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // Abertura do arquivo...
    char *file = "meuarquivo.txt";
    FILE *stream = fopen(file, "r");
    if (!stream) {
        perror("Erro na abertura do arquivo");
        return EXIT_FAILURE;
    }

    // is_empty recebe 1 se o arquivo estiver vazio...
    int is_empty = (fseek(stream, 0, SEEK_END) == 0 && ftell(stream) == 0);

    // Fechamento do arquivo...
    fclose(stream);

    // O programa terminará com sucesso (0) se o arquivo estiver vazio...
    return !is_empty;
}
#+end_src


