umask(1)は引き算しない、多分。

umask(1)の仕様を調べようとしたのである。

で、ググって見つかった「デフォルトのファイルアクセス権 (umask) (Solaris のシステム管理 (基本編))」を見てちょっと驚いた。

設定する umask の値は、与えたいアクセス権の値を 666 (ファイルの場合) または 777 (ディレクトリの場合) から引きます。引いた残りが umask に使用する値です。たとえば、ファイルのデフォルトモードを 644 (rw-r--r--) に変更したいとします。このとき 666 と 644 の差 022 が umask コマンドの引数として使用する値です。

デフォルトのファイルアクセス権 (umask) (Solaris のシステム管理 (基本編))

「引きます」とか「引いた残りが」とか、引き算をしているかのような記述である。

職業病だと思うのだが、私は `mask' という単語より、ビット演算によるマスクを行っているものと予想していた。しかしこの文書のニュアンスでは、ビットマスクではなく引き算を行っているように読み取れる。

日本語に翻訳する際に微妙な訳になったのかと思ったが、Oracleの別の文書である「umask(1) (man pages section 1: User Commands)」の記述はこんな感じだった。

The value of each specified digit is subtracted from the corresponding ``digit'' specified by the system for the creation of a file (see creat(2)). For example, umask 022 removes write permission for group and other (files normally created with mode 777 become mode 755; files created with mode 666 become mode 644).

umask(1) (man pages section 1: User Commands)

`subtracted' という単語より、私の拙い英語読解力では「引き算している」という感じに読み取れる。

しかし「Umask - ArchWiki」には別の記述がされていた。

新しく作成されたファイルに設定されるパーミッションビットの値は論理非含意 (付加) を使って計算され、論理記号で表現することができます:

R: (D & (~M))

つまり、最終的なパーミッション R はデフォルトのパーミッション D の論理積とファイル作成時のモードマスク M の論理否定が合わさった結果になります。

Umask - ArchWiki

ArchLinuxのドキュメントでは、引き算ではなくビットマスクしているものと読み取れる。

どちらが正しいのだろうか? 
POSIXのumask(1)の説明には以下のように「logical complement」とあるので、ビットマスクしているっぽいように思うのだが……。

For a symbolic_mode value, the new value of the file mode creation mask shall be the logical complement of the file permission bits portion of the file mode specified by the symbolic_mode string.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/umask.html

試しに、挙動を検証する方法を考えてみた。

まず、引き算とビットマスクとで計算結果が異なるケースは存在するだろうか? マスクが0022の場合、引き算とビットマスクとで計算結果は同じだ。しかしマスクが0033の場合、ファイルの場合の既定値(0666)との組み合わせにおいて、計算結果が異なるはずだ。

以下のプログラム(C言語)で確認したところ:

#include <stdio.h>
#include <stdlib.h>

static void pmask(const int bmsk, const int umsk)
{
    (void) printf("%04o -  %04o == %04o\n", bmsk, umsk, bmsk -  umsk);
    (void) printf("%04o & ~%04o == %04o\n", bmsk, umsk, bmsk & ~umsk);
}

int main(void)
{
    pmask(0777, 0022);
    pmask(0666, 0022);

    pmask(0777, 0033);
    pmask(0666, 0033);

    return EXIT_SUCCESS;
}

実行結果はこんな感じだった。

$ ./a.out
0777 -  0022 == 0755
0777 & ~0022 == 0755
0666 -  0022 == 0644
0666 & ~0022 == 0644
0777 -  0033 == 0744
0777 & ~0033 == 0744
0666 -  0033 == 0633
0666 & ~0033 == 0644
$ _

0666と0033の組み合わせのみ、引き算では0633が、ビットマスクでは0644が得られる。しかし他のパターンでは引き算とビットマスクとで得られる値は同じだ。

手元のUbuntu 16.04で試してみたところ、「umask 0033」の時に生成されたファイルのパーミッションは「rw-r--r--」、すなわち0644だった、同じLinuxであるArchLinuxと同様に、umask(1)では引き算ではなくビットマスクで計算されているようだ。

ということで、少なくともUbuntuとArchLinuxでは、umask(1)の結果は引き算ではなくビットマスクだ。

問題は、事の発端のドキュメントのOSであるSolarisを含む「Linux以外のUnix系OS」でのumask(1)の挙動を調べていないことと、元文書の「subtract」という単語が引き算以外のニュアンスを持っているか否かが分からないこと、以上の2点だ。実際、どうなのだろう?