openフラグのO_PATH
opeat/fchdir関数は、引数のファイルIDのディレクトリをカレントディレクトリとして、以降のパスを検索する。
openシステムコールの処理は、ファイルのdentryを検索し、配下のinodeのコールバック関数等の種々の情報を、このファイルID下のFILE構造体に設定するものだ。
しかし、opeat/fchdir関数の引数とするファイルIDは、コールバック関数等の処理は必要ない。そこで導入されたのが、O_PATHフラグである。
まずf->f_modeに、デフォルトとなるモードを設定するが、f->f_flags & O_PATHなら、f->f_mode = FMODE_PATHとなる。そしてFILE構造体のf_mapping/f_path.dentry/f_path.mnt/f_posを設定し、スーパブロックオブジェクトに、このFILEディスクリプタを登録する。そして、FILEコールバック関数として、empty_fops[]を設定することで終了している。opeat/fchdir関数で必要となる情報は、f_path.dentryだけよい。
ちなみに、通常の流れは、fops_get()でinodeのコールバック関数をFILEコールバック関数に設定し、security_dentry_open()で、selinuxでのdentryオープンチェック。OKならbreak_lease()でファイルがロックされていないかのチェックをする。場合によっては、ここでスリープする事もありえる。そしてopenコールバック関数をコールする。i_readcount_inc()はIMAというセキュリティにかかるチェック、file_ra_state_init()は先読みの処理で、そしてFILEディスクリプタの取得となる。
したがってO_PATHオプションは、セキュリティ/ファイルのロックに関係なく、ファイルIDが取得できるように導入されてものと思われる。コールバック関数は、empty_fopsのため、仮にこのファイルIDでread/writeされても問題ない。
しかし、inodeのコールバック関数をFILE構造体にあえて設定することで、おなじファイルをオープンしても、その処理を異なるものにできる。と言う設計をしていたんだな。と思うと、Linux開発者の方々には頭の下がる思いです。
。
openシステムコールの処理は、ファイルのdentryを検索し、配下のinodeのコールバック関数等の種々の情報を、このファイルID下のFILE構造体に設定するものだ。
しかし、opeat/fchdir関数の引数とするファイルIDは、コールバック関数等の処理は必要ない。そこで導入されたのが、O_PATHフラグである。
実装
openシステムコールがコールされると、パスのdentryを取得して、__dentry_open()がコールされる。ここでFILE構造体が設定される。まずf->f_modeに、デフォルトとなるモードを設定するが、f->f_flags & O_PATHなら、f->f_mode = FMODE_PATHとなる。そしてFILE構造体のf_mapping/f_path.dentry/f_path.mnt/f_posを設定し、スーパブロックオブジェクトに、このFILEディスクリプタを登録する。そして、FILEコールバック関数として、empty_fops[]を設定することで終了している。opeat/fchdir関数で必要となる情報は、f_path.dentryだけよい。
ちなみに、通常の流れは、fops_get()でinodeのコールバック関数をFILEコールバック関数に設定し、security_dentry_open()で、selinuxでのdentryオープンチェック。OKならbreak_lease()でファイルがロックされていないかのチェックをする。場合によっては、ここでスリープする事もありえる。そしてopenコールバック関数をコールする。i_readcount_inc()はIMAというセキュリティにかかるチェック、file_ra_state_init()は先読みの処理で、そしてFILEディスクリプタの取得となる。
したがってO_PATHオプションは、セキュリティ/ファイルのロックに関係なく、ファイルIDが取得できるように導入されてものと思われる。コールバック関数は、empty_fopsのため、仮にこのファイルIDでread/writeされても問題ない。
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt, struct file *f, int (*open)(struct inode *, struct file *), const struct cred *cred) { static const struct file_operations empty_fops = {}; struct inode *inode; int error; f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE; if (unlikely(f->f_flags & O_PATH)) f->f_mode = FMODE_PATH; inode = dentry->d_inode; if (f->f_mode & FMODE_WRITE) { error = __get_file_write_access(inode, mnt); if (error) goto cleanup_file; if (!special_file(inode->i_mode)) file_take_write(f); } f->f_mapping = inode->i_mapping; f->f_path.dentry = dentry; f->f_path.mnt = mnt; f->f_pos = 0; file_sb_list_add(f, inode->i_sb); if (unlikely(f->f_mode & FMODE_PATH)) { f->f_op = &empty_fops; return f; } f->f_op = fops_get(inode->i_fop); error = security_dentry_open(f, cred); if (error) goto cleanup_all; error = break_lease(inode, f->f_flags); if (error) goto cleanup_all; if (!open && f->f_op) open = f->f_op->open; if (open) { error = open(inode, f); if (error) goto cleanup_all; } if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) i_readcount_inc(inode); f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping); if (f->f_flags & O_DIRECT) { if (!f->f_mapping->a_ops || ((!f->f_mapping->a_ops->direct_IO) && (!f->f_mapping->a_ops->get_xip_mem))) { fput(f); f = ERR_PTR(-EINVAL); } } return f; : : }
補足
O_PATHのファイルIDのfcntlシステムコールは、F_DUPFD/F_DUPFD_CLOEXEC/F_GETFD/ F_SETFD/ F_GETFLのみ有効となる。static int check_fcntl_cmd(unsigned cmd) { switch (cmd) { case F_DUPFD: case F_DUPFD_CLOEXEC: case F_GETFD: case F_SETFD: case F_GETFL: return 1; } return 0; } SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) { struct file *filp; long err = -EBADF; filp = fget_raw(fd); if (!filp) goto out; if (unlikely(filp->f_mode & FMODE_PATH)) { if (!check_fcntl_cmd(cmd)) { fput(filp); goto out; } } err = security_file_fcntl(filp, cmd, arg); if (err) { fput(filp); return err; } err = do_fcntl(fd, cmd, arg, filp); fput(filp); out: return err; }また、カーネル内でファイルIDからFILEオブジェクトを取得する場合、通常fget()がコールされる。これはO_PATH(file->f_mode & FMODE_PATH)でオープンされたFILEオブジェクトは取得することはできないようになっている。
struct file *fget(unsigned int fd) { struct file *file; struct files_struct *files = current->files; rcu_read_lock(); file = fcheck_files(files, fd); if (file) { if (file->f_mode & FMODE_PATH || !atomic_long_inc_not_zero(&file->f_count)) file = NULL; } rcu_read_unlock(); return file; }ただし、fcntlシステムコールのように、O_PATHのFILEオブジェクトを取得する必要がある場合、fget_raw()がコールされる。
struct file *fget_raw(unsigned int fd) { struct file *file; struct files_struct *files = current->files; rcu_read_lock(); file = fcheck_files(files, fd); if (file) { if (!atomic_long_inc_not_zero(&file->f_count)) file = NULL; } rcu_read_unlock(); return file; }
追記
FILE構造体にはdentryが設定されており、従ってそこからinodeへ、そしてread/writeのコールバック関数を参照することが可能であって、fileオペレーションとして、わざわざそれを再設定し、そのコールバック関数を呼び出すことでread/writeすることもないのでは。と漠然と思っていました。しかし、inodeのコールバック関数をFILE構造体にあえて設定することで、おなじファイルをオープンしても、その処理を異なるものにできる。と言う設計をしていたんだな。と思うと、Linux開発者の方々には頭の下がる思いです。
。