【问题标题】:understanding and decoding the file mode value from stat function output从 stat 函数输出中理解和解码文件模式值
【发布时间】:2013-02-09 22:15:41
【问题描述】:

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

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

假设我的 $mode 值为 33188

$mode & 07777 产生一个值 = 420

  • $mode 值是十进制数吗?

  • 我们为什么选择 07777 以及为什么我们要进行按位与运算。我无法理解这里的逻辑。

【问题讨论】:

  • 不是“十进制数”;这是一个数字。如果以十进制打印,则以十进制打印,如果以八进制打印,则以八进制打印。

标签: linux perl unix permissions stat


【解决方案1】:

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

$ touch foo
$ chmod 644 富
$ perl -le '打印 +(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' # 十进制,没用
420
$ perl -e 'printf "%o\n", (stat "foo")[2] & 07777' # 八进制,尤里卡!
644

下面是一个更好的方法。请继续阅读以了解所有详细信息。


模式位

stat(对应于struct stat 中的st_mode)返回的第三个元素是bit field,其中不同的位位置是二进制标志。

例如,st_mode POSIX 名称 S_IWUSR 中的一位。模式设置了该位的文件或目录可由其所有者写入。一个相关的位是S_IROTH,设置后意味着其他用户(,既不是所有者也不是组中的用户)可以读取该特定文件或目录。

perlfunc documentation for 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 - 未定义
S_IFWHT - 未定义
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 em> 是小数点左侧从零开始的位数。要查看它们如何映射到位,八进制具有每个数字对应于三个位的便利属性。四、二和 1 都是 2 的精确幂,因此在二进制中,它们分别是 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——一个紧凑的速记。就上表而言,模式中的设置位为

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

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

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

在模式 33188 中设置的位:
  - S_IFREG
  - S_IRUSR
  - S_IWUSR
  - S_IRGRP
  - S_IROTH

没有额外的警告,所以我们得到了一切。

那个魔法 07777

将 77778 转换为二进制得到0b111_111_111_111。回想一下,78 是 1112,四个 7 对应 4×3 个。此掩码对于选择最后 12 个中的设置位很有用。回顾我们之前生成的位掩码

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。按位 AND 的结果是

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

模式中被丢弃的高位是S_IFREG,表明它是一个普通文件。请注意,以八进制表示的模式与以十进制或二进制表示的相同信息相比要清晰得多。

stat documentation 提到了一个有用的功能。

… 和 S_IF* 函数是

S_IMODE($mode)
$mode 中包含权限位和 setuid/setgid/sticky 位的部分

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

为了避免magic numbers在源代码中的不良做法,写

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)

使用这些常量和函数将通过更直接地表达您的意图使您的程序更清晰。

【讨论】:

  • 非常感谢您的简短解释。但我就是不明白。如果我使用其他数字 say..06666 来执行我的按位与运算怎么办?为什么我不能选择一些随机数并尝试对我们的 $mode 输出执行按位与操作
  • @chidori 使用不同的掩码和按位 AND 允许您选择要查询的位。获取使用S_IMODE($mode) 设置的所有内容。使用$mode &amp; 06666“屏蔽”粘性位以及所有者、组和其他人的所有执行权限位。要仅获取组的权限,请写 $mode &amp; 0070。更好的是,使用符号掩码来明确您正在选择的内容,例如if ($mode &amp; (S_IWUSR | S_IWGRP | S_IWOTH)) { ... } 来测试是否有任何写入权限位。
  • 在反复阅读您的详细解释后,我现在能够理解我们为什么使用 07777 了。非常感谢:)
  • @chidori 太棒了!我很高兴你取得了突破。
【解决方案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,这意味着 07777 中任何与 1 不匹配的位都设置为 0。

【讨论】:

  • 谢谢。现在我对它有了更好的理解,但我对此仍有一些疑问。好的,所以 07777 中的 0 用于屏蔽文件类型,但那里的 7777 是什么?为什么我们选择 07777 而不是 03333 等任何其他数字。我知道它与权限有关,但有点模糊。请帮助我理解这一点
  • @chidori 不,0 是用来告诉 perl 那是一个八进制数。数字是一样的,只是表示方式不同。 077774095 相同,与0b111111111111 相同。如果你有 14 个橘子,你也有0b1110 橘子,或者016,甚至0xE
猜你喜欢
  • 1970-01-01
  • 2015-05-16
  • 1970-01-01
  • 2022-09-29
  • 2014-08-05
  • 2019-11-28
  • 2020-05-11
  • 1970-01-01
相关资源
最近更新 更多