そのファイル、消せますか？

サーバー上の不要ファイルを消す必要が出たときに、「どういうパーミッションが付いていればそのファイルを消せるか？」という問いをすると、間違える人が割と居る。

その問いへの正しい答えと、どうしてそうなるかを見てみよう。

## ファイルを消すとは？

ファイルを消すという操作について、

* ファイルの内容を消す
* ファイルパスを消す

の二つのパターンを考えてみよう。「ファイルを消す」と言った場合、通常は後者を指すと思うが、その条件を問いかけたときに、後者の条件を答えてしまう間違いがよくある。

まず、それぞれの操作によって得られる状態の期待値を明確化しておこう。

ファイルの内容を消した場合には、ファイルの内容が空になっていることが期待値である：

```
$ ls -s path/to/file 
0 path/to/file
$ wc -c path/to/file 
0 path/to/file
$ file path/to/file 
path/to/file: empty
```

一方、ファイルパスを消した場合には、存在していたパスでファイルにアクセスできなくなっていることが期待値である：

```
$ ls path/to/file 
ls: cannot access 'path/to/file': No such file or directory
$ cat path/to/file 
cat: path/to/file: No such file or directory
```

それぞれを達成するための具体的な操作例を示す：

* ファイルの内容を消す： echo -n > path/to/file
* ファイルパスを消す： rm path/to/file

パーミッションとこれらの操作が可能かどうかの関係を見ていこう。

## ファイルの内容を消せるか？

ファイルの内容を消すには、ファイルの write パーミッションが必要。write パーミッションを外すと

```
$ cat path/to/file 
hello
$ ls -li path/to/file 
161504 -rw-r--r-- 1 yoichinakayama yoichinakayama 6 May 31 11:55 path/to/file
$ chmod -w path/to/file 
$ ls -li path/to/file 
161504 -r--r--r-- 1 yoichinakayama yoichinakayama 6 May 31 11:55 path/to/file
$ echo -n > path/to/file 
-bash: path/to/file: Permission denied
```

となって、ファイルの内容を消すことができない。write パーミッションを付けると

```
$ chmod +w path/to/file 
$ ls -li path/to/file 
161504 -rw-r--r-- 1 yoichinakayama yoichinakayama 6 May 31 11:55 path/to/file
$ echo -n > path/to/file 
$ ls -li path/to/file 
161504 -rw-r--r-- 1 yoichinakayama yoichinakayama 0 May 31 11:56 path/to/file
$ wc -c path/to/file 
0 path/to/file
$ file path/to/file 
path/to/file: empty
```

とファイルの内容を消すことができる。ファイルの内容を編集するので、そのファイルの write パーミッションが必要というわけだ。

## ファイルパスを消せるか？

ファイルパスを消すには親ディレクトリの write パーミッションが必要。write パーミッションを外すと

```
$ ls -l path/to/file 
-rw-r--r-- 1 yoichinakayama yoichinakayama 0 May 31 12:00 path/to/file
yoichinakayama@penguin:~$ ls -ld path/to
drwxr-xr-x 1 yoichinakayama yoichinakayama 8 May 31 11:55 path/to
yoichinakayama@penguin:~$ chmod -w path/to
yoichinakayama@penguin:~$ ls -ld path/to
dr-xr-xr-x 1 yoichinakayama yoichinakayama 8 May 31 11:55 path/to
yoichinakayama@penguin:~$ rm path/to/file 
rm: cannot remove 'path/to/file': Permission denied
```

となって、ファイルパスを消すことができない。write パーミッションを付けると

```
$ chmod +w path/to
$ ls -ld path/to
drwxr-xr-x 1 yoichinakayama yoichinakayama 8 May 31 11:55 path/to
$ rm path/to/file 
$ ls path/to/file
ls: cannot access 'path/to/file': No such file or directory
```

とファイルパスを消すことができる。

## ディレクトリの中身を見る

ファイルを消すのに何故ディレクトリの write パーミッションが必要だったかを知るには、ディレクトリにどんな情報が格納されているのかを見ると良い。以下のC言語のプログラムで、引数にディレクトリのパスを与えると、そのディレクトリの内容を出力してくれるのでそれを使おう。

```
$ cat sample.c 
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	DIR *dir = opendir(argv[1]);
	struct dirent *ent;
	if (dir == NULL) {
		return 1;
	}
	while ((ent = readdir(dir)) != NULL) {
		printf("d_ino=%d, d_name=%s\n", ent->d_ino, ent->d_name);
	}
	closedir(dir);
	return 0;
}
$ gcc sample.c 
```

まず、ディレクトリにファイルがある状態で実行してみる。

```
$ touch path/to/file
$ ./a.out path/to
d_ino=161394, d_name=.
d_ino=161393, d_name=..
d_ino=161598, d_name=file
$ ls -li path/to/file 
161598 -rw-r--r-- 1 yoichinakayama yoichinakayama 0 May 31 12:14 path/to/file
```

対象ファイルのinode番号と、ファイル名 "file" がディレクトリに格納されていることがわかる。

次にファイルパスを消すとどうなるか見てみよう。

```
$ rm path/to/file
$ ./a.out path/to
d_ino=161394, d_name=.
d_ino=161393, d_name=..
```

ディレクトリに格納されていた dirent 構造体が一つ減っている。つまり、ディレクトリが変更されている。この例だと、path/to/file というファイルパスを消すという操作は、ディレクトリ（inode番号161394）の内容を変更するという操作である。したがってそのディレクトリの write パーミッションが必要になる。

## まとめ

その操作で何を編集しているのか？何の情報がどこに保持されているのかを意識すると、ファイルの内容を消すとき、ファイルパスを消すときに必要なパーミッションが何かということが理解できると思う。

## 確認環境

Chromebook のターミナル上で確認した。

```
$ uname -a
Linux penguin 4.19.113-08528-g5803a1c7e9f9 #1 SMP PREEMPT Thu Apr 2 15:16:47 PDT 2020 aarch64 GNU/Linux
$ gcc --version
gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```