みなさん、「cd」コマンドって具体的になにやってるか分かりますか?ぼくはわかりません!!!……というわけでソースを読んでみます。※F reeBSDのshの中の”cd.c”を読みます。

  • bin/sh/cd.c

https://github.com/freebsd/freebsd/blob/master/bin/sh/cd.c

ざっくりと読む

エントリポイントは「cdcmd」関数と思われるので、そこから徐々に下っていきます。

  • cdcmd ※エントリポイント、引数のパース
    • docd ※「cdlogical」と「cdphysical」を呼び出す。どちらかが成功すればおk
    • cdlogical
      • chdir(2) システムコール
      • updatepwd
    • cdphysical
      • chdir(2) システムコール
      • updatepwd

どうやら「cdlogical」と「cdphysical」がキモのようです。
辿ってみると、変数「pflag」の値によってどちらを呼び出すのか変わるみたいですが、中身はだいたい似ている感じなので一旦この違いは無視します。続いて、

  • chdir(2)
  • updatepwd

の中身を見ていきます。

chdir(2)

とりあえず man 2 chdir します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
CHDIR(2)                  FreeBSD System Calls Manual                 CHDIR(2)

NAME
     chdir, fchdir -- change current working directory

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <unistd.h>

     int
     chdir(const char *path);

     int
     fchdir(int fd);

chdir(2) の定義は unistd.h にあるようです。 で、実装箇所をgrepで探してみたのですが、どうやら実体は sys/kern/vfs_syscalls.c の kern_chdir にあるような感じでした。
読んでみましたが具体的に何をやっているのかは正直良くわかりませんorz

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
 * Change current working directory (``.'').
 */
#ifndef _SYS_SYSPROTO_H_
struct chdir_args {
	char	*path;
};
#endif
int
sys_chdir(td, uap)
	struct thread *td;
	struct chdir_args /* {
		char *path;
	} */ *uap;
{

	return (kern_chdir(td, uap->path, UIO_USERSPACE));
}

int
kern_chdir(struct thread *td, char *path, enum uio_seg pathseg)
{
	struct nameidata nd;
	int error;

	NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | AUDITVNODE1,
	    pathseg, path, td);
	if ((error = namei(&nd)) != 0)
		return (error);
	if ((error = change_dir(nd.ni_vp, td)) != 0) {
		vput(nd.ni_vp);
		NDFREE(&nd, NDF_ONLY_PNBUF);
		return (error);
	}
	VOP_UNLOCK(nd.ni_vp, 0);
	NDFREE(&nd, NDF_ONLY_PNBUF);
	pwd_chdir(td, nd.ni_vp);
	return (0);
}

/*
 * Change notion of root (``/'') directory.
 */
#ifndef _SYS_SYSPROTO_H_
struct chroot_args {
	char	*path;
};
#endif

updatepwd

※該当箇所抜き出し

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/*
 * Update curdir (the name of the current directory) in response to a
 * cd command.  We also call hashcd to let the routines in exec.c know
 * that the current directory has changed.
 */
static void
updatepwd(char *dir)
{
	char *prevdir;

	hashcd();				/* update command hash table */

	setvar("PWD", dir, VEXPORT);
	setvar("OLDPWD", curdir, VEXPORT);
	prevdir = curdir;
	curdir = dir ? savestr(dir) : NULL;
	ckfree(prevdir);
}

やっていることは

  • hashcdの呼び出し
    • exec.cに定義されている。コメントによると、カレントディレクトリを変更したタイミングで呼ばないといけないらしい。
  • 環境変数 PWD の更新<
  • 環境変数 OLDPWD の更新
  • いろいろ後処理

まとめ

とてもざっくり言うと、

  • システムコールを呼び出してカレントディレクトリを変更
  • 環境変数 PWD の更新
  • 環境変数 OLDPWD の更新

をそれぞれ個別にやってるみたいでした!!