`path_lookup()`を追ってみる

はじめに

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_mountstruct vfsmountを作成する過程で、struct dentrystruct 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つの要素がこの関数の実装を複雑にしている:

  1. symbolic link
  2. mount
  3. absolute/relative path

1については、"/foo/bar/barbar"の時、"/foo/bar"がsymbolic linkかもしれないので、リンク先に飛んで、barbarファイルをlookupし、情報を入手する。 2については、mountは下図のようにoverlapさせることができる1ので適切に辿る必要がある:

f:id:knknkn11626:20190505153204p:plain
mountの概念図(The Linux Programming Interface ch.14より)

例えば、一番下の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

構造体が実際どういうことをやっているのかはここで述べるには煩瑣になりすぎるので、次章以降で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)->fsstruct 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のフェーズでは

  1. symbolic link
  2. 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_linkfollow_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という関数で、

  1. 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_lookupfollow_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つ:

  1. dentryとmntがrootおよび(rootmntもしくは、namespace上のroot6)の時、dentry, mntは更新されない -> follow_mount実行 -> return
  2. dentryがrootでない場合、単純に親ディレクトリを辿るだけ:*dentry = (*dentry)->d_parent; mntは更新されない -> follow_mount実行 -> return
  3. 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から下に下に降りていくというのでシンプルではあるが、以下の要素があり、実装が複雑になっている。

  1. symbolic link -> do_follow_link
  2. mount -> follow_mount or follow_dotdot
  3. absolute/relative path -> link_path_walkの中のcase文

で解決している。本記事では、

  • "/"
  • "/foo"
  • "/foo/"
  • "."
  • ".."
  • "/foo/bar"

あたりのpath_lookupの挙動を追うことで、大体の流れを把握する試みが成功したのではないか?と思っている。これより複雑なpathの場合も上記のいずれかのパターンにマッチしているので、他のパターンでも内部実装を追えば勉強になりそう。

補足

この章では、inode->i_opsecurity_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_lookup7 -> iget -> sb->s_op->read_inode(inode);関数に寄ってinode->i_opがfiletypeに応じて初期化される。

ちなみに、ext2では、sb->s_op->read_inodeext2_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_sbdo_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

という感じ。


  1. 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.“とある。

  2. struct open_intentはopen_execnfs(newwork filesystem)で使われているようだ

  3. ただし、一度に開けるfile数は32となっているので、これを超えた場合は、fd_arraryではないcustomのデータを指す。

  4. LAST_BINDはproc_pid_follow_linkで指定されている通り、"a symbolic link into a special filesystem"である。

  5. reference[1]では、upmost filesystemと言ってる

  6. namespaceが突然出てきたが、このオブジェクトはprocessごとに結びついており、task_struct.namespaceでアクセスできる。このnamespaceは通常、init processのnamespaceと同じ。namespaceはプロセスを他のプロセスから隔離するための仕組みであり、namespaceのrootはpivot_rootにて変更できる。(もしくは、clone()のCLONE_NEWNSフラグで新規のnamespace上のプロセスを生成できる) task_struct.fs.mntrootとは微妙に違う?(TODO: 完全に把握していない)

  7. ext2_lookupはfilp_open ->open_namei -> __lookup_hashとかlink_path_walk -> do_lookup -> real_lookupdir->i_op->lookup(dir, dentry, nd);として呼ばれる。

  8. module_initはmodule_init(x) __initcall(x);となっている。initcallがどう実行されるのかは、 http://cstmize.hatenablog.jp/entry/2019/04/22/initcall%E3%81%A8%E3%81%AF を参照のこと。