こんにちは。NMです。
例えば、webアプリケーションを開発しているとします。
もちろんフレームワークの選定をし、そのフレームワークを用いて実装をしているでしょう。
そしてフレームワークの挙動を確認するために、ソースコードを漁る。
こんなことはよくあることではないでしょうか?
使うことは出来るが(使えればいいんですがね)、
フレームワークの挙動の詳細を理解していないシーンは珍しくありません。
これは、そんなフレームワークに限ったことではなく、
例えば、私の目の前のターミナルには、
リリース手順を書きかけの状態で、コマンドの結果としてカレントディレクトリが表示されています。
pwd
これは老若男女(?)誰もが知っている、現在のカレントディレクトリを返すコマンド。
でも実際には「どう返しているのか?」と問われると、私は「?!」。
気になったら善は急げ。ソースコードを読もう!
ソースコードの準備
一応、pwdコマンドのパッケージを確認。
rpm -qf `which pwd`
# coreutils-8.4-37.el6.x86_64
そしてソースコードをダウンロード
wget "http://ftp.gnu.org/gnu/coreutils/coreutils-8.4.tar.gz"
tar -zxvf coreutils-8.4.tar.gz; cd coreutils-8.4
目的のコードは src/pwd.c
。
(ソースコード)
コード量は
wc -l src/pwd.c
# 390 src/pwd.c
結構短い。
まずは main 関数。
最初にコマンドライン引数を処理しています。
-L (–logical) と -P(–physical)のオプションが渡されたとき、logical
変数のフラグを変えていることが分かります。(346行~351行)
324 int
325 main (int argc, char **argv)
326 {
327 char *wd;
328 /* POSIX requires a default of -L, but most scripts expect -P. */
329 bool logical = (getenv ("POSIXLY_CORRECT") != NULL);
330
331 initialize_main (&argc, &argv);
332 set_program_name (argv[0]);
333 setlocale (LC_ALL, "");
334 bindtextdomain (PACKAGE, LOCALEDIR);
335 textdomain (PACKAGE);
336
337 atexit (close_stdout);
338
339 while (1)
340 {
341 int c = getopt_long (argc, argv, "LP", longopts, NULL);
342 if (c == -1)
343 break;
344 switch (c)
345 {
346 case 'L':
347 logical = true;
348 break;
349 case 'P':
350 logical = false;
351 break;
352
353 case_GETOPT_HELP_CHAR;
354
355 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
356
357 default:
358 usage (EXIT_FAILURE);
359 }
360 }
361
362 if (optind < argc)
363 error (0, 0, _("ignoring non-option arguments"));
こちらが --help
ですが、
/bin/pwd --help
# Usage: /bin/pwd [OPTION]...
# Print the full filename of the current working directory.
#
# -L, --logical use PWD from environment, even if it contains symlinks
# -P, --physical avoid all symlinks
# --help display this help and exit
# --version output version information and exit
#
# ...
-L
と -P
の違いは、以下で理解できると思います。
mkdir a
ln -sn a b; cd b
/bin/pwd -L
# /home/nemoto/b
/bin/pwd -P
# /home/nemoto/a
話を戻して main関数 の続きです。
はじめに logical
が true
の場合の処理を行います。
つまり -L
オプションの処理です。
365 if (logical)
366 {
367 wd = logical_getcwd ();
368 if (wd)
369 {
370 puts (wd);
371 exit (EXIT_SUCCESS);
372 }
373 }
logical_getcwd
関数を呼んでいまが、この関数は src/pwd.c
内にあります。
297 static char *
298 logical_getcwd (void)
299 {
300 struct stat st1;
301 struct stat st2;
302 char *wd = getenv ("PWD");
303 char *p;
304
305 /* Textual validation first. */
306 if (!wd || wd[0] != '/')
307 return NULL;
308 p = wd;
309 while ((p = strstr (p, "/.")))
310 {
311 if (!p[2] || p[2] == '/'
312 || (p[2] == '.' && (!p[3] || p[3] == '/')))
313 return NULL;
314 p++;
315 }
316
317 /* System call validation. */
318 if (stat (wd, &st1) == 0 && stat (".", &st2) == 0 && SAME_INODE(st1, st2))
319 return wd;
320 return NULL;
321 }
logical_getcwd
では、
302行目で getenv
で 環境変数PWD
を参照しています。
305行目以降では、その値のバリデーションをチェックしています。
317行目以降では、環境変数PWD
のディレクトリ情報と、カレントディレクトリ .
のディレクトリ情報が同じであるかをチェックしているということがわかりますね?
ということは、以下のように環境変数 PWD
を操作し、 pwd
コマンドを実行すると
( PWD=/home; /bin/pwd -L )
# /home/nemoto/a
というように、上の例で -L
オプションを指定し取得できた値と異なる値が取れていることがわかります。
368行目を見ると、NULLの場合には次の処理へ流れていくことがわかると思います。
この次に説明しますが、その次の処理というのが -P
オプションの処理になります。
つまり、環境変数PWDの値がカレントディレクトリと異なる場合には、-P
オプションと同一の結果を表示することになります。
では、その続きのmain関数。つまり -P
オプションを指定した場合の処理です。
375 wd = xgetcwd ();
376 if (wd != NULL)
377 {
378 puts (wd);
379 free (wd);
380 }
381 else
382 {
383 struct file_name *file_name = file_name_init ();
384 robust_getcwd (file_name);
385 puts (file_name->start);
386 file_name_free (file_name);
387 }
388
389 exit (EXIT_SUCCESS);
390 }
375行目で xgetcwd
を呼んでいます。
こちらは、lib/xgetcwd.c
を参照してください。以下がコードです。
34 char *
35 xgetcwd (void)
36 {
37 char *cwd = getcwd (NULL, 0);
38 if (! cwd && errno == ENOMEM)
39 xalloc_die ();
40 return cwd;
41 }
単純に getcwd
を呼んでいます。これが pwd -P
の実体です。
では、main関数の残りを見ていきましょう。
376行目では、xgetcwd
でディレクトリが取得できれば、それを表示しています。
382行目以降では、そこで取得できなかった場合の処理へと続きます。( robust_getcwd
)
265 static void
266 robust_getcwd (struct file_name *file_name)
267 {
268 size_t height = 1;
269 struct dev_ino dev_ino_buf;
270 struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf);
271 struct stat dot_sb;
272
273 if (root_dev_ino == NULL)
274 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
275 quote ("/"));
276
277 if (stat (".", &dot_sb) < 0)
278 error (EXIT_FAILURE, errno, _("failed to stat %s"), quote ("."));
279
280 while (1)
281 {
282 /* If we've reached the root, we're done. */
283 if (SAME_INODE (dot_sb, *root_dev_ino))
284 break;
285
286 find_dir_entry (&dot_sb, file_name, height++);
287 }
288
289 /* See if a leading slash is needed; file_name_prepend adds one. */
290 if (file_name->start[0] == '')
291 file_name_prepend (file_name, "", 0);
292 }
これは何をしているかというと、280行~287行で、find_dir_entry
を呼んでいます。
こちらは、file_name
で指定したディレクトリの親を検索し、参照渡ししている dot_sb
にセットしています。
while
では、dot_sb が /
のディレクトリ情報と同じになるまで続き、その結果返す。という処理の流れになります。
(find_dir_entry
は src/pwd.c
内にあります。ここでは割愛)
pwd
コマンドのまとめ
pwd
コマンドの流れは、
-L
オプション :
- 環境変数
PWD
を返す。 - 取得できなかった場合は、
-P
オプションの処理に続く。
-P
オプション(default)
getcwd
の結果を返す。- 取得できなかった場合は、カレントディレクトリから
/
まで辿り、そのパスを返す。
となるのかな。
まとめ
今回は pwd
のコードリーディングを行いました。
結果、どのような挙動をしているのかを理解することが出来ました。
プログラムの挙動がどのようになっているのかを理解することは、運用上必要のないことなのかもしれません。
しかし、冒頭でも話したように、現場では摩訶不思議な挙動による障害に悩まされることは、珍しいことではないとはずです。
その場合、ソースコードレベルでの調査は、避けて通れないのではないのかと。
その中、コードリーディングの癖をつけておくことは、業務改善の一環になるのではないでしょうか?
みなさんも暇を見つけて、先人の英知に触れてみてはどうでしょう?
面白かったのでまた、面白いコードがあればご紹介するかも!?