はじめに
Linux kernelにおいて、filesystemの内部のデータ構造はかなり把握しづらい。(virtual) filesystemの実装を読み解く上で重要だが、ややこしい関数として2つあると思う:
do_kern_mount
path_lookup
前者のdo_kern_mountはmounted filesystem(struct vfsmount
)を作成する関数で、
sys_mount -> do_mount -> do_new_mount -> do_kern_mount
start_kernel() -> vfs_caches_init() -> mnt_init() -> init_mount_tree() -> do_kern_mount
kern_mount(これは、do_kern_mountの補助関数)
などで使われている。do_kern_mount
はstruct vfsmount
を作成する過程で、struct dentry
やstruct inode
をallocate & initializeし、既存の構造体と色々リンクさせる。
上記の箇条書きの2番目に当たる、start_kernel() -> vfs_caches_init() -> mnt_init() -> init_mount_tree()
以下のフローは https://gist.github.com/knknkn1162/c48cb5b2023276100dc3cfed7aa84954 にまとめてみた。この関数はboot時にroot
special filesystemが作成される過程で呼ばれるものだ。
全て図に突っ込んだときの複雑さが見て取れると思う。
本記事では、後者のpath_lookup
の挙動を追ってみたい。path_lookupのやっていること自体は、pathに関する種々の情報を入手することだ:(char*)pathname をinputとして、mounted filesystem(struct vfsmount
)とdentry(struct dentry
)を返す。inodeはdentry->d_inode
で取ってこれるので、そのpathに関する情報があらかた入手できる。
内部実装としては、本質的にはroot pathから下に下に降りていくというのでシンプルではあるが、以下の3つの要素がこの関数の実装を複雑にしている:
- symbolic link
- mount
- absolute/relative path
1については、"/foo/bar/barbar"の時、"/foo/bar"がsymbolic linkかもしれないので、リンク先に飛んで、barbarファイルをlookupし、情報を入手する。 2については、mountは下図のようにoverlapさせることができる1ので適切に辿る必要がある:
例えば、一番下のcopy.cは最後にmountされたfilesystem上にあるファイルである。
3については、"../../"とか"./"みたいな相対パスが許容されるので、これを加味する必要がある。
本記事では、各構造体の意味合いについて軽く述べた上で、path_lookup()の実装をみていきたい。あとは、path_lookupが理解できることによって、virtual file systemに関わる構造体たちの把握もできたということになると思うので、かなり丁寧にこの関数の詳細を説明する。
それ以前のmountとか、inodeとか何ぞやみたいなのは、他のサイトかreference[2]のch.14が詳しい。
Note) ざっくり概要をしりたい場合はまとめの章を読んでください。
reference
- [1]Understanding the Linux kernel 3rd.ed. ch.12 (versionは前の記事と同じく2.6.11です)
- [2]The Linux Programming Interface 2nd.ed. ch.14
filesystemに関する構造体の概要
各構造体について簡単に述べる:
- struct dentry: filepathに関する情報。pathは親子関係になっているので、その情報だったり。linuxではdirectoryかfileかを区別する際には、inodeのi_typeをみて判断するので、dentryとディレクトリとは別物。あとは、diskにあるデータのcacheの役割も果たす(e.g)
__d_lookup
) - struct inode: ファイル自体の情報(
ls -l
相当の情報。filetypeやmode, 作成、更新時間とかそういうの)。 - struct super_block: 固有のfilesystemに関する情報
struct vfsmount: mountedされてるfilesystemの情報。root, mountpointや親子関係の情報など。
struct nameidata: path_lookupのときの結果を格納する用の構造体。dentryとかvfsmountへのpointerが格納されている。その他の構造体は後々出てくる2。(特に、LOOKUP_PARENTに関連するメンバ(last, last_type)とsymbolic linkに関係するメンバ(depth, saved_names))
Note) struct file
は普通のlinuxのプログラミングで言う、fopen
の戻り値と同様の構造体。このファイルは(struct files_struct)(task_struct->fs)で管理されてる。ファイルディスクリプタはfiles_structのfd_arrayメンバのindexの番号である3。
TLPIのch.5 File I/Oの図と比較してみるテスト#今日のメモ pic.twitter.com/RJ7N869uKG
— knknkn26918 (@knknkn26918) 2019年5月3日
構造体が実際どういうことをやっているのかはここで述べるには煩瑣になりすぎるので、次章以降でpath_lookupコードリーディングしていくことで把握していきたいと思う。
path_lookup
path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
となっており、struct nameidata
に探索した結果が格納される。nameはpathnameのこと。
// include/linux/namei.h struct nameidata { struct dentry *dentry; struct vfsmount *mnt; struct qstr last; unsigned int flags; int last_type; unsigned depth; char *saved_names[MAX_NESTED_LINKS + 1]; /* Intent data */ union { struct open_intent open; } intent; };
mounted filesystem(struct vfsmount
)とdentry(struct dentry
)を(struct nameidata*)ndに詰める。inodeはdentry->d_inodeから引っ張ってこれるので、pathname(文字列)からこのパスのファイルに関する情報が入手できる。
とりあえず、pathnameが
- "/"
- "/foo"
- "/foo/"
- "."
- ".."
- "/foo/bar"
の場合を考えていく。なお、filesystemに固有のcallbackが出てくるので、ext2 filesystemで考える。
"/"の場合
path_lookup(const char *name, unsigned int flags, struct nameidata *nd): name="/" - nd->mnt = mntget(current->fs->rootmnt): - nd->dentry = dget(current->fs->root): - retval = link_path_walk(name, nd): - while (*name=='/') name++; - if (!*name) return 0;
path_lookup
では、絶対パスか相対パスかを最初に判断し、retval = link_path_walk(name, nd);
を実行する。"/"の場合はすぐにlink_path_walk
が終了する。結局、path_lookupはnd->mnt=current->fs->rootmnt
, nd->dentry=current->fs->root
が格納される。
(struct task_struct*)(current)->fs
はstruct fs_struct
型で下のような感じ:
struct fs_struct { atomic_t count; rwlock_t lock; int umask; struct dentry * root, * pwd, * altroot; struct vfsmount * rootmnt, * pwdmnt, * altrootmnt; };
processごとにroot, pwdの情報が入ってる。pwdは現在のcurrent working directoryのdentry。rootは通常は"/"のdentryだが、chroot
で変更できる。
"/foo"の場合
以下のフローになっている(簡単のため、正常系のみしかまとめてない)
path_lookup(const char *name, unsigned int flags, struct nameidata *nd): name="/foo" - nd->last_type = LAST_ROOT; - lookup_flags = nd->flags; - nd->mnt = mntget(current->fs->rootmnt): - nd->dentry = dget(current->fs->root): - retval = link_path_walk(name, nd): - while (*name=='/') name++: name="foo" - (loop starts) - exec_permission_lite(inode, nd): return -EAGAIN due to ext2 specific `permission` function, ext2_permission exists - permission(inode, MAY_EXEC, nd): exec ext2_permission(inode, submask=MAY_EXEC, nd) - ext2_permission(inode, MAY_EXEC, nd) - generic_permission(inode, MAY_EXEC, ext2_check_acl): check for access rights on a Posix-like filesystem - security_inode_permission(inode, MAY_EXEC, nd) - security_ops->inode_permission (inode, MAY_EXEC, nd): selinux_inode_permission - set (struct *qstr)qstr.name = name; qstr.len=3 and qstr.hash - goto last_component - goto lookup_parent: if lookup_flags & LOOKUP_PARENT - err = do_lookup(nd, &qstr, &next: struct path*): - struct dentry *dentry = __d_lookup(nd->dentry, qstr): - get dentry whose parent is `nd->dentry` and whose name is `qstr`(search in `dentry_hashtable`) - check dentry->d_name.len == qstr.len and memcmp(dentry->d_name->name, qstr->name, qstr->len) - if dentry not found, exec dentry = real_lookup(nd->dentry, name, nd); - result = d_lookup(parent, name): redo __d_lookup(parent, name); with seqlock - struct dentry * dentry = d_alloc(parent, name): - dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL): allocate `struct denty` region - initialize dentry->(d_inode|d_parent|d_sb|d_hash|d_lru|d_subdirs|d_alias|dentry_child) - result = dir->i_op->lookup(dir, dentry, nd): in ext2, exec ext2_lookup, which reads the directory from disk. - set next->(mnt|dentry) = (nd->mnt|dentry) - follow_mount(&next.mnt, &next.dentry); - ("/foo" is symlink) ? do_follow_link(next.dentry, nd) otherwise, nd->(mnt|dentry) = next.(mnt|denry): - return 0;
前節と同じく、絶対パスなので、link_path_walkに突入するまでは同じ。
exec_permission_liteの部分は、if (inode->i_op && inode->i_op->permission) return -EAGAIN;
のため、-EAGAINを返すようになっており、すぐあとのpermission
関数を実行されるようになっている。inode->i_op->permission
のcallbackがなんなのかは少し込み入った話になるので、補足に書いた。generic_permission()
はちゃんとは調べてないので後で読みたい。security_ops
に関しては、path_lookupとずれてしまうので、これも補足に書いた。
qstr
を定めるところでは、"/foo"のfooまでを走査している。"/foo/bar"だったら、2つ目の"/"で走査がおわる。今回は、"/foo"で終わりなので、last_component
に移行する。
last_componentのフェーズでは
- symbolic link
- mount
を考慮している。1の場合(つまり"/foo"がsymbolic linkの場合)はdo_follow_link(next.dentry, nd)
を実行し、(struct nameidata*)ndに結果を埋める。symbolic linkでない場合は、do_lookupを実行し、struct path
に結果が格納される。この構造体は、struct vfsmouont*
とstruct dentry
のペアである。
2はfollow_mount(&next.mnt, &next.dentry);
で考慮されている。do_follow_link
とfollow_mount
は重いので、独立した章を設けて説明したい。(do_follow_link
は内部でlink_path_walk
を実行するので、recursiveとなる)
LOOKUP_PARENT
はpathnameがあったときに、その一つ手前のdirectoryの情報を得たいときに指定するflag。
// link_path_walk lookup_parent: nd->last = this; // this: struct qstr nd->last_type = LAST_NORM; //// enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; if (this.name[0] != '.') goto return_base; if (this.len == 1) nd->last_type = LAST_DOT; else if (this.len == 2 && this.name[1] == '.') nd->last_type = LAST_DOTDOT; else goto return_base; // return 0;
雰囲気的には、nd->mntとnd->dentryには何も手を付けずに(つまり、dirnameの情報のままで)nd->(last|last_type)
の値をセットしている。lastは今回検索対象のpathnameで、last_typeはpathnameが"."なのか".."なのか、"/"なのか、普通のファイルなのかを示す4。
do_follow_link
do_follow_link: - check the number of symlinks - cond_resched(): if scheduling, (TIF_NEED_RESCHED flag on), exec schedule() which is entrypoint of context switch. - security_inode_follow_link(dentry, nd); - security_ops->inode_follow_link (dentry, nd): security_ops=selinux_ops, selinux_inode_follow_link - current->link_count++; current->total_link_count++: increment the number of curent->(symlink count) - nd->depth++: increment the depth of symlink - __do_follow_link(dentry, nd): - touch_atime(nd->mnt, dentry): - update_atime(dentry->d_inode): read xtime with seqlock - check dentry->d_inode->(i_flags|i_sb->s_flags) is (NOATIME|NODIRATIME|RDONLY) -> return NULL; - update inode->i_atime = now if necessary. - nd_set_link(nd, NULL): nd->saved_names[nd->depth] = NULL; - dentry->d_inode->i_op->follow_link(dentry, nd): In ext2, exec ext2_follow_link - struct ext2_inode_info *ei = container_of(inode, struct ext2_inode_info, vfs_inode): ext2_inode_info contains `struct inode` object itself - nd_set_link(nd, (char *)ei->i_data): nd->saved_names[nd->depth] = ei->i_data: i_data is `__u32[15]`, which is the symbolic link name. - if success, __vfs_follow_link(nd, nd_get_link(nd): char*): nd_get_link() => nd->saved_names[nd->depth]; - link_path_walk(link, nd): exec recursively.. - current->link_count--; and nd->depth--;
do_follow_linkはext2_follow_link
を実行し、symbolic先のpathnameを(struct nameidata)ndに保存して、__vfs_follow_link
にて、そのlinkpath(文字列)を取り出す。あとは、link_path_walkで再帰的に呼ばれるという流れだ。ただし、do_follow_linkでは、無限にsymbolic linkが呼ばれて無限再帰に陥るのを防ぐため、symlinkの回数制限をかけている(reference[1]のch.12の"Lookup of Symbolic Links"を参照のこと)
ext2_inode_infoはExt2 disk inodeにアクセスするための構造体で、以下のように、struct inode
の実体が包含されている:
struct ext2_inode_info { __le32 i_data[15]; // __u32 attribute(bitwise) // skip struct inode vfs_inode; };
follow_mount
follow_mountは「はじめに」のmountの概念図にもあるように、filesystemを多重にできるので、「一番上の」filesystemを取得する関数である。
follow_mount(struct vfsmount **mnt, struct dentry **dentry): - loop: - struct vfsmount *mounted = lookup_mnt(*mnt, *dentry); - get list_head(mnt_hash member of the `struct vfsmount`) from mnt and dentry: - get vfsmount object(called `newmnt`) which list_head is previous result - find struct vfsmount if newmnt->mnt_parent == mnt && newmnt->mnt_mountpoint == dentry: find "child" vfsmount - if exists, return newmnt - if mounted==NULL, break and return; - otherwise update *mnt = mounted; && *dentry = mounted->mnt_root and goto loop
follow_mountは所要のmnt, dentryをinputとし、"最前面にある"5mnt, dentryを(破壊的に)更新する。path_lookupではfollow_mount(&next.mnt, &next.dentry);
と呼ばれるので、(struct path*)nextにfollow_mountで得られた結果が格納される。
この関数と後に出てくるfollow_dotdotという関数で、
- mount
の解決を図っている。
"/foo/"の場合
前節の"/foo"が長かったが、これが基本形なので、あとの節は下り坂だ。 今度はdirectoryが明に指定されている場合、last_componentではなく、last_with_slashesにとぶ:
path_lookup(const char *name, unsigned int flags, struct nameidata *nd): name="/foo" - nd->last_type = LAST_ROOT; - lookup_flags = nd->flags; - nd->mnt = mntget(current->fs->rootmnt): - nd->dentry = dget(current->fs->root): - retval = link_path_walk(name, nd): - while (*name=='/') name++: name="foo" - (loop starts) - exec_permission_lite(inode, nd): return -EAGAIN due to ext2 specific `permission` function, ext2_permission exists - permission(inode, MAY_EXEC, nd): exec ext2_permission(inode, submask=MAY_EXEC, nd) - set (struct *qstr)qstr.name = name; qstr.len=3 and qstr.hash: Note that (*name)="/" - while (*++name == '/'): (*name)="" - goto last_with_slashes - set lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY; - (goto last_component) - do_lookup(nd, &this, &next): put the result in (struct path*)next - follow_mount(&next.mnt, &next.dentry): put the upmost mounted filesystem in next - inode = next.dentry->d_inode: since inode is from the directory, inode->i_op->follow_link = NULL(but should be inode->i_op=ext2_dir_inode_operations) - nd->(mnt|dentry) = next.(mnt|dentry); and return 0;
前節でdo_lookup
もfollow_mount
もやったので、むずくない。。
Note) directory自体にsymbolic linkを貼ることはできない。これは、上のフローのinode->i_op->follow_link=NULL
にあらわれている。ただし、directoryに対してmountは実行できるので、follow_mount
はdirectoryに対しても実行される。
".", ".."の場合
同様にフローをyml形式で示しておく:
ただし、flagsでLOOKUP_PARENT
でないとする(LOOKUP_PARENT
の場合は、前節で説明したとおり)
path_lookup(const char *name, unsigned int flags, struct nameidata *nd): name="/foo" - lookup_flags = nd->flags; - nd->mnt = mntget(current->fs->pwdmnt): set the vfsmount of curent working directory - nd->dentry = dget(current->fs->pwd): set the dentry of curent working directory - retval = link_path_walk(name, nd): - (loop starts) - exec_permission_lite(inode, nd): return -EAGAIN due to ext2 specific `permission` function, ext2_permission exists - permission(inode, MAY_EXEC, nd): exec ext2_permission(inode, submask=MAY_EXEC, nd) - set (struct *qstr)qstr.name = name; qstr.len=1 and qstr.hash - goto last_component - if ".", break loop and return 0 - else if "..", exec follow_dotdot(&nd->mnt, &nd->dentry): put the result in (struct nameidata)nd - return 0;
上の例とは異なり、相対パスのため、nd->(mnt|dentry)はcurrent working directory(current->fs->pwd)から始まる。その後、permission checkを経てlast_componentに突入し、"."の場合(dot)は、loopを抜けて、そのままreturnする。
".."の場合(dotdot)は、一つ上の階層に移動することになるが、そのpathでの有効なfilesystemが違っている可能性がある("/dev"はdevfsだが、"/"はext2みたいな)ので、これを解消するために、follow_dotdotが呼ばれる。
follow_dotdotについて
follow_dotdot(struct vfsmount **mnt, struct dentry **dentry)
はfollow_mount
と同種の関数だが、上に移動する際に本当に移動できんの?みたいな判定基準が先に実行されたあとに、follow_mount(mnt, dentry);
が呼ばれる。その基準とは以下の3つ:
- dentryとmntがrootおよび(rootmntもしくは、namespace上のroot6)の時、dentry, mntは更新されない -> follow_mount実行 -> return
- dentryがrootでない場合、単純に親ディレクトリを辿るだけ:
*dentry = (*dentry)->d_parent;
mntは更新されない -> follow_mount実行 -> return - dentryがrootであって、rootmntでない場合、まず、親のmounted filesystemを辿る。(
*dentry = (*mnt)->mnt_mountpoint;
,*mnt = (*mnt)->mnt_parent
)その後、更新後のmnt, dentryで上記3つに当てはまるか確認する。1,2にたどり着くまで続くよ。
"/foo/bar"の場合
サブディレクトリがある場合、last_componentを初回のloop内で通らず、(struct nameidata*)ndが更新された上で(つまり、dentryとかが"/"から"/foo"のdentryに更新された上で)、for(;;)
に戻ってname="/bar"の場合を考慮していく。例のごとく、フローを書いてみる:
path_lookup(const char *name, unsigned int flags, struct nameidata *nd): name="/foo" - nd->last_type = LAST_ROOT; - lookup_flags = nd->flags; - nd->mnt = mntget(current->fs->rootmnt): - nd->dentry = dget(current->fs->root): - retval = link_path_walk(name, nd): - while (*name=='/') name++: (*name)="foo" - (loop starts) - exec_permission_lite(inode, nd): return -EAGAIN due to ext2 specific `permission` function, ext2_permission exists - permission(inode, MAY_EXEC, nd): exec ext2_permission(inode, submask=MAY_EXEC, nd) - set (struct *qstr)qstr.name = name; qstr.len=3 and qstr.hash: *name is now "/bar" - while (*++name == '/'): (*name)=bar - nd->flags |= LOOKUP_CONTINUE; - do_lookup(nd, &this, &next); - follow_mount(&next.mnt, &next.dentry); - "/foo"がsymbolic linkだった場合の処理(inode->i_op->follow_linkがnot nullなので) -> do_follow_linkと続く - "/foo"がそうでない場合は、nd->mnt = next.mnt; nd->dentry = next.dentry; - return (loop starts)。Note that (*name)="bar" - (from now on, equivalent to the case of "/foo"): later, nd->flags ~= LOOKUP_CONTINUE;
nd->flags
はflag argumentにLOOKUP_PARENT
を付けた場合に使用される。あとは似たような感じやね。
まとめ
path_lookupの実装を追ってみた。本質的にはroot pathから下に下に降りていくというのでシンプルではあるが、以下の要素があり、実装が複雑になっている。
- symbolic link ->
do_follow_link
- mount ->
follow_mount
orfollow_dotdot
- absolute/relative path ->
link_path_walk
の中のcase文
で解決している。本記事では、
- "/"
- "/foo"
- "/foo/"
- "."
- ".."
- "/foo/bar"
あたりのpath_lookupの挙動を追うことで、大体の流れを把握する試みが成功したのではないか?と思っている。これより複雑なpathの場合も上記のいずれかのパターンにマッチしているので、他のパターンでも内部実装を追えば勉強になりそう。
補足
この章では、inode->i_op
とsecurity_ops
について述べる。path_lookup()を説明するには煩瑣になりすぎるが、path_lookupから利用されている項目なので、補足としている。
inode->i_op
について
inode->i_op
はcallbackのcollectionでstruct inode_operations
だが、inodeのfiletypeによって異なる。(inode->i_fopも同じ様に設定されるが、今回のpath_lookkupでは使用しないので、今回の記事では述べない。filetypeのfileを新規作成するときはinode->i_op
に以下のoperationsが割り当てられる:
- ext2_create -> ext2_file_inode_operations;
- ext2_symlink -> ext2_fast_symlink_inode_operations or ext2_symlink_inode_operations(symnameが大きい場合)
- ext2_mknod -> ext2_special_inode_operations
- ext2_mkdir -> ext2_dir_inode_operations
これらの関数はsys_(create|symlink|mknod|mkdir)
が呼ばれるときに、specific filesystemの関数として内部で呼ばれる。
または、すでにファイルがあるがinode objectとして存在しない場合(ext2_lookup
でinodeをallocする必要がある時)に関しても、ext2_lookup
7 -> iget
-> sb->s_op->read_inode(inode);
関数に寄ってinode->i_op
がfiletypeに応じて初期化される。
ちなみに、ext2では、sb->s_op->read_inode
はext2_read_inode
となるが、sb->s_op
がどうやって決定されるかは、ext2_get_sb -> get_sb_bdev -> ext2_fill_superでsb->s_op = &ext2_sops;
として定義されている。そして、ext2_sops.read_inode = ext2_read_inode
となってる。
ext2_get_sb
はdo_kern_mount
にて使用される。do_kern_mountは冒頭で述べたように、struct vfsmount
を作成(allocate~initialize~configureまで行う)する関数であった。ext2_get_sb自体ははfile_system_typeのget_sbメンバで以下のようになっている:
// fs/ext2/super.c static struct file_system_type ext2_fs_type = { .owner = THIS_MODULE, .name = "ext2", .get_sb = ext2_get_sb, .kill_sb = kill_block_super, .fs_flags = FS_REQUIRES_DEV, };
ext2_fs_typeはmodule_init(init_ext2_fs)
とある8ので、init_ext2_fsからregister_filesystem(init_ext2_fs)
にて、(static struct file_system_type*)file_systems
のリストに登録される。
security_opsについて
Note) この章では、linuxのsecurity自体については大して触れていない...
defaultでは、security_ops=selinux_ops
と定義されるようだ。そして、.inode_permission = selinux_inode_permission
となっている。
security_opsは直接的には、register_securityで定義されている:
// security/security.c int register_security(struct security_operations *ops) { if (verify(ops)) { // security_fixup_ops(ops); printk(KERN_DEBUG "%s could not verify " "security_operations structure.\n", __FUNCTION__); return -EINVAL; } // re-register is not allowed if (security_ops != &dummy_security_ops) return -EAGAIN; security_ops = ops; return 0; }
boot時のstart_kernel ->security_initにてsecurity_ops = &dummy_security_ops;
と指定された後、do_security_initcalls();を実行する。この関数は以下のようになっている:
// security/security.c struct security_operations *security_ops; static void __init do_security_initcalls(void) { initcall_t *call; call = __security_initcall_start; // exec rootplug_init(), capability_init() and selinux_init() while (call < __security_initcall_end) { (*call) (); call++; } }
__secturity_initcall_startはarch/i386/kernel/vmlinux.lds.S
にて、以下のように定義されている
// arch/i386/kernel/vmlinux.lds.S // Uninitialized kernel data __initcall_start = .; // skip __initcall_end = .; __con_initcall_start = .; // skip __con_initcall_end = .; // #define VMLINUX_SYMBOL(_sym_) _sym_ // #define SECURITY_INIT \ // .security_initcall.init : { \ // VMLINUX_SYMBOL(__security_initcall_start) = .; \ // *(.security_initcall.init) \ // VMLINUX_SYMBOL(__security_initcall_end) = .; \ // } SECURITY_INIT
また、このsectionに関数を定義するために、security_initcall(fn)
というマクロが定義されている:
// include/linux/init.h #define security_initcall(fn) \ static initcall_t __initcall_##fn \ __attribute_used__ __attribute__((__section__(".security_initcall.init"))) = fn
security_initcallはsecurity_initcall (capability_init);
, security_initcall(selinux_init);
, security_initcall (rootplug_init);
それぞれのソースコードを見る限りでは、capability_init or rootplug_init -> selinux_initと呼ばれるっぽい。defaultでは、#define selinux_enabled 1
となっており、
- capability_init - register_security (&capability_ops): capability_disable = 0のため(多分) - security_ops = capability_ops - selinux_init: -original_ops = secondary_ops = security_ops: security_ops=capability_ops in the previous process - register_security (&selinux_ops) - security_ops = selinux_ops
という感じ。
-
mount自体はunixからある考え方だが、このmountの特性に関してはlinux specificのようだ。Reference[1]には、"When a filesystem is mounted on a directory, the contents of the directory in the parent filesystem are no longer accessible, because every pathname, including the mount point, will refer to the mounted filesystem. However, the original directory’s content shows up again when the filesystem is unmounted. This somewhat surprising feature of Unix filesystems is used by system administrators to hide files; they simply mount a filesystem on the directory containing the files to be hidden.“とある。↩
-
struct open_intentは
open_exec
やnfs(newwork filesystem)で使われているようだ↩ -
ただし、一度に開けるfile数は32となっているので、これを超えた場合は、fd_arraryではないcustomのデータを指す。↩
-
LAST_BINDは
proc_pid_follow_link
で指定されている通り、"a symbolic link into a special filesystem"である。↩ -
reference[1]では、
upmost filesystem
と言ってる↩ -
namespaceが突然出てきたが、このオブジェクトはprocessごとに結びついており、task_struct.namespaceでアクセスできる。このnamespaceは通常、init processのnamespaceと同じ。namespaceはプロセスを他のプロセスから隔離するための仕組みであり、namespaceのrootは
pivot_root
にて変更できる。(もしくは、clone()のCLONE_NEWNS
フラグで新規のnamespace上のプロセスを生成できる)task_struct.fs.mntroot
とは微妙に違う?(TODO: 完全に把握していない)↩ -
ext2_lookupはfilp_open ->open_namei ->
__lookup_hash
とかlink_path_walk
->do_lookup
->real_lookup
でdir->i_op->lookup(dir, dentry, nd);
として呼ばれる。↩ -
module_initは
module_init(x) __initcall(x);
となっている。initcallがどう実行されるのかは、 http://cstmize.hatenablog.jp/entry/2019/04/22/initcall%E3%81%A8%E3%81%AF を参照のこと。↩