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

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

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

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

$mode & 07777 дает значение = 420

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

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

2 ответов


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

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

значение $mode can рассматриваться как десятичное целое число, но это не особенно актуальным. Видение восьмеричного представления дает что-то более знакомое.

$ 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

более хороший способ сделать это ниже. Читайте дальше для всех деталей.


Mode Bits

третий элемент, возвращенный из 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 и имеет значение места 8n, где n - это нулевое число мест слева от точки радиуса. Чтобы увидеть, как они сопоставляются с битами, octal имеет удобное свойство, что каждая цифра соответствует трем битам. Четыре, два, и 1 все точные степени двух, поэтому в двоичном формате они равны 100, 10 и 1 соответственно. Семь (= 4 + 2 + 1) в двоичном - 111, так что 708 это 1110002. Последний пример показывает, как преобразование назад и вперед просто.

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

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-компактная стенография. С точки зрения приведенной выше таблицы, набор бит в режиме are

  • 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

преобразование 77778 двоичным дает 0b111_111_111_111. Напомним, что 78 is 1112, а четыре 7-ки соответствуют 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 или 3428510. Побитовое и работает до будь

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

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

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

... и S_IF* функции

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

на 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 содержащий тип файла, который может быть бит-anded с (например) 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)

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


это объясняется в perldoc -F в стат, где, я полагаю, вы нашли такой пример:

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 is 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.