Подтвердить что ты не робот

Понимание и декодирование значения режима файла из выхода функции stat

Я пытался понять, что именно происходит в нижеприведенном коде. Но я не могу это понять.

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

Допустим, что значение моего $mode равно 33188

$mode и 07777 дает value = 420

  • - значение $mode десятичное число?

  • почему мы выбираем 07777 и почему мы делаем поразряд и операцию. Я не могу поддаться логике здесь.

4b9b3361

Ответ 1

Режим из вашего вопроса соответствует регулярному файлу с 644 правами доступа (чтение и запись для владельца и доступно только для чтения для всех остальных), но не задумывайтесь об этом.

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

Значение $mode можно рассматривать как десятичное целое, но это не особенно полезно. Видя, что восьмеричное представление дает нечто более знакомое.

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

Побитовое И с 07777 дает последние двенадцать битов двоичного представления чисел. В режиме Unix эта операция дает бит разрешения или режима и отбрасывает информацию о типе.

$ perl -e 'printf "%d\n", (stat "foo")[2] & 07777'  # decimal, not useful
420
$ perl -e 'printf "%o\n", (stat "foo")[2] & 07777'  # octal, eureka!
644

Более удобный способ сделать это ниже. Читайте все подробности.


Биты режима

Третий элемент, возвращаемый из stat (который соответствует st_mode в struct stat), представляет собой поле бит , где разные позиции битов являются двоичными флагами.

Например, один бит в st_mode именах POSIX S_IWUSR. Файл или каталог, режим которого имеет этот бит, доступен для записи его владельцем. Связанный бит S_IROTH, который при установке означает, что другие пользователи (т.е. Ни владелец, ни группа) не могут прочитать этот конкретный файл или каталог.

Документация perlfunc для stat дает имена бит общедоступного режима. Мы можем проверить их значения.

#! /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;

В Red Hat Enterprise Linux и других операционных системах семейства System V выход указанной программы будет

S_ISTXT    - undefined
S_IFWHT    - undefined
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

Свертывание бит

Цифры выше восьмеричные (база 8), поэтому любая заданная цифра должна быть 0-7 и имеет значение места 8 n где n - нулевое число мест слева от точка радиуса. Чтобы увидеть, как они сопоставляются с битами, восьмеричное имеет удобное свойство, которое каждая цифра соответствует трем битам. Четыре, два и 1 являются точными степенями двух, поэтому в двоичном выражении они равны 100, 10 и 1 соответственно. Семь (= 4 + 2 + 1) в двоичном формате равно 111, поэтому 70 8 равно 111000 2. В последнем примере показано, как преобразование вперед и назад является простым.

С полем бит вы не заботитесь точно о том, какое значение имеет бит в этой позиции, но является ли оно нулевым или ненулевым, поэтому

if ($mode & $mask) {

проверяет, установлен ли какой-либо бит в $mode, соответствующий $mask. Для простого примера, учитывая 4-битовое целое число 1011 и маску 0100, их побитовое И является

  1011
& 0100
------
  0000

Итак, бит в этой позиции ясен - в отличие от маски, скажем, 0010 или 1100.

Удаление наиболее значимого бита 1011 выглядит как

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

Напомним, что ~ в Perl побитовое дополнение.

Для полноты, установите бит с поразрядным ИЛИ, как в

$bits |= $mask;

Разрешения октала и файла

Прямое отображение восьмеричных цифр на три бита удобно для разрешений Unix, потому что они входят в группы из трех. Например, разрешения для программы, которая выдала вышеприведенный результат, -

-rwxr-xr-x 1 gbacon users 1096 Feb 24 20:34 modebits

То есть владелец может читать, писать и выполнять; но все остальные могут читать и исполнять. В восьмеричном, это 755 - компактное сокращение. В приведенной выше таблице заданные биты в режиме

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

Мы можем разложить режим из вашего вопроса, добавив несколько строк в программу выше.

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;

Тест режима должен быть более осторожным, поскольку некоторые из масок являются сокращенными для нескольких бит. Тестирование того, что мы получаем точную маску назад, позволяет избежать ложных срабатываний, когда некоторые из битов установлены, но не все.

Цикл также очищает биты от всех обнаруженных ударов, поэтому в конце мы можем проверить, что мы учли каждый бит. Выходной сигнал

Bits set in mode 33188:
  - S_IFREG
  - S_IRUSR
  - S_IWUSR
  - S_IRGRP
  - S_IROTH

Никакого дополнительного предупреждения, поэтому мы получили все.

Это волшебство 07777

Преобразование 7777 8 в двоичный код дает 0b111_111_111_111. Напомним, что 7 8 - 111 2, а четыре 7s соответствуют 4 × 3. Эта маска полезна для выбора заданных бит за последние двенадцать. Оглядываясь назад на бит-маски, которые мы сгенерировали ранее

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

мы видим, что последние 9 бит являются разрешениями для пользователя, группы и других. Три бита, предшествующие этим, - setuid, setgroupid и то, что иногда называют липким битом. Например, полный режим sendmail в моей системе - -rwxr-sr-x или 34285 10. Побитовое И работает

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

Высокий бит в режиме, который отбрасывается, S_IFREG, указывает, что он является обычным файлом. Обратите внимание, насколько яснее режим, выраженный в восьмеричном, сравнивается с той же информацией в десятичной или двоичной форме.

В документации stat упоминается полезная функция.

& hellip; и функции S_IF*

S_IMODE($mode)
часть $mode, содержащая бит разрешения и биты setuid/setgid/sticky

В ext/Fcntl/Fcntl.xs мы найдем его реализацию и знакомую константу в последней строке.

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);

Чтобы избежать неправильной практики магических чисел в исходном коде, напишите

my $permissions = S_IMODE $mode;

Использование S_IMODE и других функций, доступных из модуля Fcntl, также скрывает низкоуровневое разбиение бит и фокусируется на информации о доменном уровне, которую хочет программа. Документация продолжается

S_IFMT($mode)
часть $mode, содержащая тип файла, который может быть битовым (например) S_IFREG или со следующими функциями

# 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)

Использование этих констант и функций сделает ваши программы более четкими, более прямо выражая ваши намерения.

Ответ 2

Это объясняется в perldoc -f stat, где я предполагаю, что вы нашли этот пример:

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.

Вывод printf "%04o", 420 равен 0644, который является правами на ваш файл. 420 - это просто десятичное представление восьмеричного числа 0644.

Если вы попытаетесь напечатать номера в двоичной форме, легче увидеть:

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

Как вы заметили, побитовое and удаляет крайний левый бит в приведенном выше номере, который предположительно представляет тип файла, оставляя вам только разрешения на файл. Это число 07777 является двоичным числом:

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

который действует как "маска" в побитом and. Поскольку 1 и 1 = 1 и 0 и 1 = 0, это означает, что любой бит, который не соответствует 1 в 07777, установлен в 0.