Tipos de Arquivos em Sistemas Unix-like

Olá pessoal! Faz um tempo que não escrevo aqui no blog sobre algum assunto relacionado a segurança ou sistema operacional em si, já que na maior parte do tempo conto apenas dicas de problemas que eu encontro. Porém, hoje quero fazer diferente. Quero fazer uma pequena explicação sobre os tipos de arquivos encontrados no Linux e em outros ambientes Unix-like. Por que isso é importante saber? Bem, qualquer profissional que tenha que se deparar com uma situação de recuperação de dados, por exemplo, terá que ter um mínimo de conhecimento sobre como funciona um sistema de arquivos e os tipos de dados suportados. Quando é necessário a recuperação de dados? Apenas trabalhando com segurança para ter uma idéia. Vamos lá.

De maneira resumida, posso dizer que existem basicamente os diretórios e alguns outros tipos de arquivos. Porém, como o sistema trabalha com isso internamente? Simples! O sistema de arquivos trata tudo como arquivos! Sendo assim, para o sistema de arquivos, um diretório é como outro arquivo, porém com características especiais.

Arquivos regulares

São os tipos de arquivos mais comuns no sistema, podendo ser desde textos até arquivos binários. Ou seja, são os dados propriamente dito.

Ligações

As ligações funcionam como “atalhos”, ou apelidos para outros arquivos. Essas ligações podem ser de dois tipos: simbólicas e físicas. As ligações simbólicas são arquivos independentes do destino que é referenciado, funcionando apenas como um link. A remoção de uma ligação simbólica não implica na remoção do arquivo original. Porém, qualquer alteração realizada na ligação é realizada diretamente no arquivo original. As ligações físicas são entradas adicionais na tabelas de inodes para um arquivo que já possui outro inode associado. Ou seja, é como criar dois arquivos, aparentemente diferentes, que apontam para o mesmo bloco de dados. Vamos representar os dois tipos de ligação abaixo para nos ajudar a entender melhor.

O que ocorre quando removemos um arquivo? Simplesmente é apagada a referência ao inode. Ou seja, os inodes “órfãos” são ditos como removidos. Em uma ligação simbólica, se a referência do arquivo original for removida, a ligação se torna obsoleta, ou seja, “quebrada”. Porém, em um caso semelhante na ligação física, se removermos a referência de um arquivo a um inode, a ligação poderá continuar possuindo acesso ao bloco de dados normalmente, como se a própria ligação fosse o arquivo original.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
int linksimbolico = symlink(argv[0],”linksimbolico”); //cria uma ligação simbólica
if (linksimbolico==0) printf(“Link simbólico criado.\n”);
int linkfisico = link(argv[0],”linkfisico”); //cria uma ligação física
if (linkfisico==0) printf(“Link físico criado.\n”);
char nome[100];
readlink (“linksimbolico”,nome,100); //lê o conteúdo da ligação
printf(“O link simbólico linksimbolico aponta para o arquivo original: %s\n”,nome);
return(0);
}

O trecho de código em C acima é bem simples. Utilizamos as primitivas symlink, link e readlink. Fazem, respectivamente, a criação de uma ligação chamada linksimbolico que aponta para o arquivo do próprio binário, cria uma ligação chamada linkfisico que aponta para o mesmo arquivo e, por último, lê o conteúdo do arquivo linksimbolico para nos informar a qual arquivo esta ligação aponta.

Diretórios

Ao contrário dos arquivos que vimos até então, os diretórios não possuem acesso diretamente pelo usuário. O usuário na verdade requisita ao sistema, através de funções primitivas, o acesso ao diretório. O diretório é na verdade um arquivo que contém o nome e referência para todos os outros arquivos que estão dentro do diretório.

Para entendermos isso melhor, vamos demonstrar um pequeno trecho de código em C que serve apenas para listar o conteúdo de um diretório. A biblioteca que implementa as primitivas de acesso a diretórios chama-se dirent.h.

#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main (int argc, char *argv[])
{
DIR *dp;
struct dirent *ep;
dp = opendir (argv[1]); //abre o diretório para leitura
if (dp != NULL)
{
while (ep = readdir(dp)) //lê o conteúdo do diretório
puts (ep->d_name);
(void) closedir (dp); //fecha o diretório
}
else
puts (“Não foi possível abrir o diretório.\n”);
return(0);
}

O exemplo acima realiza basicamente a abertura de um diretório, através do comando opendir com o parâmetro passado ao binário, para a listagem de seu conteúdo. O comando opendir retorna um dado do tipo DIR, que é então manipulado pela função readdir. Para cada arquivo, é armazenado diversas informações na estrutura do diretório, como nome (d_name), tipo (d_type), dentre outras. Sendo assim, precisamos armazenar os dados retornados pelo readdir em uma estrutura.

Ao final é necessário proceder com o fechamento do diretório, como seria realizado com qualquer outro arquivo, afim de poupar recursos do sistema.

Inter-Process Communication

Os arquivos do tipo IPC (Inter-Process Communication) são arquivos que representam estruturas para comunicação com meios externos, como sockets, barramentos, filas FIFO (First-In, First-Out). A idéia básica por detrás deste tipo de arquivo é haver alguém enviando dados em uma ponta da estrutura e alguém recebendo do outro lado. Esses arquivos são também chamados de pipes (“canos”).

A criação de pipes é simples, sendo necessário o uso da primitiva mknod. Vamos a outro código em C que realiza a criação de um pipe no sistema.

#include<sys/stat.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int result;
result = mknod(argv[1],S_IFIFO|0666,0); //criação do pipe
if (result == 0)
puts (“Pipe criado!”);
else
puts (“Erro na criação do pipe!”);
}

O exemplo irá proceder com a criação de um pipe FIFO com o nome fornecido ao binário como argumento. O arquivo terá permissão de acesso 0666 (-r–r–r–). O último parâmetro para a primitiva mknod será utilizada apenas caso estejamos tratando de um FIFO para um dispositivo.

Seu uso pode ser problemático, caso não haja um receptor para o canal estabelecido. Uma aplicação pode ficar inativa caso fique a espera de dados de um pipe.

Arquivos de dispositivos

O último tipo de arquivo que nos resta são os arquivos de dispositivos. Esses arquivos são utilizados para prover uma interface de comunicação do sistema com o hardware. Existem dois tipos de arquivos de dispositivos: voltado a caracteres ou bloco. Essa diferença reflete o meio físico que é utilizado para comunicação com o hardware. Dispositivos com arquivos voltados a caracteres possuem normalmente conexões através de linhas seriais (interface de rede, por exemplo), enquanto que dispositivos com arquivos voltados a blocos possuem em geral um meio de transferência paralela (interface com a controladora de discos, por exemplo).

#include<sys/stat.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int result;
result = mknod (argv[1],S_IFBLK|0777,1); //criação do dispositivo
if (result == 0)
puts (“Dispositivo de Caracteres criado!”);
else
puts (“Erro na criação do Dispositivo de Caracteres!”);
}

O exemplo de código acima faz basicamente o que o anterior, porém ao invés de criar um pipe, foi criado um arquivo de dispositivo de blocos.

É necessário um cuidado especial com esse tipo de arquivo pois caso seja realizado um acesso indevido a um dispositivo, como mapeamento da memória RAM, podem ocasionar instabilidade do sistema, podendo travá-lo completamente.

Finalização

O conteúdo em si é bem pouco e foi bem resumido. Para terem uma boa noção, recomendo a leitura de textos que abordem profundamente o sistema de arquivos virtual e o sistema operacional. Alguns exemplos eu baseei deste site.

Dúvidas, correções, declarações, são todas bem vindas! Usem os comentários, se necessário.

Abraços a todos!

{fcomment}

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *