ti-enxame.com

compreender e decodificar o valor do modo de arquivo da saída da função estatística

Tenho tentado entender o que exatamente está acontecendo no código abaixo mencionado. Mas não consigo entender.

$mode = (stat($filename))[2];
printf "Permissions are %04o\n", $mode & 07777;

Digamos que meu valor de $ mode é 33188

$ mode & 07777 produz um valor = 42

  • o valor do modo $ é um número decimal?

  • por que estamos escolhendo 07777 e por que estamos fazendo uma operação bit a bit. Eu não sou capaz de entender a lógica aqui.

23
chidori

O modo de sua pergunta corresponde a um arquivo normal com 644 permissões (leitura e gravação para o proprietário e somente leitura para todos os outros), mas não acredite na minha palavra.

$ touch foo 
 $ chmod 644 foo 
 $ Perl -le 'print + (stat "foo") [2]' 
 33188

O valor de $mode pode ser visto como um inteiro decimal, mas fazer isso não é particularmente esclarecedor. Ver a representação octal dá algo um pouco mais familiar.

$ Perl -e 'printf "% o\n", (stat "foo") [2]' 
 100644

AND bit a bit com 07777 dá os últimos doze bits da representação binária de um número. Com um modo Unix, esta operação dá a permissão ou bits de modo e descarta qualquer tipo de informação.

$ Perl -e 'printf "% d\n", (stat "foo") [2] & 07777' # decimal, não é útil 
 420 
 $ Perl -e 'printf "% o\n ", (stat" foo ") [2] & 07777 '# octal, eureka! 
 644

Uma maneira melhor de fazer isso está abaixo. Continue lendo para todos os detalhes.


Bits de modo

O terceiro elemento retornado de stat (que corresponde a st_mode Em struct stat) É um campo de bit onde as diferentes posições de bit são sinalizadores binários.

Por exemplo, um bit em st_mode Nomes POSIX S_IWUSR. Um arquivo ou diretório cujo modo possui este conjunto de bits pode ser escrito por seu proprietário. Um bit relacionado é S_IROTH Que, quando definido, significa que outros usuários ( ou seja,, nem o proprietário nem no grupo) podem ler esse arquivo ou diretório específico.

O documentação perlfunc para stat fornece os nomes dos bits de modo comumente disponíveis. Podemos examinar seus valores.

#! /usr/bin/env Perl

use strict;
use warnings;
use Fcntl ':mode';

my $perldoc_f_stat = q(
  # Permissions: read, write, execute, for user, group, others.
  S_IRWXU S_IRUSR S_IWUSR S_IXUSR
  S_IRWXG S_IRGRP S_IWGRP S_IXGRP
  S_IRWXO S_IROTH S_IWOTH S_IXOTH

  # Setuid/Setgid/Stickiness/SaveText.
  # Note that the exact meaning of these is system dependent.
  S_ISUID S_ISGID S_ISVTX S_ISTXT

  # File types.  Not necessarily all are available on your system.
  S_IFREG S_IFDIR S_IFLNK S_IFBLK S_IFCHR S_IFIFO S_IFSOCK S_IFWHT S_ENFMT
);

my %mask;
foreach my $sym ($perldoc_f_stat =~ /\b(S_I\w+)\b/g) {
  my $val = eval { no strict 'refs'; &$sym() };
  if (defined $val) {
    $mask{$sym} = $val;
  }
  else {
    printf "%-10s - undefined\n", $sym;
  }
}

my @descending = sort { $mask{$b} <=> $mask{$a} } keys %mask;
printf "%-10s - %9o\n", $_, $mask{$_} for @descending;

No Red Hat Enterprise Linux e outros sistemas operacionais da família System V, a saída do programa acima será

S_ISTXT - indefinido 
 S_IFWHT - indefinido 
 S_IFSOCK - 140000 
 S_IFLNK - 120000 
 S_IFREG - 100000 
 S_IFBLK - 60000 
 S_IFDIR - 40000 
 S_IFCHR - 20000 
 S_IFIFO - 10000 
 S_ISUID - 4000 
 S_ISGID - 2000 
 S_ISVTX - 1000 
 S_IRWXU - 700 
 S_IRUSR - 400 
 S_IWUSR - 200 
 S_IXUSR - 100 
 S_IRWXG - 70 
 S_IRGRP - 40 
 S_IWGRP - 20 
 S_IXGRP - 10 
 S_IRWXO - 7 
 S_IROTH - 4 
 S_IWOTH - 2 
 S_IXOTH - 1

Brincando

Os números acima são octais (base 8), então qualquer dígito deve ser 0-7 e tem valor posicional 8 n, onde n é o número baseado em zero de casas à esquerda do ponto de raiz. Para ver como eles mapeiam para bits, octal tem a propriedade conveniente de que cada dígito corresponde a três bits. Quatro, dois e 1 são potências exatas de dois, portanto, em binário, são 100, 10 e 1, respectivamente. Sete (= 4 + 2 + 1) em binário é 111, então 708 é 1110002. O último exemplo mostra como a conversão para frente e para trás é direta.

Com um campo de bits, você não se importa exatamente o quê o valor de um bit nessa posição é mas seja é zero ou diferente de zero, então

if ($mode & $mask) {

testa se algum bit em $mode correspondente a $mask está definido. Para um exemplo simples, dado o inteiro de 4 bits 1011 e uma máscara 0100, seu AND bit a bit é

  1011
& 0100
------
  0000

Portanto, o bit nessa posição é claro - em oposição a uma máscara de, digamos, 0010 ou 1100.

Limpar o bit mais significativo de 1011 parece

    1011      1011
& ~(1000) = & 0111
            ------
              0011

Lembre-se de que ~ Em Perl é um complemento bit a bit.

Para completar, defina um pouco com bit a bit OR como em

$bits |= $mask;

Permissões octais e de arquivo

O mapeamento direto de um dígito octal para três bits é conveniente para permissões Unix porque eles vêm em grupos de três. Por exemplo, as permissões para o programa que produziu a saída acima são

-rwxr-xr-x 1 usuários gbacon 1096 24 de fevereiro 20:34 modebits

Ou seja, o proprietário pode ler, escrever e executar; mas todos os outros podem ler e executar. Em octal, é 755 - uma abreviação compacta. Em termos da tabela acima, os bits definidos no modo são

  • S_IRUSR
  • S_IWUSR
  • S_IXUSR
  • S_IRGRP
  • S_IXGRP
  • S_IROTH
  • S_IXOTH

Podemos decompor o modo de sua pergunta adicionando algumas linhas ao programa acima.

my $mode = 33188;
print "\nBits set in mode $mode:\n";
foreach my $sym (@descending) {
    if (($mode & $mask{$sym}) == $mask{$sym}) {
        print "  - $sym\n";
        $mode &= ~$mask{$sym};
    }
}

printf "extra bits: %o\n", $mode if $mode;

O teste de modo deve ser mais cuidadoso porque algumas das máscaras são abreviações para vários bits. Testar se obtemos a máscara exata de volta evita falsos positivos quando alguns dos bits são definidos, mas não todos.

O loop também limpa os bits de todos os acertos detectados, de modo que, no final, possamos verificar se contabilizamos cada bit. A saída é

Bits definidos no modo 33188: 
 - S_IFREG 
 - S_IRUSR 
 - S_IWUSR 
 - S_IRGRP 
 - S_IROTH

Sem aviso extra, temos tudo.

Essa magia 07777

Convertendo 77778 para binário dá 0b111_111_111_111. Lembre-se de que 78 é 1112, e quatro 7s correspondem a 4 × 3 uns. Esta máscara é útil para selecionar os bits definidos nos últimos doze. Olhando para as máscaras de bits que geramos anteriormente

S_ISUID - 4000 
 S_ISGID - 2000 
 S_ISVTX - 1000 
 S_IRWXU - 700 
 S_IRWXG - 70 
 S_IRWXO - 7

vemos que os últimos 9 bits são as permissões para usuário, grupo e outros. Os três bits que precedem esses são setuid, setgroupid e o que às vezes é chamado de sticky bit. Por exemplo, o modo completo de sendmail em meu sistema é -rwxr-sr-x Ou 3428510. O bit a bit AND acaba sendo

  (dec)      (oct)                (bin)
  34285     102755     1000010111101101
&  4095 = &   7777 = &     111111111111
-------   --------   ------------------
   1517 =     2755 =        10111101101

O bit alto no modo que é descartado é S_IFREG, O indicador de que é um arquivo regular. Observe como o modo expresso em octal é muito mais claro quando comparado com as mesmas informações em decimal ou binário.

A stat documentação menciona uma função útil.

… E as funções S_IF* São

S_IMODE($mode)
a parte de $mode contendo os bits de permissão e os bits setuid/setgid/sticky

Em ext/Fcntl/Fcntl.xs , encontramos sua implementação e uma constante familiar na última linha.

void
S_IMODE(...)
    PREINIT:
        dXSTARG;
        SV *mode;
    PPCODE:
        if (items > 0)
            mode = ST(0);
        else {
            mode = &PL_sv_undef;
            EXTEND(SP, 1);
        }
        PUSHu(SvUV(mode) & 07777);

Para evitar a má prática de números mágicos no código-fonte, escreva

my $permissions = S_IMODE $mode;

O uso de S_IMODE E outras funções disponíveis no módulo Fcntl também esconde a manipulação de bits de baixo nível e concentra-se nas informações de nível de domínio que o programa deseja. A documentação continua

S_IFMT($mode)
a parte de $mode que contém o tipo de arquivo que pode ser bit-anded com (por exemplo) S_IFREG ou com as seguintes funções

# The operators -f, -d, -l, -b, -c, -p, and -S.
S_ISREG($mode) S_ISDIR($mode) S_ISLNK($mode)
S_ISBLK($mode) S_ISCHR($mode) S_ISFIFO($mode) S_ISSOCK($mode)

# No direct -X operator counterpart, but for the first one
# the -g operator is often equivalent.  The ENFMT stands for
# record flocking enforcement, a platform-dependent feature.
S_ISENFMT($mode) S_ISWHT($mode)

O uso dessas constantes e funções tornará seus programas mais claros, expressando mais diretamente sua intenção.

33
Greg Bacon

Isso é explicado em perldoc -f stat , que é onde eu suponho que você encontrou este exemplo:

Because the mode contains both the file type and its
permissions, you should mask off the file type portion and
(s)printf using a "%o" if you want to see the real permissions.

A saída de printf "%04o", 420 é 0644 que são as permissões em seu arquivo. 420 é apenas a representação decimal do número octal 0644.

Se você tentar imprimir os números na forma binária, será mais fácil ver:

Perl -lwe 'printf "%016b\n", 33188'
1000000110100100
Perl -lwe 'printf "%016b\n", 33188 & 07777'
0000000110100100

Como você notará, bit a bit and remove o bit mais à esquerda no número acima, que presumivelmente representa o tipo de arquivo, deixando você apenas com as permissões do arquivo. Este número 07777 é o número binário:

Perl -lwe 'printf "%016b\n", 07777'
0000111111111111

Que atua como uma "máscara" no bit a bit and. Como 1 & 1 = 1 e 0 & 1 = 0, significa que qualquer bit que não corresponda a 1 em 07777 é definido como 0.

7
TLP