ufabc-bcc / 2019.Q1.SO.BrisaFS

Projeto de Programação - BrisaFS
3 stars 5 forks source link

Guia básico sobre Fuse e Git #5

Open LukeDDC opened 5 years ago

LukeDDC commented 5 years ago

Guia sobre FUSE

Objetivos

Este guia ter por objetivo dar uma visão básica sobre como o FUSE funciona e compartilhar materiais que possuam documentação sobre o mesmo. Caso você deseje adicionar qualquer coisa ou corrigir por favor faça um comentário abaixo.

O guia surgiu prioritariamente para ajudar meu grupo, mas decidi disponitilizá-lo para caso alguém precise.

GIT

Foi comentado durante a aula que muitas pessoas não tinham nenhum conhecimento sobre GIT e por isso estavam tendo alguma dificuldade. Vou deixar alguns links bem interessantes para caso você queira aprender e algumas dicas para ajudar você a desenvolver.

Por quê git é importante?

  1. Ele vai permitir você escrever código colaborativo com seus colegas de forma muito mais fácil e sem conflitos.
  2. Esqueça problemas para saber em qual versão vocês estão, pessoas sobrescrevendo código um do outro ou até mesmo ter que procurar em uma lista enorme de backups uma versão estável do seu projeto no qual tudo ainda funcionava.

O git vai resolver tudo isso pra você com pouco esforço e dentro de poucas horas você provavelmente já estará dominando o suficiente para desenvolver sem problemas.

Claro que pra usufruir das vantagens você vai ter que seguir algumas boas práticas e dominar a ferramenta.

Tutoriais interessantes de GIT

Uma base teórica legal pode ser encontrada nos seguintes links:

  1. https://www.atlassian.com/git/tutorials/what-is-version-control
  2. http://rogerdudler.github.io/git-guide/

Link com um tutorial iterativo desde o básico até algumas coisa mais avançadas. (RECOMENDO)

  1. https://learngitbranching.js.org/

Se souber de mais links deixe um comentário

Dicas (volte quando estiver entendendo)

  1. Escreva commits com mensagens descritivas, isso vai ajudar você quando precisar voltar para uma versão onde o sistema estava funcionando, além de documentar o projeto.

  2. Tente escrever commits que contenham poucas mudanças e que representem uma versão estável do seu sistema, ou seja, evite commitar quando nada compila ou existe algum bug.

  3. Seja descritivo nas mensagens de commit.

  4. Trabalhe em BRANCHS com seus colegas, isso vai evitar conflitos e irá garantir que a versão da master do projeto é a mais atualizada e estável.

Comandos úteis

  1. Adicionar arquivos para o commit (Staging)
    git add path_para_o_arquivo

Caso deseje adicionar tudo que modificou pode usar o atalho

git add -A
  1. Verificar o que foi adicionado para commit e o que foi modificado
git status

Arquivos que estão adicionados para o próximo commit (staged) irão aparecer em uma lista em verde logo após o texto "Changes to be committed:".

Arquivos modificados irão aparecer em uma lista em vermelho após o texto "Untracked files:"

  1. Criar nova branch

    git checkout -b nomedabranch

    ou para mudar para uma branch já existente

    git checkout nomedabranch
  2. Realizar um novo commit (após adicionar mudanças)

git commit -m "escreva aqui seu texto"

ou

git commit (isso irá abrir o editor padrão do seu computador)
  1. Subir mudanças realizadas em uma branch para o repositório remoto
git push nomedorepositorioremoto (comunmente será origin) nome_da_branch

para verificar qual nome do repositório remoto do seu projeto basta digitar

git remote -v
  1. Recuperar mudanças que estão em uma branch no repositório remoto
git pull nomedoremoto (comunmente origin) nome_da_branch

Caso queira completar e aprimorar esse guia deixe um comentário.

Fuse

Links úteis para entendimento:

  1. Documentação explicativa básica sobre fuse: https://www.cs.hmc.edu/~geoff/classes/hmc.cs135.201001/homework/fuse/fuse_doc.html

  2. Documentação explicando como implementar uma função em fuse http://www.maastaar.net/fuse/linux/filesystem/c/2016/05/21/writing-a-simple-filesystem-using-fuse/

  3. Documentação sobre alguns erros que aparecem no file system cedido pelo professor como ENOSPC. Caso queira entender um pouco mais sobre a semântica e o que representam, basta acessar o link: https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html

DICA:

Quando retornar algum desses erros em sua função lembre-se de retornar o mesmo com um um sinal de menos na frente, é assim que o fuse espera tratar os erros retornados pela sua função. Exemplo: -ENOSPC

  1. Documentação sobre a struct muito utilizada nas funções do fuse chamada stat, recomendo sua leitura toda vez que for necessário transmitir algum metadado sobre seus INodes: http://pubs.opengroup.org/onlinepubs/007908799/xsh/sysstat.h.html

  2. Documentação do FUSE.h, nele você vai encontrar um mapeamento de todas as funções que você pode implementar junto com uma breve descrição sobre elas. https://github.com/libfuse/libfuse/blob/master/include/fuse.h

Como o fuse funciona:

O fuse faz o intermédio entre o seu sistema de arquivo o SO e a interface de usuário. Isso significa que toda chamada que envolva operações do sistema de arquivos irá primeiro passar pelo fuse.

Exemplo:

Usuário digita stat nome_do_arquivo.txt no terminal, a partir de então os seguintes eventos acontecem:

  1. Fuse intercepta a chamada, parsea os argumentos e chama uma a função que você mapeou para o a stat.
  2. Você recebe a chamada do FUSE, faz as atualizações necessárias no seu file system e retorna ao fuse o conjunto de dados que é necessário para que ele converse com o SO e/ou apresente algo ao usuário.
  3. Fuse recebe os parâmetros que você passou e repassa a interface do usuário, nesse caso listando todos os arquivos.

Exemplificando com código:

Digite no seu terminal:

stat nomedoarquivo.txt

Observe que no seu console de debug algo parecido com isso será printado:

LOOKUP /nomedoarquivo.txt
getattr /nomedoarquivo.txt
   NODEID: 2
   unique: 164, success, outsize: 144
unique: 165, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 27656
getattr /

e no seu terminal irá aparecer os metadados do arquivo

File: nomedoarquivo.txt
  Size: 6           Blocks: 0          IO Block: 4096   regular file
Device: 37h/55d Inode: 2           Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2019-04-23 20:16:18.000000000 -0300
Modify: 2019-04-23 20:16:18.000000000 -0300
Change: 2019-04-23 20:16:18.000000000 -0300
 Birth: -

O que acontece aqui é que o fuse recebeu uma chamada para getattr para seu arquivo e esses logs ficaram gravados no console de debug.

Então primeiro a chamada ao getattr do fuse aconteceu e depois ele chamou a função presente no seu file system passando alguns argumentos que irei comentar a seguir.

Exemplo sobre o que deve ser feito em 2 com código no repositório base:

static int getattr_brisafs(const char *path, struct stat *stbuf) {
    memset(stbuf, 0, sizeof(struct stat));

    //Diretório raiz
    if (strcmp(path, "/") == 0) {
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 2;
        return 0;
    }

    //Busca arquivo na lista de inodes
    for (int i = 0; i < MAX_FILES; i++) {
        if (superbloco[i].bloco != 0 //Bloco sendo usado
            && compara_nome(superbloco[i].nome, path)) { //Nome bate

            stbuf->st_mode = S_IFREG | superbloco[i].direitos;
            stbuf->st_nlink = 1;
            stbuf->st_size = superbloco[i].tamanho;
            return 0; //OK, arquivo encontrado
        }
    }

    //Erro arquivo não encontrado
    return -ENOENT;
}

O parâmetro mais importante recebido na sua função é a struct do tipo stat (link para documentação: http://pubs.opengroup.org/onlinepubs/007908799/xsh/sysstat.h.html), essa struct é utilizada para passar os metadados do seu INode (arquivo) para o fuse, então todos os metadados que você desejar que sejam apresentados ao usuário devem ser preenchidos nessa estrutra.

Mas ok, como o fuse irá pegar esses dados de volta?

Bom, repare que stbuf é apenas um ponteiro, então ao final da execução da sua função todos os dados que você preencher nessa estrutura estarão disponíveis para a função do fuse que chamou sua função. A partir daí como já disse, o fuse irá se encarregar de mostrar esses dados ao usuário.

Esse padrão de passar um ponteiro para sua função irá se repetir diversas vezes em outras funções que você implementar, recomendo sempre procurar a documentação dos tipos que o fuse está passando para sua função junto com a documentação do fuse.h. Isso irá facilitar a você entender o que o fuse espera que você faça

Último ponto a se destacar é que o fuse espera que suas funções retornem 0 quando tudo ocorreu como esperado ou o código de erro correspondente ao problema encontrado como -ENOENT (não se esqueça do negativo), caso um arquivo não seja encontrado.

O que fazer quando quero implementar uma nova funcionalidade?

Primeiro pense qual função do sistema operacional seria responsável por aquilo, por exemplo escrever em um arquivo certamente deve corresponder a write. Depois disso lembre de criar sua função no qual o fuse irá chamar, mas como saber qual e quais parâmetros ela deve receber?

Bom, pra isso recomendo olhar em fuse.h lá você vai encontrar a assinatura que todas as suas funções devem ter para que elas sejam chamadas corretamente, exemplo:

int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);

Depois disso mapeie a função do fuse para sua função na struct fuse_operations:

static struct fuse_operations fuse_brisafs = {
    .write = write_brisafs // Isso é basicamente um ponteiro para sua função.
};

Isso irá dizer ao fuse qual função chamar quando a função write for invocada.

Após isso implemente a sua função write_brisafs e tá pronto o sorvetinho.

Espero que tenha ajudado, é tudo bem básico mas se alguém ainda estiver completamente perdido já é um começo.

Sugestões, dúvidas ou correções basta me contactar.

Obrigado,

francesquini commented 5 years ago

Muito obrigado @LukeDDC