Programação do sistema Linux (2): arquivos e diretórios

Referências

1. Armazenamento de arquivos

  • Um arquivo consiste principalmente em duas partes, dentry (entrada de diretório) e Inode
  • A chamada exclusão de arquivos significa excluir o Inode, mas os dados ainda estão no disco rígido e serão sobrescritos no futuro.

Insira a descrição da imagem aqui

1.1 Inode

  • Sua essência é uma estrutura que armazena as informações de atributos do arquivo. Tais como: permissões, tipo, tamanho, hora, usuário, localização do disco
  • Inode também é chamado de estrutura de gerenciamento de atributos de arquivo.A maioria dos Inodes são armazenados no disco.
  • Um pequeno número de Inodes comumente usados ​​e recentemente usados ​​será armazenado em cache na memória.

1.2 Entrada de diretório (dentry)

  • A essência de um item de diretório ainda é uma estrutura.Existem duas variáveis ​​de membro importantes {nome do arquivo, Inode,...}, e o conteúdo do arquivo (dados) é armazenado no bloco do disco.

2. Sistema de arquivos

2.1 Funções stat, fstat, fstatat e lstat

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
int fstatat(int fd, const char *pathname, struct stat *buf, int flag);
  • valor de retorno da função

    • Retorne 0 em caso de sucesso
    • Retorna -1 em caso de erro
  • Depois que o nome do caminho for fornecido

    • A função stat retornará uma estrutura de informação relacionada a este arquivo nomeado
    • A função fstat obtém informações sobre arquivos que foram abertos no descritor fd
    • A função lstat é semelhante a stat, mas quando o arquivo nomeado é um link simbólico, lstat retorna informações sobre o link simbólico em vez do arquivo referenciado pelo link simbólico.
      • stat obterá os atributos do arquivo ou diretório apontado pelo link simbólico. Se você não quiser penetrar nos símbolos, use lstat.
    • A função fstatat retorna estatísticas de arquivo para um nome de caminho relativo ao diretório atualmente aberto apontado pelo parâmetro fd.
      • O parâmetro flag controla se um link simbólico é seguido. Quando o sinalizador AT_SYMLINK_NOFOLLOW é definido, fstatat não segue o link simbólico, mas retorna informações sobre o próprio link simbólico; caso contrário, o padrão é retornar informações sobre o arquivo real apontado pelo link simbólico.
      • Se o valor do parâmetro fd for AT_FDCWD e o parâmetro pathname for um nome de caminho relativo, fstatat avaliará o parâmetro pathname em relação ao diretório atual; se pathname for um caminho absoluto, o parâmetro fd será ignorado
  • O segundo parâmetro buf é um ponteiro, que armazena atributos de arquivo e é um ponteiro de estrutura Inode . Sua forma básica é a seguinte

    struct stat {
          
          
        dev_t     st_dev;         /* ID of device containing file */
        ino_t     st_ino;         /* Inode number */
        mode_t    st_mode;        /* File type and mode */
        nlink_t   st_nlink;       /* Number of hard links */
        uid_t     st_uid;         /* User ID of owner */
        gid_t     st_gid;         /* Group ID of owner */
        dev_t     st_rdev;        /* Device ID (if special file) */
        off_t     st_size;        /* Total size, in bytes */
        blksize_t st_blksize;     /* Block size for filesystem I/O */
        blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */
        struct timespec st_atim;  /* Time of last access */
        struct timespec st_mtim;  /* Time of last modification */
        struct timespec st_ctim;  /* Time of last status change */
    };
    

Caso

  • Obtenha o tamanho do arquivo: st_size

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    
    int main(int argc, char* argv[]) {
          
          
        struct stat sbuf;
    
        int ret = stat(argv[1], &sbuf);
        if (ret == -1) {
          
          
            perror("stat error");
            exit(1);
        }
    
        printf("file size: %ld\n", sbuf.st_size);
    
        return 0;
    }
    
  • bits de permissão de arquivo
    Insira a descrição da imagem aqui

2.2 Tipos de arquivo

  • A maioria dos arquivos em um sistema UNIX são arquivos ou diretórios comuns, mas também existem outros tipos de arquivos. Os tipos de arquivo incluem o seguinte
    • arquivo normal
      • Contém algum tipo de dados. Se esses dados são texto ou dados binários, não faz diferença para o kernel UNIX
    • arquivo de diretório
      • Contém os nomes de outros arquivos e ponteiros para informações sobre esses arquivos
      • Qualquer processo com permissões de leitura em um arquivo de diretório pode ler o conteúdo do diretório, mas apenas o kernel pode gravar diretamente no arquivo de diretório.
    • bloquear arquivo especial
      • Fornece acesso em buffer a um dispositivo (como um disco) com comprimento fixo por acesso
    • arquivo especial de personagem
      • Fornece acesso sem buffer ao dispositivo, com cada acesso de comprimento variável
      • Todos os dispositivos no sistema são arquivos especiais de caracteres ou arquivos especiais de bloco
    • Tubo FIFO
      • Usado para comunicação entre processos, às vezes chamado de pipes nomeados
    • soquete
      • Usado para comunicação de rede entre processos e também pode ser usado para comunicação fora da rede entre processos em uma máquina host
    • ligação simbólica
      • Este tipo de arquivo aponta para outro arquivo

Insira a descrição da imagem aqui

Caso

  • Obtenha o tipo/permissões de arquivo: st_mode
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    
    int main(int argc, char* argv[]) {
          
          
        struct stat sbuf;
    
        // stat 会穿透符号链接,导致无法判断符号链接
        // int ret = stat(argv[1], &sbuf);
        int ret = lstat(argv[1], &sbuf);
        if (ret == -1) {
          
          
            perror("stat error");
            exit(1);
        }
    
        if (S_ISREG(sbuf.st_mode)) {
          
          
            printf("It's a regular\n");
        } else if (S_ISDIR(sbuf.st_mode)) {
          
          
            printf("It's a dir\n");
        } else if (S_ISFIFO(sbuf.st_mode)) {
          
          
            printf("It's a pipe\n");
        } else if (S_ISLNK(sbuf.st_mode)) {
          
          
            printf("It's a sym link\n");
        }
    
        return 0;
    }
    

O comando ls -l não penetra em links simbólicos; os comandos cat e vim sim.

2.3 Definir ID do usuário e definir ID do grupo

  • Existem 6 ou mais IDs associados a um processo
    • O ID de usuário real e o ID de grupo real identificam quem somos. Esses dois campos são obtidos das entradas de login no arquivo de senha no momento do login. Normalmente, esses valores não mudam durante uma sessão de login, mas o processo do superusuário possui métodos para alterá-los.
    • ID de usuário efetivo, ID de grupo efetivo e ID de grupo afiliado determinam as permissões de acesso a arquivos
    • O ID do usuário de configurações salvas e o ID do grupo de configurações salvas contêm uma cópia do ID do usuário efetivo e do ID do grupo efetivo ao executar um programa

Insira a descrição da imagem aqui

  • Normalmente, o ID do usuário efetivo é igual ao ID do usuário real e o ID do grupo efetivo é igual ao ID do grupo real.
  • Cada arquivo tem um proprietário e um proprietário de grupo
    • O proprietário é especificado por st_uid na estrutura estatística
    • O proprietário do grupo é especificado por st_gid

2.4 Permissões de acesso a arquivos

  • O valor st_mode também contém os bits de direitos de acesso ao arquivo. Todos os tipos de arquivos (diretórios, arquivos especiais de caracteres, etc.) têm direitos de acesso

  • Cada arquivo possui 9 bits de direitos de acesso

    • Use u para representar o usuário (proprietário), g para representar o grupo e o para representar outros.

Insira a descrição da imagem aqui

  • Regras de acesso a arquivos
    • Ao abrir um arquivo de qualquer tipo com um nome, você deve ter permissão de execução para cada diretório contido no nome (incluindo o diretório de trabalho atual implícito)
      • Por exemplo, para abrir o arquivo /usr/include/stdio.h, você precisa de permissão de execução nos diretórios /, /usr e /usr/include
    • A permissão de leitura de um arquivo determina se um arquivo existente pode ser aberto para leitura.
      • Isso está relacionado aos sinalizadores O_RDONLY e O_RDWR da função aberta
    • As permissões de gravação para um arquivo determinam se um arquivo existente pode ser aberto para gravação
      • Isso está relacionado aos sinalizadores O_WRONLY e O_RDWR da função aberta
    • Para especificar o sinalizador O_TRUNC em um arquivo na função open , você deve ter permissão de gravação no arquivo
    • Para criar um novo arquivo em um diretório , você deve ter permissões de gravação e execução no diretório
    • Para excluir um arquivo existente , você deve ter permissões de gravação e execução no diretório que contém o arquivo
      • Você não precisa ter permissões de leitura ou gravação no próprio arquivo.

Cada vez que um processo abre, cria ou exclui um arquivo, o kernel realiza um teste de acesso ao arquivo , e esse teste pode envolver o proprietário do arquivo (st_uid e st_gid), o ID efetivo do processo (ID efetivo do usuário e grupo efetivo ID) e O ID do grupo de afiliação do processo (se compatível). Os dois IDs de proprietário são propriedades do arquivo, enquanto os dois IDs efetivos e o ID do grupo de afiliação são propriedades do processo

2.5 Propriedade de novos arquivos e diretórios

  • O ID do usuário do novo arquivo é definido como o ID do usuário efetivo do processo. Em relação aos IDs de grupo, POSIX.1 permite que implementações selecionem um dos seguintes como ID de grupo para novos arquivos
    • O ID do grupo do novo arquivo pode ser o ID do grupo efetivo do processo
    • O ID do grupo do novo arquivo pode ser o ID do grupo do diretório no qual ele está localizado.

2.6 Função de acesso e faaccessat

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

int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
  • Mesmo que um processo já esteja em execução com privilégios de superusuário ao definir um ID de usuário, ele ainda pode querer verificar se seu usuário real pode acessar um determinado arquivo

  • valor de retorno da função

    • Se tiver sucesso, retorne 0

    • Se ocorrer um erro, -1 será retornado

    • As funções access e faccessat testam o acesso em relação a IDs de usuário reais e IDs de grupo reais

  • Se o arquivo de teste já existir, o modo é F_OK, caso contrário, o modo é o OR bit a bit das constantes listadas na figura abaixo.

Insira a descrição da imagem aqui

  • A função faccessat é idêntica à função de acesso nos dois casos seguintes. Caso contrário, faccessat calcula o caminho relativo ao diretório aberto apontado pelo parâmetro fd.

    • Uma é que o parâmetro pathname é um caminho absoluto
    • A outra é que o valor do parâmetro fd é AT_FDCWD e o parâmetro pathname é um caminho relativo.
  • O parâmetro flag pode ser usado para alterar o comportamento do faccessat

    • Se o sinalizador estiver definido como AT_EACCESS, a verificação de acesso usará o ID do usuário efetivo e o ID do grupo efetivo do processo de chamada em vez do ID do usuário real e do ID do grupo real.

Caso

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char* argv[]) {
    
    
    if (argc != 2) {
    
    
        perror("usage: access <pathname>");
        exit(1);
    }

    if (access(argv[1], R_OK) < 0) {
    
    
        perror("access error");
        exit(1);
    } else {
    
    
        printf("read access OK\n");
    }

    if (open(argv[1], O_RDONLY) < 0) {
    
    
        perror("open error");
        exit(1);
    } else {
    
    
        printf("open for reading OK\n");
    }

    return 0;
}
$ gcc access.c -o access
$ ./access fcntl.c
read access OK
open for reading OK

2.7 Função umask

#include <sys/types.h>
#include <sys/stat.h>

// mask 取值见 2.4 节图
mode_t umask(mode_t mask);
  • A função umask cria uma palavra de máscara para o processo definir o modo de arquivo e retorna o valor anterior.Esta é uma das poucas funções de retorno que não causa erro.

  • valor de retorno da função

    • O modo de arquivo anterior cria palavras mascaradas
  • Quando um processo cria um novo arquivo ou diretório, ele definitivamente usará o modo texto para criar caracteres de tela.

    • Ambas as funções open e create possuem um parâmetro mode, que especifica os bits de permissão de acesso do novo arquivo.

Caso

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int main(int argc, char* argv[]) {
    
    
    umask(0);
    if (creat("foo", RWRWRW) < 0) {
    
    
        perror("creat error for foo");
    }
    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    if (creat("bar", RWRWRW) < 0) {
    
    
        perror("creat error for bar");
    }

    return 0;
}
$ umask      # 先打印当前文件模式创建屏蔽字
0002
$ gcc umask.c -o umask
$ ./umask
$ ls -l foo bar
-rw------- 1 yxd yxd 0 9月  14 08:53 bar
-rw-rw-rw- 1 yxd yxd 0 9月  14 08:53 foo
$ umask
0002
$ umask -S   # 观察文件模式创建屏蔽字是否更改
u=rwx,g=rwx,o=rx
$ umask 027  # 更改文件模式创建屏蔽字
$ umask -S
u=rwx,g=rx,o=
  • Ao escrever um programa que cria um novo arquivo, se você quiser garantir que o bit de permissão de acesso especificado esteja ativado, deverá modificar o valor umask enquanto o processo estiver em execução. Por exemplo, se quiser garantir que qualquer usuário possa ler o arquivo, você deve definir umask como 0 . Caso contrário, quando o processo estiver em execução, um valor umask válido poderá desligar o bit de permissão
  • Alterar o modo de arquivo de um processo cria uma máscara sem afetar a máscara de seu processo pai (geralmente o shell)
  • O usuário pode definir o valor umask para controlar as permissões padrão dos arquivos criados. O valor é expresso como um número octal. Um bit representa uma permissão a ser bloqueada. Conforme mostrado na figura abaixo, após o bit correspondente ser definido, a permissão correspondente será negada.
    • Os valores umask comumente usados ​​são 002, 022 e 027
      • 002 Impedir que outros usuários gravem em seus arquivos
      • 022 Impedir que membros do grupo e outros usuários gravem em seus arquivos
      • 027 Impedir que membros do mesmo grupo gravem em seus arquivos e que outros usuários leiam, gravem ou executem seus arquivos

Insira a descrição da imagem aqui

2.8 Funções chmod, fchmod e fchmodat (alterar permissões de acesso a arquivos existentes)

#include <fcntl.h>
#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
  • valor de retorno da função

    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • A função chmod opera no arquivo especificado, enquanto a função fchmod opera no arquivo aberto.

  • Para alterar os bits de permissão de um arquivo

    • O ID de usuário efetivo do processo deve ser igual ao ID do proprietário do arquivo
    • Ou o processo deve ter privilégios de superusuário
  • O modo de parâmetro é o OR bit a bit das constantes mostradas na figura abaixo

Insira a descrição da imagem aqui

Caso

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int main(int argc, char* argv[]) {
    
    
    struct stat statbuf;

    // 对于其当前状态设置权限:先调用 stat 获得其当前权限,然后修改它
    // 显式地打开设置组 ID 位、关闭了组执行位
    if (stat("foo", &statbuf) < 0) {
    
    
        perror("stat error for foo");
        exit(1);
    }
    if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) {
    
    
        perror("chmod error for foo");
        exit(1);
    }

    // 不管文件 bar 的当前权限位如何,都将其权限设置为一个绝对值 rw-r--r--
    if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
    
    
        perror("chmod error for bar");
    }

    return 0;
}
$ gcc chmod.c -o chmod
$ ./chmod
$ ls -l foo bar
-rw-r--r-- 1 yxd yxd 0 9月  14 08:53 bar
-rw-rwSrw- 1 yxd yxd 0 9月  14 08:53 foo

2.9 Ponta de adesão

  • S_ISVTX é chamado de sticky bit
    • Se este bit for definido para um arquivo de programa executável, quando o programa for executado pela primeira vez, uma cópia do texto do programa ainda será salva na área de troca (o texto do programa são as instruções da máquina) quando ele terminar. , o que permite que o programa para ser carregado na memória mais rapidamente na próxima vez que for executado
    • Os diretórios /tmp e /var/tmp são candidatos típicos para definir o sticky bit : qualquer usuário pode criar arquivos nesses dois diretórios. As permissões para qualquer usuário (usuário, grupo e outros) nesses dois diretórios geralmente são de leitura, gravação e execução. Mas os usuários não devem poder excluir ou renomear arquivos pertencentes a outras pessoas, para os quais o sticky bit está definido no modo de arquivo de ambos os diretórios

2.10 Funções chown, fchown, fchownat e lcown

  • As seguintes funções chown podem ser usadas para alterar o ID do usuário e o ID do grupo de um arquivo

    #include <fcntl.h>
    #include <unistd.h>
    
    // 如果两个参数 owner 或 group 中的任意一个是 -1,则对应的 ID 不变
    int chown(const char *pathname, uid_t owner, gid_t group);
    int fchown(int fd, uid_t owner, gid_t group);
    int lchown(const char *pathname, uid_t owner, gid_t group);
    
    int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
    
  • valor de retorno da função

    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • As operações destas quatro funções são semelhantes, exceto que os arquivos referenciados são links simbólicos.

    • No caso de links simbólicos, lchown e fchownat (com o sinalizador AT_SYMLINK NOFOLLOW definido) alteram o proprietário do link simbólico em si, não o proprietário do arquivo para o qual o link simbólico aponta.
    • A função fchown altera o proprietário do arquivo aberto apontado pelo parâmetro fd. Como opera em um arquivo já aberto, não pode ser usada para alterar o proprietário de um link simbólico.

2.11 Comprimento do arquivo

  • O membro da estrutura estatística st_size representa o comprimento do arquivo em bytes.

    • Este campo só tem sentido para arquivos comuns, arquivos de diretório e links simbólicos
  • Para arquivos comuns, o comprimento do arquivo pode ser 0. Ao começar a ler esse arquivo, você receberá uma indicação de fim do arquivo.

  • Para diretórios, o comprimento do arquivo geralmente é um múltiplo inteiro de um número (como 16 ou 512)

  • Para links simbólicos, o comprimento do arquivo é o número real de bytes no nome do arquivo

    • Como o comprimento do arquivo de link simbólico é sempre indicado por st_size, ele não contém o byte nulo que normalmente é usado pela linguagem C para encerrar o nome.
  • Buraco de arquivo

    • Buracos são causados ​​pela definição de um deslocamento além do final do arquivo e pela gravação de alguns dados.

2.12 Truncamento de arquivo

  • Às vezes é necessário truncar alguns dados no final do arquivo para encurtá-lo
    • Truncar o comprimento de um arquivo para 0 é um caso especial e pode ser feito usando o sinalizador O_TRUNC ao abrir o arquivo.
    • Para truncar o arquivo você pode chamar as funções truncate e ftruncate
#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
  • valor de retorno

    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • Essas duas funções truncam o comprimento de um arquivo existente para o comprimento

    • Se o comprimento anterior do arquivo for maior que o comprimento, os dados além do comprimento não poderão ser acessados.
    • Se o comprimento anterior for menor que o comprimento, o comprimento do arquivo será aumentado e os dados entre o final anterior do arquivo e o novo final do arquivo serão lidos como 0 (ou seja, um buraco pode ser criado no arquivo )

2.13 Sistema de arquivos

  • Um disco pode ser dividido em uma ou mais partições. Cada partição pode conter um sistema de arquivos e os inodes são entradas de registro de comprimento fixo que contêm a maior parte das informações sobre o arquivo.

Insira a descrição da imagem aqui

  • O nó i e a parte do bloco de dados de um grupo de cilindros
    • Há uma contagem de links em cada i-node, cujo valor é o número de entradas de diretório que apontam para aquele i-node. Somente quando a contagem de links for reduzida a 0 o arquivo poderá ser excluído (ou seja, os blocos de dados ocupados pelo arquivo poderão ser liberados)
      • É por isso que “desvincular um arquivo” nem sempre significa “liberar os blocos do disco ocupados pelo arquivo”
      • É por isso que a função para excluir uma entrada de diretório é chamada de unlink em vez de delete.
    • Outro tipo de link é chamado de link simbólico. O conteúdo real do arquivo de link simbólico (no bloco de dados) contém o nome do arquivo apontado pelo link simbólico
    • O nó i contém todas as informações sobre o arquivo: tipo de arquivo, bits de permissão de acesso ao arquivo, comprimento do arquivo e ponteiros para blocos de dados do arquivo, etc. A maior parte das informações na estrutura estatística é obtida de i nós. Apenas dois dados importantes são armazenados nas entradas do diretório: nomes de arquivos e números de i-nodes.
    • Como o número do i-node em uma entrada de diretório aponta para o i-node correspondente no mesmo sistema de arquivos, uma entrada de diretório não pode apontar para um i-node em outro sistema de arquivos.
    • Quando você renomeia um arquivo sem alterar o sistema de arquivos, o conteúdo real do arquivo não é movido; apenas uma nova entrada de diretório é construída apontando para o i-node existente e a entrada de diretório antiga é excluída . A contagem de links não mudará
      • Exemplo: Renomeie o arquivo /usr/lib/foo para /usr/foo. Se /usr/lib e /usr estiverem no mesmo sistema de arquivos, o conteúdo do arquivo foo não precisa ser movido.

Insira a descrição da imagem aqui

2.14 Funções link, linkat, unlink, unlinkat e remove

Por que as entradas de diretório devem ser mantidas fora do inode e dos nomes de arquivos armazenados separadamente? Quais são os benefícios deste método de armazenamento?

  • Seu objetivo é conseguir o compartilhamento de arquivos . O Linux permite que múltiplas entradas de diretório compartilhem um inode, ou seja, bloco de disco compartilhado (dados)
  • Nomes de arquivos diferentes são interpretados como dois arquivos aos olhos humanos, mas são o mesmo arquivo aos olhos do kernel.

2.14.1 Link de função, linkat

  • A maneira de criar um link para um arquivo existente é usar a função link ou a função linkat
    #include <fcntl.h>
    #include <unistd.h>
    
    int link(const char *oldpath, const char *newpath);
    int linkat(int oldfd, const char *oldpath, int newfd, const char *newpath, int flag);
    
  • Essas duas funções criam uma nova entrada de diretório newpath que faz referência ao arquivo existente oldpath
    • Se newpath já existir, um erro será retornado. Crie apenas o último componente em newpath, o resto do caminho já deve existir
  • valor de retorno
    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • Quando o arquivo existente é um link simbólico, o parâmetro flag controla se a função linkat cria um link para o link simbólico existente ou para o arquivo apontado pelo link simbólico existente.

Caso

  • Implementar comando mv
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    
    int main(int argc, char* argv[]) {
          
          
        link (argv[1], argv[2]);
        unlink(argv[1]);    
    
        return 0;
    }
    

2.14.2 Funções unlink e unlinkat

  • Para excluir uma entrada de diretório existente, chame a função unlink
#include <fcntl.h>
#include <unistd.h>

int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
  • Essas duas funções excluem a entrada do diretório e diminuem a contagem de links do arquivo referenciado pelo nome do caminho em 1
    • Se houver outros links para o arquivo, os dados do arquivo ainda poderão ser acessados ​​através dos outros links
    • Se ocorrer um erro, nenhuma alteração será feita no arquivo
  • valor de retorno
    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • O parâmetro flag fornece uma maneira para o processo de chamada alterar o comportamento padrão da função unlinkat.
    • Quando o sinalizador AT_REMOVEDIR é definido, a função unlinkat pode excluir um diretório semelhante a rmdir.
    • Se este sinalizador estiver desmarcado, unlinkat executa a mesma operação que unlink

Somente quando a contagem de links atingir 0 o conteúdo do arquivo poderá ser excluído . Outra condição também impede a exclusão do conteúdo do arquivo: enquanto um processo estiver com o arquivo aberto, seu conteúdo não poderá ser excluído . Ao fechar um arquivo, o kernel primeiro verifica o número de processos que possuem o arquivo aberto. Se esta contagem chegar a 0, o kernel então verifica sua contagem de links. Se a contagem também for 0, então o conteúdo do arquivo será excluído.

  • Esse recurso de desvinculação é frequentemente usado por programas para garantir que, mesmo que o programa trave, os arquivos temporários que ele cria não serão deixados para trás.
    • O processo cria um arquivo com open ou create e então chama imediatamente unlink. Como o arquivo ainda está aberto, seu conteúdo não será excluído. O conteúdo do arquivo é excluído somente quando o processo fecha o arquivo ou termina (nesse caso o kernel fecha todos os arquivos abertos pelo processo)
    • Excluir um arquivo, de certa forma, apenas deixa o arquivo pronto para lançamento
    • Características da função de desvinculação: Ao limpar um arquivo, se a contagem de hard link do arquivo atingir 0, não haverá dentry correspondente, mas o arquivo não será liberado imediatamente. O sistema aguardará até que todos os processos que possuem o arquivo aberto fechem o arquivo antes que o sistema reserve algum tempo para liberá-lo.

Reciclagem implícita : Quando um processo termina, todos os arquivos abertos pelo processo serão fechados e o espaço de memória solicitado será liberado. Esse recurso do sistema é chamado de reciclagem implícita dos recursos do sistema.

Caso

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc, char* argv[]) {
    
    
    int fd, ret;
    char* p = "test of unlink\n";
    char* p2 = "after write something.\n";

    fd = open("lseek.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
    
    
        perror("open temp error");
        exit(1);
    }

    ret = unlink("lseek.txt");
    if (ret < 0) {
    
    
        perror("unlink error");
        exit(1);
    }

    // 此处的 write 实际是把内容写到了缓冲区而非磁盘区
    ret = write(fd, p, strlen(p));
    if (ret == -1) {
    
    
        perror("-----write error");
    }
    printf("hi! I'm printf\n");

    ret = write(fd, p2, strlen(p2));
    if (ret == -1) {
    
    
        perror("-----write error");
    }
    printf("Enter anykey continue\n");
    getchar();

    p[3] = 'H';

    close(fd);

    return 0;
}

2.14.3 Função de remoção

#include <stdio.h>

int remove(const char* pathname);
  • Você pode usar a função remove para desvincular um arquivo ou diretório.
    • Para arquivos, remover tem a mesma função que desvincular
    • Para diretórios, remove tem a mesma função que rmdir

2.15 Funções renomear e renomear

  • Arquivos ou diretórios podem ser renomeados usando a função rename ou a função renameat.
#include <fcntl.h> 
#include <stdio.h>

int rename(const char *oldpath, const char *newpath);
int renameat(int oldfd, const char *oldpath, int newfd, const char *newpath);
  • Se oldname se referir a um arquivo, renomeie o arquivo ou link simbólico

    • newname não pode fazer referência a um diretório se ele já existir
    • Se newname já existir e não for um diretório, exclua a entrada do diretório e renomeie oldname para newname.
    • O processo de chamada deve ter permissões de gravação no diretório que contém o nome antigo e no diretório que contém o novo nome porque serão feitas alterações em ambos os diretórios
  • Se oldname se referir a um diretório, renomeie o diretório

    • Se newname já existir, ele deverá se referir a um diretório, e o diretório deverá ser um diretório vazio (um diretório vazio significa que existem apenas entradas . e... no diretório)
    • Se newname existir (e for um diretório vazio), exclua-o primeiro e depois renomeie oldname para newname
    • Ao renomear um diretório, newname não pode conter oldname como prefixo de caminho
      • Exemplo: /usr/foo não pode ser renomeado para /usr/foo/testdir porque o nome antigo (/usr/foo) é o prefixo do caminho do novo nome e não pode ser excluído.
  • . e ... não podem ser renomeados. Mais precisamente, nem .nem ... podem aparecer na última parte de oldname e newname.

  • Como um caso especial, se oldname e newname se referirem ao mesmo arquivo, a função retornará com sucesso sem quaisquer alterações.

2.16 Links simbólicos

  • Um link simbólico é um ponteiro indireto para um arquivo. É diferente do link físico descrito na seção anterior. O link físico aponta diretamente para o nó i do arquivo.

  • A razão para a introdução de links simbólicos é evitar algumas limitações dos links físicos

    • Links físicos geralmente exigem que o link e o arquivo estejam no mesmo sistema de arquivos
    • Somente o superusuário pode criar links físicos para diretórios (se o sistema de arquivos subjacente suportar)
  • Não há restrições do sistema de arquivos sobre links simbólicos e para quais objetos eles apontam. Qualquer usuário pode criar um link simbólico para um diretório. Links simbólicos são normalmente usados ​​para mover um arquivo ou uma estrutura de diretório inteira para outro local no sistema.

Caso

  • O uso de links simbólicos pode introduzir ciclos no sistema de arquivos . A maioria das funções que procuram nomes de caminhos retornarão um erro com um valor errno de ELOOP quando isso ocorrer. Considere a seguinte sequência de comandos:
    $ mkdir foo                 # 创建一个新目录
    $ touch foo/a               # 创建一个 0 长度的文件
    $ ln -s ../foo foo/testdir  # 创建一个符号链接
    $ ls -l foo
    total 0
    -rw-rw-r-- 1 yxd yxd 0 9月  14 15:28 a
    lrwxrwxrwx 1 yxd yxd 6 9月  14 15:28 testdir -> ../foo
    
  • O comando acima cria um diretório foo, que contém um arquivo chamado a e um link simbólico apontando para foo
    • Link simbólico testdir que forma um loop
  • Esse ciclo é facilmente eliminado
    • Como unlink não segue links simbólicos, você pode desvincular o arquivo foo/testdir
  • Mas se for criado um link físico que forme esse loop, será difícil eliminá-lo
    • É por isso que a função link não permite a construção de links físicos para diretórios (a menos que o processo tenha privilégios de superusuário)

Insira a descrição da imagem aqui

  • Ao abrir um arquivo com open, se o nome do caminho passado para a função open especificar um link simbólico, open seguirá o link para o arquivo especificado. O arquivo apontado por este link simbólico não existe e open retorna um erro, indicando que não é possível abrir o arquivo.
    $ ln -s /no/such/file myfile   # 创建一个符号链接
    $ ls myfile
    myfile
    $ cat myfile                   # 试图查看文件
    cat: myfile: No such file or directory
    $ ls -l myfile
    lrwxrwxrwx 1 yxd yxd 13 9月  14 15:37 myfile -> /no/such/file
    
    • O arquivo myfile existe, mas cat diz que não existe. A razão é que myfile é um link simbólico e o arquivo apontado pelo link simbólico não existe.
    • A opção -l do comando ls possui dois prompts
      • O primeiro caractere é l, que indica que este é um link simbólico, e -> também indica que este é um link simbólico
    • O comando ls tem outra opção -F
      • Ele anexa um símbolo @ ao final do nome do arquivo do link simbólico, o que pode ajudar a identificar o link simbólico quando a opção -l não é usada.

2.17 Criando e lendo links simbólicos

  • Um link simbólico pode ser criado usando a função symlink ou symlinkat
    #include <fcntl.h>
    #include <unistd.h>
    
    int symlink(const char *target, const char *linkpath);
    int symlinkat(const char *target, int newdirfd, const char *linkpath);
    
  • valor de retorno
    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • Como a função open segue um link simbólico, é necessário que haja uma maneira de abrir o próprio link e ler os nomes no link . As funções readlink e readlinkat fornecem essa funcionalidade.
    #include <fcntl.h>
    #include <unistd.h>
    
    ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
    ssize_t readlinkat(int fd, const char *pathname, char *buf, size_t bufsiz);
    

2.18 Funções mkdir, mkdirat e rmdir

2.18.1 Funções mkdir, mkdirat (criar diretório)

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
  • valor de retorno
    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • Estas duas funções criam um novo diretório vazio
    • Entre eles, as entradas do diretório . e ... são criadas automaticamente. O modo de permissão de acesso ao arquivo especificado é modificado pela máscara de criação do modo de arquivo do processo.
    • Um erro comum é especificar o mesmo modo do arquivo (somente permissões de leitura e gravação são especificadas). No entanto, os diretórios geralmente têm pelo menos um bit de permissão de execução definido para permitir o acesso aos nomes dos arquivos no diretório.

2.18.2 Função rmdir (excluir diretório)

#include <unistd.h>

int rmdir(const char *pathname);
  • valor de retorno

    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • Use a função rmdir para excluir um diretório vazio

    • Um diretório vazio é um diretório contendo apenas . e...
  • Se esta função for chamada de forma que a contagem de links do diretório se torne 0, e nenhum outro processo tenha este diretório aberto, o espaço ocupado por este diretório será liberado.

  • Se um ou mais processos tiverem este diretório aberto quando a contagem de links atingir 0, o último link e as entradas .e... serão excluídos antes que esta função retorne.

  • Além disso, nenhum novo arquivo pode ser criado neste diretório. Mas este diretório não é liberado até que o último processo o feche

2.18.3 Permissões de arquivos e diretórios

  • Um arquivo de diretório também é um "arquivo" e seu conteúdo de arquivo é a entrada de diretório de todos os subarquivos no diretório . Você pode tentar abrir um diretório com o vim

Insira a descrição da imagem aqui

2.19 Lendo diretório

  • Qualquer usuário com acesso a um diretório pode ler o diretório, mas para evitar confusão no sistema de arquivos, somente o kernel pode gravar no diretório.
    #include <sys/types.h>
    #include <dirent.h>
    
    // 1、打开目录
    // 若成功,返回目录结构体指针;若出错,返回 NULL
    // DIR* 类似于 FILE*
    DIR *opendir(const char *name);
    DIR *fdopendir(int fd);
    
    // 2、读目录
    // 若成功,返回目录项结构体指针;若在目录尾或出错,返回 NULL,设置 errno 为相应值
    struct dirent *readdir(DIR *dirp);
    
    // 3、关闭目录
    // 若成功,返回 0;若出错,返回 -1,设置 errno 为相应值
    int closedir(DIR *dirp);
    
  • A estrutura direta definida no arquivo de cabeçalho <dirent.h> depende da implementação. A definição da implementação desta estrutura contém pelo menos os dois membros a seguir
    ino_t d_ino;       // inode 编号
    char d_name[256]   // 文件名
    

Caso

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>

int main(int argc, char* argv[]) {
    
    
    DIR* dp;
    struct dirent* sdp;

    dp = opendir(argv[1]);
    if (dp == NULL) {
    
    
        perror("opendir error");
        exit(1);
    }

    while ((sdp = readdir(dp)) != NULL) {
    
    
        if ((strcmp(sdp->d_name, ".") == 0)) {
    
    
            continue;
        }
        printf("%s\t", sdp->d_name);
    }
    printf("\n");

    closedir(dp);

    return 0;
}
$ gcc myls.c -o myls
$ ./myls ..   # 与 ls .. 命令等价

2.20 Funções chdir, fchdir e getcwd

2.20.1 Função chdir, fchdir

  • Cada processo possui um diretório de trabalho atual, que é o ponto de partida para pesquisar todos os nomes de caminhos relativos (nomes de caminhos que não começam com uma barra são nomes de caminhos relativos). O diretório de trabalho atual é um atributo do processo e o diretório inicial é um atributo do nome de login.

  • O processo chama a função chdir ou fchdir para alterar o diretório de trabalho atual

    #include <unistd.h>
    
    int chdir(const char *path);
    int fchdir(int fd);
    
  • valor de retorno

    • Se tiver sucesso, retorne 0
    • Se ocorrer um erro, -1 será retornado
  • Como o diretório de trabalho atual é um atributo do processo, ele afeta apenas o próprio processo que chama chdir e não afeta outros processos.

2.20.2 Função getcwd

  • função getcwd função

    • Começando no diretório de trabalho atual (.), use... para encontrar seu diretório de nível superior e, em seguida, leia sua entrada de diretório até que o número do i-node na entrada do diretório seja igual ao número do i-node no diretório de trabalho. diretório, encontrando assim seu nome de arquivo correspondente
    • De acordo com este método, suba camada por camada até encontrar a raiz. Desta forma, você obterá o nome do caminho absoluto completo do diretório de trabalho atual.
    #include <unistd.h>
    
    char* getcwd(char* buf, size_t size);
    
  • valor de retorno

    • Se tiver sucesso, retorne buff
    • Se ocorrer um erro, NULL será retornado
  • Dois parâmetros devem ser passados ​​para esta função, um é o endereço do buffer buf e o outro é o comprimento do tamanho do buffer (em bytes). O buffer deve ser longo o suficiente para conter o nome do caminho absoluto mais um byte nulo de terminação, caso contrário, um erro será retornado

  • A função getcwd é útil quando um aplicativo precisa retornar ao ponto inicial de seu trabalho no sistema de arquivos

    • Antes de alterar o diretório de trabalho, você pode chamar a função getcwd para salvá-lo primeiro. Após a conclusão do processamento, o nome do caminho do diretório de trabalho original salvo pode ser passado para chdir como um parâmetro de chamada, retornando assim ao ponto inicial no sistema de arquivos

2.21 Arquivos especiais do dispositivo

  • st_dev e st_rdev são dois campos que costumam causar confusão
    • O dispositivo de armazenamento no qual cada sistema de arquivos reside é representado pelos números de dispositivo principais e secundários.
      • O tipo de dados usado para o número do dispositivo é o tipo de dados básico do sistema dev_t
      • O número principal do dispositivo identifica o driver do dispositivo
      • O número menor identifica um subdispositivo específico
    • Geralmente você pode usar duas macros: maior e menor para acessar os números de dispositivos maiores e menores.
    • O valor st_dev associado a cada nome de arquivo no sistema é o número do dispositivo do sistema de arquivos que contém o nome do arquivo e seu nó i correspondente
    • Somente arquivos especiais de caracteres e arquivos especiais de bloco possuem o valor st_rdev , que contém o número do dispositivo real.

Caso de passagem de diretório recursivo

1. Análise de ideias

Insira a descrição da imagem aqui

2. Implementação de código

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <dirent.h>

void isFile(char* name);

// 打开目录读取,处理目录
void read_dir(char* dir, void (*func)(char*)) {
    
    
    char path[256];
    DIR* dp;
    struct dirent *sdp;

    dp = opendir(dir);
    if (dp == NULL) {
    
    
        perror("opendir error");
        return;
    }

    // 读取目录项
    while ((sdp = readdir(dp)) != NULL) {
    
    
        if (strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0) {
    
    
            continue;
        }

        // 目录项本身不可访问, 拼接 目录/目录项
        sprintf(path, "%s/%s", dir, sdp->d_name);

        // 判断文件类型,目录递归进入,文件显示名字/大小
        (*func)(path);
    }
    closedir(dp);

    return;
}

void isFile(char* name) {
    
    
    int ret = 0;
    struct stat sub;

    // 获取文件属性, 判断文件类型
    ret = stat(name, &sub);
    if (ret == -1) {
    
    
        perror("stat error");
        return;
    }

    // 是目录文件
    if (S_ISDIR(sub.st_mode)) {
    
    
        read_dir(name, isFile);
    }

    // 是普通文件, 直接打印名字/大小
    printf("%10s\t\t%ld\n", name, sub.st_size);

    return;
}

int main(int argc, char* argv[]) {
    
    
    // 命令行参数个数 argc = ---→ ./ls-R
    // 命令行参数列表 argv[1]---→ ./ls-R /home/test
    if (argc == 1) {
    
      // 判断命令行参数
        isFile(".");
    } else {
    
    
        isFile(argv[1]);
    }

    return 0;
}
$ gcc ls-R.c -o ls-R
$ ./ls-R 
   ./fcntl		8384
 ./mycat.c		262
  ./ls-R.c		943
  ./fcntl2		8432
    ./ls-R		8768
         .		4096

Acho que você gosta

Origin blog.csdn.net/qq_42994487/article/details/132910626
Recomendado
Clasificación