lsコマンドで表示されるファイルのモード(drwxr-xr-x) 〜RubyのFile::Stat#modeとは〜

10 min read読了の目安(約9000字

最初に

フィヨルドブートキャンプ Part 2 Advent Calendar 2020の2日目の記事です。
昨日は、fukindesuさんによる「FJORD BOOT CAMPのカリキュラムで必要な書籍・参考書籍」の補助輪でした。
そして、今のところ明日・明後日だけが、まだ埋まっていません。3日後がsasaboさんです。
また、Part1について、昨日がshita1112さんのDevise入門64のレシピ その1で、今日がbecolomochiさんで、明日がおぐさんです。

本記事の主な目的

  • Unix系のlsコマンドの-lオプションでの詳細表示(long format)で表示される左端の記号
  • RubyのFile::Stat#mode数字

これら2つはどちらもファイルタイプ・アクセス権限を示すもので、これら2つの対応関係を示すことを目的とします。

$ ls -l
-rw-r--r--   1 uname  staff    0 11 28 12:31 default.txt
drwxr-xr-x   2 uname  staff   64 11 28 11:55 default_dir

$ irb
irb(main):001:0> File.lstat("default.txt").mode.to_s(8)
=> "100644"
irb(main):002:0> File.lstat("default_dir").mode.to_s(8)
=> "40755"

-rw-r--r--"100644", drwxr-xr-x"40755"が対応してます。

背景として、lsコマンドをRubyで再現するにあたり、RubyリファレンスマニュアルのFile::Stat#modeの説明だけだとシンプルで十分な理解が得られなかったため(別途Unixについての知識が必要だったため)、この記事を書くことにしました。

読者対象

  • Unixのlsコマンド(特にファイル権限)について、学習してこなかったがザックリ理解したい人
  • Unixのlsコマンド(特にファイル権限)について、改めて理解したい人

用語の整理

混乱のないよう、簡単にですがまず用語を整理します。
ディレクトリとは、Windowsでいうフォルダです。
ディレクトリは、ディレクトリ、ファイル、シンボリックリンク等を格納します。
シンボリックリンクは、ファイルのショートカットで、別のディレクトリやファイル等を指すものです。
さらに、ディレクトリ、ファイル、シンボリックリンク等をまとめてファイルともいいます。狭い意味でのファイルを特に強調したい場合は、「通常ファイル(regular file)」と言います。ただ、一般にどちらも単に「ファイル」と書くことも少なくなく、本記事でも特に区別しないので適宜どの意味か読み取って下さい。

数の表記について

ファイル権限は8進数(octal digit), 2進数(binary digit)と結びついているため、本記事では10進数ではなく主に8進数, 2進数を扱います。
Ruby等の多くのプログラミング言語などで、2進数は0b始まりで表記し、8進数は0oまたは0始まりで表記します。それにならい、本記事でも0b始まりは2進数、0o始まりは8進数とします。逆に、必ずしも8進数は0o0始まりで表記されているわけではないので注意してください。

なお、Rubyでは以下のように2進数や8進数で直接書くことができます。

p 0b101 #=>   5 = (2**2) * 1 + (2**1) * 0 + (2**0) * 1
p 0o12  #=>   9 =              (8**1) * 1 + (8**0) * 2
p 0170  #=> 120 = (8**2) * 1 + (8**1) * 7 + (8**0) * 0

**は、累乗です。
(内部的にはどれも同じIntegerであり、10進数や8進数のデータ型が存在するわけではなく、そのまま出力した場合は10進数で表示されます)

動作環境

  • MacOS Catalina Version 10.15.7
  • Ruby 2.7.1 (今月のクリスマス前後にRuby 3.0が新しくでる予定)

RubyリファレンスマニュアルでのFile::Stat#mode

File::Stat#mode (Ruby 2.7.0 リファレンスマニュアル)
Image from Gyazo

Rubyリファレンスマニュアル(るりま)の説明は、とてもシンプルです。
周辺知識もないと厳しい気がするので、本記事で補足します。

特殊変数$0とは

まず「$0とは何なのか」となる人も少なくないでしょう。
$0は、まさに自身のファイル名・パス(文字列)が入っている特殊変数です。
説明だけだと理解しにくいと思うので、実際にp $0と中身を出力してみるといいでしょう。
main.rbというファイルにp $0が書かれ、これを実行したなら、"main.rb"等と表示されるはずです。

【参考】
$0 (Ruby リファレンスマニュアル)

現在実行中の Ruby スクリプトの名前を表す文字列です。

File::Stat#modeの返り値の注意

また注意しておきたいのが、File::Statインスタンスのmodeメソッドの返り値です。リファレンスマニュアルに「mode -> Integer」とあるように、Integerクラスの数値が返ります。
しかし、この数値は8進数(or 2進数)の表記で見たときに初めて意味のわかるもので、そのまま単純に数値を出力すると10進数で表示され意味がわかりません。このあたり勘違いされる方が、少なくないです。
実際、リファレンスマニュアルの例ではprintf "%o\n", fs.modeと書き、8進数表記で出力するように%oで指定しています(\nは改行)。他に、fs.mode.to_s(8)と書くことで、8進数表記した文字列を返してくれます。

puts fs.mode  #=> 33188
puts fs.mode.to_s(8) #=> 100644
printf "%o\n", fs.mode #=> 100644

0o0始まりではないですが、ここでの100644は8進数表記であることに注意です。
後述になりますが、100644という8進数表記で、それぞれの数字が意味を持っています。

File::Statクラスのインスタンスを作成する方法

なお、File::StatクラスのインスタンスをFile::Stat.newで作成していますが、これ以外にFile::Statクラスのインスタンスを作成する方法が存在します。

  • File.stat …… File::Stat.newと同じ。
  • File.lstat

名称が異なるように、前者と後者は挙動が異なることがあるので、注意が必要です。
具体的には、引数がシンボリックリンクのときの挙動が異なっており、前者はシンボリックリンク先のファイル情報を取得しますが、後者はシンボリックリンクそのもののファイル情報を取得します。目的に応じて、使い分けましょう。

【参考】

実際のファイルの数字と記号について

さて、次の記号と数字の対応関係を明らかにすることが目的でした。

8進数(mode) lsコマンドの記号
0o100644 -rw-r--r--
0o040755 drwxr-xr-x

0o40755と書くと説明が不便になるので、6桁になるように0o040755とします。
この0o040755drwxr-xr-xは、次のように対応しています。

ファイル種類 特殊な権限 所有者の権限 所有グループ権限 その他ユーザーの権限
04 0 7 5 5
d 各xの部分 rwx r-x r-x

これから、大筋として、ファイルの種類(タイプ)、通常の権限、特殊な権限の順に書いていきます。

1文字目のファイルの種類(タイプ)について

8進数での頭2桁の数値とlsコマンドの記号の頭1文字目は、ファイルの種類(タイプ)です。
以下のように対応しています。

8進数 記号 意味 RubyのFile::Stat#ftype
0o010000 p FIFO "fifo"
0o020000 c Character special file "characterSpecial"
0o040000 d Directory(ディレクトリ) "directory"
0o060000 b Block special file "blockSpecial"
0o100000 - Regular file(通常ファイル) "file"
0o120000 l Symbolic link(シンボリックリンク) "link"
0o140000 s Socket link "socket"

列挙しましたが、最初は-d、次にlがわかれば、きっと十分でしょう。
なお、File::Stat#modeでなくとも、File::Stat#ftypeで識別可能だったため、これも表に記載しました。

fs = File.lstat($0)
p fs.mode.to_s(8) #=> "100644" (8進数の文字列)
p fs.ftype        #=> "file"

【参考】
File.ftype (Ruby リファレンスマニュアル)

アクセス権と数値の関係

ファイルに対する通常の権限は、独立して3種類あります。
readable (読み込み権限)、writable (書き込み権限)、executable (実行権限)です。
そして、このそれぞれの権限に、3桁の2進数でのそれぞれの位に対応するよう紐付けられています。
権限があれば1, 権限がなければ0です。
つまり、読み込み権限があれば1番下の位の1が立つし、なければ立たないように決まっています。

10進数 8進数 2進数 記号 意味する単語 (権限)
1 0o1 0b001 r readable (読み込み権限)
2 0o2 0b010 w writable (書き込み権限)
4 0o4 0b100 x executable (実行権限)

これら通常の権限はそれぞれ「権限がある」「権限がない」の2通りで、権限の種類が独立に3種類なので、8通り(= 2の3乗)となります。
これらの8通りをまとめると、以下のようになります。

10進数 8進数 2進数 lsコマンドの記号
0 0o0 0b000 ---
1 0o1 0b001 --x
2 0o2 0b010 -w-
3 0o3 0b011 -wx
4 0o4 0b100 r--
5 0o5 0b101 r-x
6 0o6 0b110 rw-
7 0o7 0b111 rwx

ディレクトリの権限について

ディレクトリは実行するものではないので、ディレクトリにxが立っているときの意味は、searchable(探索権限=移動権限)の意味で、cdコマンドでそのディレクトリに
移動できるようになります。
また、ディレクトリに読み込み権限があると、lsコマンドでディレクトリの中身を見ることが可能になります。
感覚的にディレクトリの読み込み権限・移動権限は、あまり区別する必要がなさそうに感じます。

実際のファイルの権限の表示

さらに、ファイルの権限は、所有者(owner)、所有グループ(group)、その他のユーザー(other)にわけて、管理されます。
それぞれの権限を、owner permission, group permission, other permissionといいます。
それぞれのユーザーのセグメントについて、前述の8通りのどれかの権限が付与されます。

$ ls -l
-rw-r--r--   1 uni  staff    0 11 28 12:31 default.txt
drwxr-xr-x   2 uni  staff   64 11 28 11:55 default_dir

2文字目から3文字区切りで、所有者の権限rwx、グループの権限rwx、その他のユーザーの権限rwxを表し、権限がない部分は-となります。

ここで説明を加えて、対応関係を明記します。

8進数(mode) lsコマンドの記号 意味
0o100644 -rw-r--r-- 通常ファイルで誰でも閲覧できるが、所有者のみ書き込める。プログラムの実行は誰もできない。
0o040755 drwxr-xr-x ディレクトリで閲覧等は全員できるが、中でのファイル新規作成・名前変更・削除はwのある所有者のみ

実際に数値でファイル権限を変更できる

File::Stat#modeの返り値の8進数はRubyで使われているだけでなく、Unix内で使われています。

chmod 755 file_name

上記のようにコマンドをうつことで、アクセス権限をrwxr-xr-xに変更できます。

特殊な権限の設定

File::Stat#modeの返り値の8進数表記の"0o040755"を例に説明すると、

  • 先頭から2桁04が、ファイルのタイプ。
  • 先頭から4桁目7が、所有者の権限。
  • 先頭から5桁目5が、所有グループの権限。
  • 先頭から6桁目5が、その他のユーザーの権限。

となりました。

これから、先頭から3桁目の特殊権限について、これから説明します。これで最後です。
先頭から3桁目の位の数について、2進数表記で1が立っていれば、特殊な権限が設定されています。
ビットの立っている位置によって、次のような特殊な権限がつきます。

8進数 2進数 記号 特殊な権限の設定
0o0000 0b000000000000 (特殊な権限なし)
0o1000 0b001000000000 t / T スティッキービット
0o2000 0b010000000000 s / S SUID(Set User ID)
0o4000 0b100000000000 s / S SGID(Set Group ID)

これらの特殊な権限の設定があると、lsコマンドの詳細で表示される権限のxの部分がstに上書きされます。
次から、「スティッキービット」そして「SUID、GUID」について簡単に説明して、終わりにします。

スティッキービット

スティッキービットが立っているディレクトリの中では、(自分が所有者のファイルでない限り)たとえother permission(その他のユーザー権限)にwの権限があるファイルであっても、出来るのは書き込みまでで、ファイル名の変更や削除が出来なくなります。一応いうと、スティッキービットの配下であっても、自分が所有者のファイルやrootは名称変更や削除できるので注意です。

$ mkdir sticky_dir

$ chmod 1755 sticky_dir
$ ls -l
drwxr-xr-t  2 uname  staff   64 11 28 12:15 sticky_dir

$ chmod 1754 sticky_dir
$ ls -l
drwxr-xr-T  2 uname  staff   64 11 28 12:15 sticky_dir

ディレクトリにスティッキービットがたっているのに、other permission(その他のユーザー権限)に移動権限xが立ってない場合は、大文字のTになります。

SUID(Set User ID), SGID(Set Group ID)

owner permissionやgroup permissionのxs(またはS)が立つことがあります。
例えば、ファイルの所有者権限にsが立っている場合は、SUIDが機能することになり、所有者以外が実行した場合でも所有者が実行したものとして機能します。
なお、スティッキービットと同様に、ファイルの所有者の実行権限xがないにもかかわらず、SUIDを立たせると大文字のSが立ちます。

最後に

以上で説明は終わりです。
文章を構成するのは難しいと改めて実感しました。

ソース・参考書籍

lsコマンドの説明は、man lsとコマンドを打つとlsコマンドの説明が表示されます。
これが仕様であり正確な説明のはずなので、こちらを読むといいでしょう。