web-dev-qa-db-ja.com

stat関数出力からのファイルモード値の理解とデコード

私は、以下のコードで何が起こっているのかを正確に理解しようとしています。しかし、私はそれを理解することができません。

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

私の$ mode値が33188だとしましょう

$ mode&07777は値= 420を生成します

  • $ mode値は10進数ですか?

  • 07777を選択する理由と、ビット単位の操作を行う理由。私はここの論理を理解することができません。

23
chidori

あなたの質問のモードは、644の権限(所有者には読み取り/書き込み、他のすべての人には読み取り専用)を持つ通常のファイルに対応していますが、私の言葉を受け入れないでください。

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

_$mode_ canの値は、10進整数と見なされますが、そうすることは特に啓発的ではありません。 8進表現を見ると、もう少し馴染みのあるものになります。

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

07777とのビットごとのANDは、数値の2進表現の最後の12ビットを示します。 Unixモードでは、この操作は許可ビットまたはモードビットを与え、型情報を破棄します。

$ Perl -e'printf "%d\n"、(stat "foo")[2]&07777 '#10進数、役に立たない
 420 
 $ Perl -e'printf "%o\n "、(stat" foo ")[2]&07777 '#8進数、eureka!
 644

これを行うためのより良い方法は以下のとおりです。詳細については、以下をお読みください。


モードビット

statから返される3番目の要素(_st_mode_の_struct stat_に対応)は ビットフィールド で、異なるビット位置はバイナリフラグです。

たとえば、_st_mode_ POSIXの1ビットは_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 EnterpriseLinuxおよびSystemVファミリの他のオペレーティングシステムでは、上記のプログラムの出力は次のようになります。

S_ISTXT-未定義
 S_IFWHT-未定義
 S_IFSOCK-140000 
 S_IFLNK-120000 
 S_IFREG-100000 
 S_IFBLK-60000 
 S 40000 
 S_IFCHR-20000 
 S_IFIFO-10000 
 S_ISUID-4000 
 S_ISGID-2000 
 S_ISVTX-1000 
 S_IRWXU-700 [ .________________ 。] S_IXGRP-10 
 S_IRWXO-7 
 S_IROTH-4 
 S_IWOTH-2 
 S_IXOTH-1

ビットをいじる

上記の数値は8進数(基数8)であるため、任意の桁は0〜7である必要があり、桁の値は8です。n、ここでnは、基数ポイントの左側にあるゼロベースの場所の数です。それらがビットにどのようにマップされるかを確認するために、8進数には各桁が3ビットに対応するという便利なプロパティがあります。 4、2、および1はすべて2の正確な累乗であるため、2進数では、それぞれ100、10、および1になります。 2進数の7(= 4 + 2 + 1)は111なので、708 は111000です2。後者の例は、前後の変換がいかに簡単であるかを示しています。

ビットフィールドを使用すると、正確に気にする必要はありませんwhatその位置のビットの値はかどうかゼロかゼロ以外か。

_if ($mode & $mask) {
_

_$mode_に対応する_$mask_のビットが設定されているかどうかをテストします。簡単な例として、4ビット整数1011とマスク0100が与えられた場合、それらのビット単位のANDは次のようになります。

_  1011
& 0100
------
  0000
_

したがって、たとえば0010または1100のマスクとは対照的に、その位置のビットは明確です。

1011の最上位ビットをクリアすると次のようになります

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

Perlの_~_はビット単位の補数であることを思い出してください。

完全を期すために、ビット単位のOR

_$bits |= $mask;
_

8進数とファイルのアクセス許可

8進数の3ビットへの直接マッピングは、3つのグループで提供されるため、Unixのアクセス許可に便利です。たとえば、上記の出力を生成したプログラムの権限は次のとおりです。

-rwxr-xr-x 1gbaconユーザー10962月24日20:34モードビット

つまり、所有者は読み取り、書き込み、および実行を行うことができます。しかし、他の誰もが読んで実行することができます。 8進数では、これは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

7777の変換8 バイナリに変換すると、_0b111_111_111_111_が得られます。その7を思い出してください8 111です2、および4つの7は4×3のものに対応します。このマスクは、最後の12のセットビットを選択するのに役立ちます。以前に生成したビットマスクを振り返って

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

最後の9ビットは、ユーザー、グループ、およびその他のアクセス許可であることがわかります。それらの前の3つのビットは、setuid、setgroupid、およびスティッキービットと呼ばれることもあります。たとえば、私のシステムのsendmailのフルモードは_-rwxr-sr-x_または34285です。10。ビットごとのANDは、

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

破棄されるモードの上位ビットは_S_IFREG_であり、これは通常のファイルであることを示します。 10進数または2進数の同じ情報と比較すると、8進数で表されるモードがどれほど明確であるかに注意してください。

statドキュメント は便利な関数について言及しています。

…そして_S_IF*_関数は

S_IMODE($mode)
許可ビットとsetuid/setgid/stickyビットを含む_$mode_の部分

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

これらの定数と関数を使用すると、意図をより直接的に表現することで、プログラムがより明確になります。

33
Greg Bacon

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は、8進数0644の10進数表現です。

数値を2進形式で印刷しようとすると、次のことがわかりやすくなります。

Perl -lwe 'printf "%016b\n", 33188'
1000000110100100
Perl -lwe 'printf "%016b\n", 33188 & 07777'
0000000110100100

お気づきのように、ビット単位のandは、上記の番号の左端のビットを削除します。これはおそらくファイルタイプを表しており、ファイルのアクセス許可のみが残ります。この番号07777は2進数です。

Perl -lwe 'printf "%016b\n", 07777'
0000111111111111

これは、ビット単位のandで「マスク」として機能します。 1&1 = 1、および0&1 = 0であるため、07777の1と一致しないビットはすべて0に設定されます。

7
TLP