パスにおいて /a/b/../c と /a/c は等価か

今、分散コンパイラのキャッシュを作るようなお仕事をやっています。キャッシュを作るときには、キャッシュヒット率を上げるためにファイル名はなるべく正規化して持ちたいと思うことがしばしばあります。

コンパイラですと、-I で指定されたインクルードディレクトリ名とインクルードファイルを join してパスを得ることがあります。インクルードディレクトリやファイルが ../ を含んでいることは多分にあり、もし、../などを解決して絶対パス名にして保持できると、その後の扱いが楽にあります。例えば、パス名として /a/b/../c が与えられたとき、/a/c の形で保持できると少し嬉しいわけです。ファイルにアクセスすると遅いため、できればパス名だけでこのような正規化ができると嬉しいわけです。

さて、このような正規化をしてもいいのでしょうか。ダメだとするとどういう例があるでしょうか。

ダメそうな例として、b がどこかの directory への symlink である場合が考えられます。例えば /s/t への symlink であったとします。/a/b/../c を symlink を辿って解決すると、/s/c になり、/a/c とは違う場所を指しています。

実際、gcc で次のような ファイル構成および内容のとき、

a/b
  s/t への symlink
a/c.h
  #define KOTORI 100
s/c.h
  #define KOTORI 200
s/t
  空ディレクトリ
test.h
  #include <a/b/../c.h>
test.cc
  #include <stdio.h>
  #include <test.h>
  int main() { printf("%d\n", KOTORI); return 0; }

gcc -I. test.c として出来上がった実行ファイルは何を出力するでしょうか。symlink を解決していれば 200 が、解決されてなければ 100 が表示されるはずです。

$ gcc -I. test.c
$ ./a.out
200

実際は 200 となるため、symlink が解決されており、/a/b/../c/a/c は等価ではないという結論になりました。

一見 /a/b/../c/a/c にしたくなるのはわかりますが、これは悪魔の囁きです。

2017-06-05