ioutil.WriteFile のFileMode の挙動の意味がわからなかったので調べてみた

ファイルを一括で記述したかったので、`ioutil.WriteFile` を使おうと思いました。この第三引数の部分がいまいち理解できていないので調べていました。

# 定義

```golang

func WriteFile(filename string, data []byte, perm os.FileMode) error
```

よくここに`os.ModePrem` とか書くけどあれなによ？というやつです。`FileMode`の定義も読んでみます。様々なデバイスファイルやモードが定義されています。単にファイルを書きたいときだと、`os.ModePrem` でオッケーで、Unix の0777のパーミッションがファイルに設定されるように見えます。

```golang
const (
    // The single letters are the abbreviations
    // used by the String method's formatting.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
    ModeAppend                                     // a: append-only
    ModeExclusive                                  // l: exclusive use
    ModeTemporary                                  // T: temporary file; Plan 9 only
    ModeSymlink                                    // L: symbolic link
    ModeDevice                                     // D: device file
    ModeNamedPipe                                  // p: named pipe (FIFO)
    ModeSocket                                     // S: Unix domain socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: Unix character device, when ModeDevice is set
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: non-regular file; nothing else is known about this file

    // Mask for the type bits. For regular files, none will be set.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

    ModePerm FileMode = 0777 // Unix permission bits
)
```

# ファイルのパーミッションがうまく動作しない理由

それなら、普通に書きこむファイルのパーミッションを設定できるのでは？と思いしらべてみると、次のように書けます。

```golang
package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
)

func main() {
	ioutil.WriteFile("ModePerm.txt", []byte("ModePerm.txt"), os.ModePerm)
	fmt.Println(os.ModePerm.IsRegular())
	perm := "0777"
	perm32, _ := strconv.ParseUint(perm, 8, 32)
	ioutil.WriteFile("777.txt", []byte("some"), os.FileMode(perm32))
	fmt.Println(perm32)
	perm = "0644"
	perm32, _ = strconv.ParseUint(perm, 8, 32)
	ioutil.WriteFile("644.txt", []byte("some"), os.FileMode(perm32))
	fmt.Println(perm32)
	perm = "0666"
	perm32, _ = strconv.ParseUint(perm, 8, 32)
	ioutil.WriteFile("666.txt", []byte("some"), os.FileMode(perm32))
	// os.Chmod("./666.txt", os.FileMode(perm32))
}
```

これを実行すると、４つのファイルが生成されて、想定通りのパーミッションが設定されるといいのですが、そうなりません。

```bash
ls -l 
total 4
-rw-r--r-- 1 ushio ushio   4 Dec 21 08:45 644.txt
-rw-r--r-- 1 ushio ushio   4 Dec 21 08:45 666.txt
-rwxr-xr-x 1 ushio ushio   4 Dec 21 08:45 777.txt
-rwxr-xr-x 1 ushio ushio  12 Dec 21 08:45 ModePerm.txt
-rw-r--r-- 1 ushio ushio  47 Dec 21 08:11 go.mod
-rw-r--r-- 1 ushio ushio 809 Dec 21 08:44 main.go
```

なんでやねん！と思って私のGo師匠であるかえる師匠に聞いてみたのですが、こんなStackOverflowの記事を見つけてくれました。

* [ioutils.WriteFile() not respecting permissions](https://stackoverflow.com/questions/50257981/ioutils-writefile-not-respecting-permissions)

これを読むと、これは、go の問題ではなく、OS で設定されている `umask`の問題では？との答えがありました。

# umask

> umask()  sets  the  calling process's file mode creation mask (umask) to mask & 0777 (i.e., only the file 
  permission bits of mask are used),and returns the previous value of the mask.
>
>  The umask is used by open(2), mkdir(2), and other system calls that create files to modify the permissions placed on newly created files  or directories.  Specifically, permissions in the umask are turned off from the mode argument to open(2) and mkdir(2).

Umaskは、openやmkdirのシステムコールで使われて、新しいファイルやディレクトリを作るときにパーミッションをモディファイするとあります。ここで、私が今使っているWSLのumaskを見てみましょう。

```bash
$ umask
022
```
022 ですね、これをマスクするということは、Unixのファイルパーミッション設定は、4(read) 2(write) 1(execute) なので、022は所有者以外の書き込み権限なので、それでマスクをすると、書き込み権限がなくなることになります。
実際の上の実行例を見てみると、644は正しく設定されているのに、666は、644に変更されています。ModePerm も元が777なのが、755に変更されています。

# 想定どおりのパーミッションを設定したい場合は？

では、想定通りのパーミッションを設定するにはどういう方法がよいでしょう？先ほどのStack Overflow からたどれるWikiで次のような記述があります。

> In Unix-like systems, each file has a set of attributes that control who can read, write or execute it. When a program creates a file the file permissions are restricted by the mask. If the mask has a bit set to "1", then the corresponding initial file permission will be disabled. A bit set to "0" in the mask means that the corresponding permission will be determined by the program and the file system. In other words, the mask acts as a last-stage filter that strips away permissions as a file is created; each bit that is set to a "1" strips away its corresponding permission. Permissions may be changed later by users and programs using chmod.

Unixシステムでは、ファイルを作成した時にはマスクで、制限されてるとあります。パーミッションはchmodを使って後で変えられるかもしれないとあります。つまり、想定通りのパーミッション例えば、`0666`を設定したい場合は、

```golang
 os.Chmod("./666.txt", os.FileMode(perm32))
```

を追記すればいいことになります。そうすれば、666に無事設定されていることがわかると思います。

```bash
ls -l 
total 4
-rw-r--r-- 1 ushio ushio   4 Dec 21 08:49 644.txt
-rw-rw-rw- 1 ushio ushio   4 Dec 21 08:49 666.txt
-rwxr-xr-x 1 ushio ushio   4 Dec 21 08:49 777.txt
-rwxr-xr-x 1 ushio ushio  12 Dec 21 08:49 ModePerm.txt
-rw-r--r-- 1 ushio ushio  47 Dec 21 08:11 go.mod
-rw-r--r-- 1 ushio ushio 651 Dec 21 08:49 main.go
```