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

标签 linux perl unix permissions stat

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

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

假设我的 $mode 值为 33188

$mode & 07777 产生一个值 = 420

  • $mode 值是十进制数吗?

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

最佳答案

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

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

The value of $mode can be viewed as a decimal integer, but doing so is not particularly enlightening. Seeing the octal representation gives something a bit more familiar.

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

Bitwise AND with 07777 gives the last twelve bits of a number’s binary representation. With a Unix mode, this operation gives the permission or mode bits and discards any type information.

$ 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

A nicer way to do this is below. Read on for all the details.


Mode Bits

The third element returned from stat (which corresponds to st_mode in struct stat) is a bit field where the different bit positions are binary flags.

For example, one bit in st_mode POSIX names S_IWUSR. A file or directory whose mode has this bit set is writable by its owner. A related bit is S_IROTH that when set means other users (i.e., neither the owner nor in the group) may read that particular file or directory.

The perlfunc documentation for stat gives the names of commonly available mode bits. We can examine their values.

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

Bit twiddling

The numbers above are octal (base 8), so any given digit must be 0-7 and has place value 8n, where n is the zero-based number of places to the left of the radix point. To see how they map to bits, octal has the convenient property that each digit corresponds to three bits. Four, two, and 1 are all exact powers of two, so in binary, they are 100, 10, and 1 respectively. Seven (= 4 + 2 + 1) in binary is 111, so then 708 is 1110002. The latter example shows how converting back and forth is straightforward.

With a bit field, you don’t care exactly what the value of a bit in that position is but whether it is zero or non-zero, so

if ($mode & $mask) {

测试是否设置了对应于$mask$mode 中的任何位。举个简单的例子,给定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

That is, the owner may read, write, and execute; but everyone else may read and execute. In octal, this is 755—a compact shorthand. In terms of the table above, the set bits in the mode are

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

We can decompose the mode from your question by adding a few lines to the program above.

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

No extra warning, so we got everything.

That magic 07777

Converting 77778 to binary gives 0b111_111_111_111. Recall that 78 is 1112, and four 7s correspond to 4×3 ones. This mask is useful for selecting the set bits in the last twelve. Looking back at the bit masks we generated earlier

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

we see that the last 9 bits are the permissions for user, group, and other. The three bits preceding those are the setuid, setgroupid, and what is sometimes called the sticky bit. For example, the full mode of sendmail on my system is -rwxr-sr-x or 3428510. The bitwise AND works out to be

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

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

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

… and the S_IF* functions are

S_IMODE($mode)
the part of $mode containing the permission bits and the setuid/setgid/sticky bits

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)
the part of $mode containing the file type which can be bit-anded with (for example) S_IFREG or with the following functions

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

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

关于linux - 从 stat 函数输出中理解和解码文件模式值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15055634/

相关文章:

java - 从带有参数的 shellscript 调用 java

linux - `sudoedit` 和 `sudo vim` 有什么区别?

perl - 这个混淆的 bash/perl 代码有什么作用?

macos - 为什么使用 ./helloworld 而不是 helloworld 直接从终端运行我的程序?

perl - 为什么这会返回空字符串?

regex - Perl 将正则表达式匹配组分配给变量

linux - 用于在工作区之间切换的键盘快捷键

linux - 使用 power iso 提取 VMDK 文件

ruby-on-rails - 执行 Rails 应用程序时出现问题。版本问题

java - 使用 Java 进程设置 Linux ACL 权限