从stat函数输出中了解和解码文件模式值 [英] understanding and decoding the file mode value from stat function output

查看:131
本文介绍了从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

可以将$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返回的第三个元素(对应于struct stat中的st_mode)是位字段其中不同的位位置是二进制标志.

例如,st_mode POSIX名称S_IWUSR中的一位.设置了该位的模式的文件或目录可由其所有者写.一个相关的位是S_IROTH,当设置该位时,意味着其他用户(即,所有者或组中的任何用户)都不能读取该特定文件或目录.

stat perlfunc文档给出了常用模式位的名称.我们可以检查它们的值.

#! /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 是小数点左侧的从零开始的位数.要查看它们如何映射到位,八进制具有方便的属性,即每个数字对应于三个位. 4、2和1都是2的精确幂,因此以二进制形式分别是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 ,四个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或34285 10 .按位与的结果是

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

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

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

…和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)

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

I have been trying to understand what exactly is happening in the below mentioned code. But i am not able to understand it.

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

Lets say my $mode value is 33188

$mode & 07777 yields a value = 420

  • is the $mode value a decimal number ?

  • why we are choosing 07777 and why we are doing a bitwise and operation. I am not able to underand the logic in here.

解决方案

The mode from your question corresponds to a regular file with 644 permissions (read-write for the owner and read-only for everyone else), but don’t take my word for it.

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

On Red Hat Enterprise Linux and other operating systems in the System V family, the output of the above program will be

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

tests whether any bit in $mode corresponding to $mask is set. For a simple example, given the 4-bit integer 1011 and a mask 0100, their bitwise AND is

  1011
& 0100
------
  0000

So the bit in that position is clear—as opposed to a mask of, say, 0010 or 1100.

Clearing the most significant bit of 1011 looks like

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

Recall that ~ in Perl is bitwise complement.

For completeness, set a bit with bitwise OR as in

$bits |= $mask;

Octal and file permissions

An octal digit’s direct mapping to three bits is convenient for Unix permissions because they come in groups of three. For example, the permissions for the program that produced the output above are

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

The mode test has to be more careful because some of the masks are shorthand for multiple bits. Testing that we get the exact mask back avoids false positives when some of the bits are set but not all.

The loop also clears the bits from all detected hits so at the end we can check that we have accounted for each bit. The output is

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

The high bit in the mode that gets discarded is S_IFREG, the indicator that it is a regular file. Notice how much clearer the mode expressed in octal is when compared with the same information in decimal or binary.

The stat documentation mentions a helpful function.

… 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, we find its implementation and a familiar constant on the last line.

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

To avoid the bad practice of magic numbers in source code, write

my $permissions = S_IMODE $mode;

Using S_IMODE and other functions available from the Fcntl module also hides the low-level bit twiddling and focuses on the domain-level information the program wants. The documentation continues

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)

Using these constants and functions will make your programs clearer by more directly expressing your intent.

这篇关于从stat函数输出中了解和解码文件模式值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆