[Estudos de C] Escrevendo uma versão simples do utilitário cat
Tags: webdev, programming, braziliandevs, softwaredevelopment
Published: 2023-08-23 11:00:50

O `cat` é um utilitário de linha de comando que lê arquivos, seja apenas um ou uma sequência informada, e escreve o seu conteúdo em fluxo de destino. 

O nome `cat` é derivado de: *(con)**cat**enate files*. 

Conforme o manual (`man cat`) é descrito com duas funções: concatenar arquivos e imprimir o seu conteúdo em um fluxo de saída. 

Utilizamos o `cat` da seguinte forma:

```bash
cat [OPTION]... [FILE]...
```

Essa implementação é com fim de tentar a resolução, própria, do Exercício 01, da Piscine 10 de C, da Escola 42. Não participei de nenhuma piscina, apenas encontrei os PDFs relativos no Github e estou tentando fazer os exercícios. 

São as regras a implementação desta versão: 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isukn4quaiek3lu33v8m.png)

Como pode se ler das orientações acima, a implementação desta versão tem limitações das funções que poderão ser utilizadas. 

Nesta minha implementação, optei por utilizar apenas as funções: *open*, *close*, *read* e *write*.

Antes de adentrar diretamente na escrita da versão do `cat` , vou estar escrevendo algumas notas de introdutórias. 

## File Descritores

Um ***************File Descritor (FD)*************** é um número inteiro único maior que zero que guarda referência para um arquivo aberto pelo sistema operacional. 

Quando pede ao sistema operacional o acesso a determinado arquivo ou fluxo de dados, o ******kernel****** retorna, um ************File Descritor************ de um registro na tabela global de registros de arquivos do sistema operacional. 

Por meio dele o processo ou programa pode acessar os dados constantes no arquivo, ou fluxo referenciado pelo FD. Observa-se que o pedido de acesso tenha sido acatado com sucesso. 

Em sistemas baseados em Unix e Linux os primeiros FD referem aos fluxos *stdin*, *stdout* e *stderr*.

| Nome | File Descriptor | Descrição | Abreviatura |
| --- | --- | --- | --- |
| Standard input | 0 | O fluxo de dados padrão para entrada, por exemplo, em um pipeline de comando. No terminal, o padrão é a entrada do teclado do usuário. | stdin |
| Standard output | 1 | O fluxo de dados padrão para saída, por exemplo, quando um comando imprime texto. No terminal, o padrão é a tela do usuário. | stdout |
| Standard error | 2 | O fluxo de dados padrão para a saída relacionada à ocorrência de um erro. No terminal, o padrão é a tela do usuário. | stderr |

Os valores dos FD acima descritos podem serem acessados indicando diretamente os seus valores ou por meio de constantes presentes nas bibliotecas `<unistd.h>` e `<stdio.h>`.

| <unistd.h> | <stdio.h> |
| --- | --- |
| STDIN_FILENO | stdin |
| STDOUT_FILENO | stdout |
| STDOUT_FILENO | stderr |

Outra questão referente aos FD é a possibilidade de redirecionar os fluxos de dados entre diferentes FD por meio do ****pipe****. Mas esse tópico tratarei em outro momento.  

**Referências:**

1. https://www.computerhope.com/jargon/f/file-descriptor.htm 

## Função `open()`

A função `open()` permite a realização de conexão entre um arquivo e um FD.  Por meio dessa função, o programa tem acesso ao FD de um arquivo informado como parâmetro na chamada da função. 

A função tem o seguinte protótipo: 

`int open(const char *pathname, int flags);
 int open(const char *pathname, int flags, mode_t mode);`

E é disponibilizada pela biblioteca `<fcntl.h>`.

O primeiro argumento, *path*, é pelo qual passamos o caminho do caminho ou fluxo de dados que queremos acessar/abrir. 

O segundo argumento é o tipo de acesso que desejamos(leitura, escrita, leitura e escrita, etc). Esse passamos informando a respectiva, quis são constantes definidas no cabeçalho `<fcntl.h>`. Exemplos de algumas *****flags***** definidas e mais utilizadas. 

| Flag | Descrição |
| --- | --- |
| O_EXEC: | Abre o arquivo para execução, apenas. Não se aplica a diretórios. |
| O_RDONLY | Apenas leitura. |
| O_RDWR | Abre o arquivo para leitura e escrita. |
| O_RDWR | Abre um diretório para pesquisa, apenas. Não se aplica a não diretórios. |
| O_WRONLY | Abre o arquivo apenas para a escrita. |

O terceiro argumento, facultativo, é utilizado quando estamos criando um novo arquivo. Pois, é por meio dele que definimos as permissões iniciais do arquivo criado. 

******************Retorno:****************** O retorno da chamada da função `open()` é um número inteiro positivo que representa FD do respectivo arquivo ou fluxo de dados requerido a ser aberto. Caso contrário, a função retorna `-1` e define o tipo de erro que ocorreu. 

O código abaixo é a implementação de utilização da função open() que cria, se não existir um arquivo e salva nele que o usuário digitar no `stdin`, por enquanto o programa estiver em execução. 

```c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#define MAX_BUF 1024

int	main(int argc, char **argv)
{
	char 	*file;
	char 	buf[1024];
	int 	fd;
	int 	flags;
	int 	bytes_reads;
	int 	bytes_writes;

	if (argc < 2)
	{
		perror("Arquivo de destino não definido.\n");
		return (1);
	}

	flags = O_WRONLY | O_CREAT | O_TRUNC;
	file = argv[1];

	fd = open(file, flags, S_IRUSR | S_IWUSR);
	if (fd == -1)
	{
		perror("Erro ao abrir o arquivo!\n");
		return (1);
	}

	while ((bytes_reads = read(STDIN_FILENO, buf, MAX_BUF - 1)) > 0)
	{
		bytes_writes = write(fd, buf, bytes_reads);
		if (bytes_writes != bytes_reads)
		{
			perror("Error ao escrever no arquivo\n.");
			close(fd);
			return (1);
		}
	}	

	if (bytes_reads == -1)
	{
		perror("Erro na leitura do STDIN\n");
		close(fd);
		return (1);
	}	

	close(fd);
	return (0);
}
```

O código ficou um pouco longo, mas bom que aproveito como exemplo para as explicações abaixo, sobre as demais funções. 

Para mais informações: `man 2 open`.

**Referências:**

1. https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
2. [https://en.wikipedia.org/wiki/Open_(system_call)](https://en.wikipedia.org/wiki/Open_(system_call)) 

## Função `close()`

A função `close()` fecha um FD aberto e referenciado. A chamamos quando precisamos fechar um arquivo ou fluxo de dados abertos. Fazemos isso passamos o arquivo ou fonte de dados como parâmetro, no qual será terminado a conexão com o FD referente. 

A função tem o seguinte protótipo:

`int close(int fd);`

E é disponibilizada pela biblioteca `<unistd.h>`.

A função retorna 0 no caso de sucesso ou -1, em caso tenha ocorrido algum erro. 

Como pose se ver no código anteriormente exposto, a chamamos para fechar o FD do arquivo aberto antes do retorno final da função `main`().

## Função `read()`

A função `read()` lê uma certa quantidade de *bytes* a partir de um FD e os salva em um *buffer*.

Ela tem o seguinte protótipo:

`ssize_t read(int fd, void *buf, size_t count);`

O primeiro argumento é a indicação do FD no qual será feita a leitura. O segundo é o do *buffer* que será salvo os dados lidos e o terceiro é a quantidade de *****bytes***** a serem lidos/transferidos.

E é disponibilizada pela biblioteca `<unistd.h>`.

Quanto ao retorno: 

> Cada chamada retorna uma contagem do número de bytes transferidos. Na leitura, o número de bytes retornados pode ser menor que o número solicitado.
Um valor de retorno de zero bytes implica o fim do arquivo, e -1 indica algum tipo de erro.
> 

Referências: RITCHIE, Dennis M. et al. The C programming language. **Bell Sys. Tech. J**, v. 57, n. 6, p. 1991-2019, 1978.

Como pode ser observado no código recortado do exposto acima, a função `read()` é chamada no contexto de um *loop* `while`. Por enquanto o valor retornado for maior que zero, os dados lidos, por quantidade definida: a vez, são salvos no na variável `buf` e valor da quantidade de *bytes* lidos são salvos na variável `bytes_reads`. 

Após a execução, é feita uma verificação se ocorreu algum erro na leitura. 

```c
while ((bytes_reads = read(STDIN_FILENO, buf, MAX_BUF - 1)) > 0)
	{
		bytes_writes = write(fd, buf, bytes_reads);
		if (bytes_writes != bytes_reads)
		{
			perror("Error ao escrever no arquivo\n.");
			close(fd);
			return (1);
		}
	}	

	if (bytes_reads == -1)
	{
		perror("Erro na leitura do STDIN\n");
		close(fd);
		return (1);
	}	
```

## Função `write()`

A função `write()` escreve uma quantidade `n` de *bytes* provenientes de um buffer em um FD informado. 

Ela tem o seguinte protótipo:

`ssize_t write(int fd, const void *buf, size_t count);` 

O primeiro argumento é o FD onde serão escritos os dados, o segundo é do ******buffer****** no qual serão provenientes e o terceiro é quantidade de *****bytes***** que serão escritos por vez.

A função é disponibilizada pela biblioteca `<unistd.h>`.

Quanto ao retorno: 

> Para gravação, o valor de retorno é o número de bytes gravados. Ocorreu um erro se esse número não for igual ao número solicitado.
> 

Referências: RITCHIE, Dennis M. et al. The C programming language. **Bell Sys. Tech. J**, v. 57, n. 6, p. 1991-2019, 1978.

## Implementação de uma versão simples do utilitário cat

Escrita as breves notas acima, agora é vez de escrever sobre a implementação do utilitário `cat`.

Nas primeiras linhas são escritas as inclusões das bibliotecas necessárias, a definição da constante referente à quantidade de ******bytes****** que serão lidos e escritos por vez e o protótipo das funções. 

```c
#include <unistd.h>
#include <fcntl.h>

#define MAX_BUF 1024

void    ft_cat(int fd);
void    clear_buf(char *);
```

Em seguida tem o corpo da função `main()`.

```c
int     main(int argc, char **argv)
{
    int     i;
    int     fd;

    if (argc == 1)
    {
        fd = STDIN_FILENO;
        ft_cat(fd);
    }
    else 
    {
        i = 1;
        while (i < argc)
        {
            fd = open(argv[i], O_RDONLY);
            if (fd == -1)
            {
                write(2, "Error opening the file\n", 30);
                return (1);
            }
            ft_cat(fd);
            close(fd);
            i++;
        }
    }

    return (0);
}
```

Nela é declarada duas variáveis do tipo inteiro: `i` e `fd`. 

Em seguida é verificado se foi informado arquivos para serem lidos ou não. Caso não tenham sido informados pelo usuário, o variável `fd` recebe o FD referente ao `stdin`, por meio da constante `STDIN_FILENO`. Após, é chamada a `ft_cat(fd)` passando o respectivo `fd`. 

Por outro lado, na situação do usuário tenha informado um ou mais arquivos; é atribuído o valor 1 para a variável `i` e dado início ao loop, que será executado por enquanto `i` for menor que o valor da variável `argc`. 

Para cada interação do *loop*: Um arquivo é aberto por meio da função `open()` e o seu respectivo FD é salvo na variável `fd`; é feita uma verificação de erro e, se estiver tudo *ok*, a função `ft_cat()` é chamada tedndo o FD atual como parâmetro.

Por fim, as últimas linhas de execução do loop é: fechar o arquivo após a execução da função `ft_cat()` e o incremento da variável `i` para acessar o próximo arquivo, se houver.  

Antes de passar para a explicação da função f`t_cat()`; apenas anotar aqui sobre a função `clear_buf()`. Que tem a tarefa de limpar a memória de alguma *array* recebida como parâmetro. 
Assim, evita que haja restos de dados no *buffer*, principalmente quando estamos o utilizando no contexto do *loop* e leitura e escrita. 

```c
void    clear_buf(char *buf)
{
    int     i;
    i = 0;
    while(i < MAX_BUF)
        buf[i++] = '\0';
}
```

Agora, as notas para função `ft_cat()`. Segue o código da respectiva. 

```c
void    ft_cat(int fd)
{
    int     bytes_reads;
    int     bytes_writes;
    char    buf[MAX_BUF];

    bytes_reads = 0;
    bytes_writes = 0;
    clear_buf(buf);

    while ((bytes_reads = read(fd, buf, MAX_BUF - 1)) > 0)
    {
        buf[bytes_reads - 1] = '\0';
        bytes_writes = write(1, buf, bytes_reads);
        if (bytes_writes != bytes_reads)
        {
            write(2, "Error reading the file\n", 30);
            return;
        }
    }

    if (bytes_reads == -1)
    {
        write(2, "Error reading the file\n", 30);
        return;
    }

    write(1, "\n\n", 2);   
}
```

Ela recebe como parâmetro um inteiro que representa um FD de de onde os dados serão lidos. A escolha é pela opção, ao invés de receber o caminho de arquivo a ser lido, é pela possibilidade do FD referir tanto para arquivos, como também para outras fontes de da dados: tais como o `stdin`. 

De início são declaradas três variáveis: duas do tipo inteiro e uma cadeia de caráteres com tamanho definido pela constante `MAX_BUF`.  As variáveis `bytes_reads` e `bytes_writes` , como pelo nome dá para saber, serão onde armazenaremos os retornos das funções `read()` e `write()`. E a função `buf[]` será onde salvaremos os dados lidos e a serem, posteriormente, escritos no `stdout`. 

Em seguida, são atribuídas as variáveis valores zero, com fim de evitar que haja dados remanescentes da memórias. 

A atividade de leitura e escrita é feita no contexto de um ****loop**** `while`****.****

O ****loop**** é executado por enquanto o retorno da chamada da função `read()` for maior que zero. Na chamada da dessa função passamos, como parâmetro o FD atual da chamada da função `ft_cat()` , o `buf` onde os dados serão salvos e a quantidade de *****bytes***** que serão lidos por vez. 

No corpo do ****loop****, adicionamos o caractere de terminação na última posição do `buf`. Após é feita a chamada da função `write()` para escrever no `stdout` os dados presentes no `buf`.  Um verificação de erro é feita para checar se quantidade de *****bytes***** escritos é igual a quantidade a ser escrita. Caso haja alguma diferença, é disparado um erro. 

Por fim, encerrada a execução do `while`, outra verificação de erro é feita relativa à chamada da função `read()` . 

## Conclusão

Bem, está foi uma implementação simples do utilitário `cat`. Observando a proposta do desafio que apresentou restrições de recursos, o intuito com esta atividade é aprender a utilização das funções mais básicas de manipulação de entradas e saídas de dados na linguagem C. 

# Referências

https://twobithistory.org/2018/11/12/cat.html 

[https://en.wikipedia.org/wiki/Cat_(Unix)](https://en.wikipedia.org/wiki/Cat_(Unix)) 

https://history-computer.com/understanding-cat-command-in-linux-with-examples/ 

https://austinedger.medium.com/unix-commands-lets-build-cat-59b8a91b9708