从stat函数输出中理解和解码文件模式值

chi*_*ori 22 unix linux permissions perl stat

我一直试图了解下面提到的代码到底发生了什么.但我无法理解它.

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

让我说我的$ mode值是33188

$ mode&07777产生一个值= 420

  • $模式值是十进制数?

  • 为什么我们选择07777以及为什么我们要做一个按位和操作.我无法在这里取消逻辑.

Gre*_*con 30

您问题中的模式对应于具有644权限的常规文件(所有者的读写和其他人的只读),但不要相信我的话.

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

$mode 可以被视为十进制整数,但这样做并不是特别有启发性.看到八进制表示给出了一些更熟悉的东西.

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

按位与07777给出数字二进制表示的最后12位.在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_modein struct stat)返回的第三个元素是位字段,其中不同的位位置是二进制标志.

例如,st_modePOSIX名称中的一位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;
Run Code Online (Sandbox Code Playgroud)

在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都是2的精确幂,所以在二进制中,它们分别是100,10和1.二进制中的七(= 4 + 2 + 1)是111,因此70 8是111000 2.后一个例子说明了如何来回转换是直截了当的.

与位域,你不在乎究竟是什么在那个位置有点值,但是否是零或非零,所以

if ($mode & $mask) {
Run Code Online (Sandbox Code Playgroud)

测试是否设置了$mode对应的任何位$mask.举一个简单的例子,给定4位整数1011和掩码0100,它们的按位AND是

  1011
& 0100
------
  0000
Run Code Online (Sandbox Code Playgroud)

因此,该位置的位是明确的 - 而不是例如0010或1100的掩模.

清除1011的最重要部分看起来像

    1011      1011
& ~(1000) = & 0111
            ------
              0011
Run Code Online (Sandbox Code Playgroud)

回想一下,~在Perl中是按位补码.

为完整起见,请按位进行按位OR设置

$bits |= $mask;
Run Code Online (Sandbox Code Playgroud)

八进制和文件权限

八进制数字直接映射到三位对于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;
Run Code Online (Sandbox Code Playgroud)

模式测试必须更加小心,因为一些掩码是多位的简写.测试我们得到精确的掩码可以避免在设置某些位时出现误报,但不是全部.

该循环还清除了所有检测到的命中的位,因此最后我们可以检查我们是否考虑了每个位.输出是

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,四个7对应于4×3.此掩码对于选择最后十二个中的设置位非常有用.回顾我们之前生成的位掩码

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

我们看到最后9位是用户,组和其他人的权限.前面的三个位是setuid,setgroupid,有时也称为sticky bit.例如,sendmail我系统上的完整模式是-rwxr-sr-x或34285 10.按位AND可行

  (dec)      (oct)                (bin)
  34285     102755     1000010111101101
&  4095 = &   7777 = &     111111111111
-------   --------   ------------------
   1517 =     2755 =        10111101101
Run Code Online (Sandbox Code Playgroud)

被丢弃的模式中的高位是S_IFREG指示它是常规文件.注意当与十进制或二进制中的相同信息进行比较时,以八进制表示的模式有多清楚.

stat文件提到了一个有用的功能.

......而且S_IF*功能是

S_IMODE($mode)
的部分$mode包含所述许可位和的setuid/setgid的/粘性位

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);
Run Code Online (Sandbox Code Playgroud)

为了避免源代码中的魔术数字的不良做法,写

my $permissions = S_IMODE $mode;
Run Code Online (Sandbox Code Playgroud)

使用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)
Run Code Online (Sandbox Code Playgroud)

使用这些常量和函数可以更直接地表达您的意图,从而使您的程序更加清晰.


TLP*_*TLP 7

它在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.
Run Code Online (Sandbox Code Playgroud)

输出printf "%04o", 4200644您文件的权限.420只是八进制数的十进制表示0644.

如果您尝试以二进制形式打印数字,则更容易看到:

perl -lwe 'printf "%016b\n", 33188'
1000000110100100
perl -lwe 'printf "%016b\n", 33188 & 07777'
0000000110100100
Run Code Online (Sandbox Code Playgroud)

正如您将注意到的那样,按位and删除上面数字中最左边的位,这可能代表文件类型,只留下文件权限.这个数字07777是二进制数:

perl -lwe 'printf "%016b\n", 07777'
0000111111111111
Run Code Online (Sandbox Code Playgroud)

它在按位中充当"掩码" and.由于1&1 = 1,0&1 = 0,这意味着07777中与1不匹配的任何位都设置为0.