mkdirシステムコールの変遷

先週の@magoroku15さんのustで、システムコール番号の歴史が参照され、V7にはmkdirシステムコールがなくて、mknodとlinkを使っていたんだよねという話題が出てきた。それに触発され、mkdirについて調べてみたところ、試行錯誤の痕がいろいろ見られ面白かったのでまとめてみた。

まず、V7のmkdir.cを読むと、mknodでinodeを作り*1、自分(".")と親("..")にlinkを張っているのがわかる。mknodはinodeは作るがファイルの中身はからっぽで、ディレクトリとしては正しいフォーマットではない。最低限自分と親ディレクトリへの参照が必要だからだ。そこで、linkを使う。UNIXファイルシステムディレクトリエントリの構造については説明不要だよね。

mkdir(d)
char *d;
{
     char pname[128], dname[128];
     register i, slash = 0;

     pname[0] = '\0';
     for(i = 0; d[i]; ++i)
          if(d[i] == '/')
               slash = i + 1;
     if(slash)
          strncpy(pname, d, slash);
     strcpy(pname+slash, ".");
     if (access(pname, 02)) {
          fprintf(stderr,"mkdir: cannot access %s\n", pname);
          ++Errors;
          return;
     }
     if ((mknod(d, 040777, 0)) < 0) {
          fprintf(stderr,"mkdir: cannot make directory %s\n", d);
          ++Errors;
          return;
     }
     chown(d, getuid(), getgid());
     strcpy(dname, d);
     strcat(dname, "/.");
     if((link(d, dname)) < 0) {
          fprintf(stderr, "mkdir: cannot link %s\n", dname);
          unlink(d);
          ++Errors;
          return;
     }
     strcat(dname, ".");
     if((link(pname, dname)) < 0) {
          fprintf(stderr, "mkdir: cannot link %s\n",dname);
          dname[strlen(dname)] = '\0';
          unlink(dname);
          unlink(d);
          ++Errors;
     }
}

mkdirシステムコール(そしてrmdirも)が追加されたのは、SVR3とか4.2BSD以降である。なぜmkdirシステムコールが必要だったのか? mkdirコマンドがmknodとlinkの間でSIGKILLを受けた(もしくはシステムクラッシュした)としよう。すると、そのファイルはどこともつながっていない宙ぶらりんなゴミエントリになってしまう(追記:koieさんに指摘された通り、mknodで親ディレクトリからのリンクはできるけど、逆方向のリンクがない状態になる。)。そこで、mknodとlinkを不可分に実行できるようにmkdirシステムコールが追加されたのだ。また、ディレクトリにリンクを張れるのはスーパユーザだけだったので、mkdirコマンドにsetuidする必要があった。

ここまではUNIXユーザとしての基本。さらに深掘りしていこう。

V6のmkdirを見てみよう。コマンドがCに書き直されたのはV7になってからなので、まだアセンブリで書かれている。しかし、予想に反してmknodではなく、makdirとある。

        sys     makdir; 0:..; 140777; 0

カーネル的(sysent.c)には14番目のシステムコールはmknodだけど、ユーザランド的(アセンブラ(as19.s))にはmakdirになっている。不思議だね。これには歴史的な経緯がありそうだ。なんだか黒歴史のにおいがする。

そもそもV1にはmkdirシステムコールがあった(man mkdir(2))。そう、mkdirは一度は消えて、復活したシステムコールだったのだ。V2ではなぜかmkdirからmakdirに改名されている(man makdir(2))。おい、6文字に増やすなら、mkdirよりもcreatの方じゃないか!とツッコミを入れたくなるが、なぜ改名されたのか理由は不明。そして、V4でmakdirからmknodに(man mknod(2))。カーネルは変更されたが、ユーザランドは互換性を考えたのか、手が入っていない。この状況はV6時点まで続き。ようやくV7でmkdirコマンドがCで書き直されたのを機にmknodで統一される。

このように追っていくと、やはりV6はほぼ完成の域に達しているけども、まだ過渡期という感じで、V7が一つの完成形という印象をさらに強めた。

(追記)ここまで書いてmkdir(2)追加後のことも書いておこうと思った。4.2BSDのmkdir(1)はこんなにシンプル!

static char *sccsid = "@(#)mkdir.c	4.4 (Berkeley) 12/19/82";
/*
 * make directory
 */
#include <stdio.h>

main(argc, argv)
	char *argv[];
{
	int errors = 0;

	if (argc < 2) {
		fprintf(stderr, "mkdir: arg count\n");
		exit(1);
	}
	while (--argc)
		if (mkdir(*++argv, 0777) < 0) {
			fprintf(stderr, "mkdir: ");
			perror(*argv);
			errors++;
		}
	exit(errors != 0);
}

*1:ちなみにLinuxではmknodでディレクトリを作れない。BSDはOKなのかな?