sys_stat()を読んで見る。

なんとなく昨日のLinux:sparse fileのファイルサイズと実際に使っている領域の大きさはどう調べるのか。続きで見てみます。 見ているカーネルは3.15です。

まずはarch/x86/syscalls/syscall_64.tblシステムコールの定義を見てみるとstat()はsys_newstat()使われることがわっかります。

 13 4       common  stat                    sys_newstat
 14 5       common  fstat                   sys_newfstat
 15 6       common  lstat                   sys_newlstat

このテーブルは以下のように読みます。

 5 # <number> <abi> <name> <entry point>

sys_newstat()はどういうものかというと、このような感じでざっくり言うとvfs_stat()のほうでstatのデータを取得してcp_new_stat()でユーザー空間のstatbufにデータを入れる形。

266 SYSCALL_DEFINE2(newstat, const char __user *, filename,
267                 struct stat __user *, statbuf)
268 {
269         struct kstat stat;
270         int error = vfs_stat(filename, &stat);
271 
272         if (error)
273                 return error;
274         return cp_new_stat(&stat, statbuf);
275 }

vfs_stat()は単なるラッパー関数でvfs_fstatat()を呼ぶだけ。vfs_fstatat()でstat構造体向けのデータを取得しているのはvfs_getattr()

vfs_getattr()はセキュリティ系の機能(selinuxとか)を使っている場合はsecurity_inode_getattr()、使ってない場合はvfs_getattr_nosec()が呼ばれる。 ここではvfs_getattr_nosec()を見ていくとしましょう。これも処理としてはinode構造体のinode_operationsがセットされているかどうかで呼ぶ関数が変わる程度。inode_operationsがセットされているならファイルシステム固有の処理を、セットされていなければgeneric_fillattr()を呼ぶ。

ext4はどうなっているの?ということでfs/ext4/file.cを見る。この場合ext4_getattr()が呼ばれるらしい。

613 const struct inode_operations ext4_file_inode_operations = {
614         .setattr        = ext4_setattr,
615         .getattr        = ext4_getattr,
616         .setxattr       = generic_setxattr,
617         .getxattr       = generic_getxattr,
618         .listxattr      = ext4_listxattr,
619         .removexattr    = generic_removexattr,
620         .get_acl        = ext4_get_acl,
621         .set_acl        = ext4_set_acl,
622         .fiemap         = ext4_fiemap,
623 };

ext4_getattr()もgeneric_fillattr()を呼んでいるので基本的なところはこれが担当でしょう。 処理はというとinodeのデータをkstatにコピーする。ここでサイズとかブロック数が取れている。

 21 void generic_fillattr(struct inode *inode, struct kstat *stat)
 22 {
 23         stat->dev = inode->i_sb->s_dev;
 24         stat->ino = inode->i_ino;
 25         stat->mode = inode->i_mode;
 26         stat->nlink = inode->i_nlink;
 27         stat->uid = inode->i_uid;
 28         stat->gid = inode->i_gid;
 29         stat->rdev = inode->i_rdev;
 30         stat->size = i_size_read(inode);
 31         stat->atime = inode->i_atime;
 32         stat->mtime = inode->i_mtime;
 33         stat->ctime = inode->i_ctime;
 34         stat->blksize = (1 << inode->i_blkbits);
 35         stat->blocks = inode->i_blocks;
 36 }
 37 

じゃあ、ext4固有の処理は何なのかというとブロック数に関する処理で、ファイルがinodeに入りきる程度に小さい場合はblockを割り当てないのでその場合のblocksの再計算とかが行われる。他にはブロックの遅延割り当てがある場合にblocksの数を更新するなど。sizeはi_size_read()が使われているけどx86_64環境ならinode->i_sizeがそのまま返る。そんなわけでstat構造体のst_sizeはinodeのi_size、blocksはinode->i_blocksの値が入る。

これで必要なデータをinodeから読み込んでkstat構造体に入れたのであとはcp_new_stat()でユーザー空間から渡ってきたstat構造体にデータを入れて返す。 kstat構造体からstat構造体へのコピーは基本的に単なる代入のみ。たまに処理が入っているのは実際のファイル所有者のuidをそのプロセス名前空間の人のuidに変えるとか。例えばdockerコンテナ内でstatシステムコールが呼ばれた場合にはそのコンテナ内でのuidが返る感じかな。

あとは最後にcopy_to_user()でデータをカーネル空間からユーザ空間のメモリにコピーして終了。

ちなみに、statシステムコールはsys_newstatが呼ばれるようになっているけどこれはcp_old_stat()というものがあるため。cp_old_stat()は互換性のために残しているっぽいけどコメントにこんなことが書いてあって、そもそも必要なのかも微妙に謎ですね。

36 /*
137  * For backward compatibility?  Maybe this should be moved
138  * into arch/i386 instead?
139  */