readlinkシステムコール
シンボリックリンク名の取得は、inodeコールバックの.readlinkから.follow_linkがコールされ、シンボリックでないファイルはこのコールバックを有しない故エラーとなります。
user_path_at_empty()は、pathnameを走査しそのファイルをpathに設定します。pathname=""なら返値は-ENOENTですが、LOOKUP_EMPTYだとエラーにならず、empty=1としdfdのファイルをpathに設定します。
.readlinkコールバックの返値、readlinkシステムコールの返値となりますが、シンボリックでないファイルは、.readlinkの定義されておらず、readlinkシステムコールの返値として、empty ? -ENOENT : -EINVALとします。
ext2_follow_link()の返値はNULL,page_follow_link_light()の返値はデータブロックをreadしたバッファのpageで、i_op->put_link()はデータブロックを読み込んだpageキャッシュとしてのpageを解放します。
user_path_at_empty()は、pathnameを走査しそのファイルをpathに設定します。pathname=""なら返値は-ENOENTですが、LOOKUP_EMPTYだとエラーにならず、empty=1としdfdのファイルをpathに設定します。
.readlinkコールバックの返値、readlinkシステムコールの返値となりますが、シンボリックでないファイルは、.readlinkの定義されておらず、readlinkシステムコールの返値として、empty ? -ENOENT : -EINVALとします。
SYSCALL_DEFINE3(readlink, const char __user *, path, char __user *, buf, int, bufsiz) { return sys_readlinkat(AT_FDCWD, path, buf, bufsiz); } SYSCALL_DEFINE4(readlinkat, int, dfd, const char __user *, pathname, char __user *, buf, int, bufsiz) { struct path path; int error; int empty = 0; if (bufsiz <= 0) return -EINVAL; error = user_path_at_empty(dfd, pathname, LOOKUP_EMPTY, &path, &empty); if (!error) { struct inode *inode = path.dentry->d_inode; error = empty ? -ENOENT : -EINVAL; if (inode->i_op->readlink) { error = security_inode_readlink(path.dentry); if (!error) { touch_atime(path.mnt, path.dentry); error = inode->i_op->readlink(path.dentry, buf, bufsiz); } } path_put(&path); } return error; }ext2ファイルシステムで、データブロックにシンボリック名称が設定されているならext2_symlink_inode_operations、inodeブロックに設定されているならext2_fast_symlink_inode_operationsがinodeコールバックとなります。
const struct inode_operations ext2_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = page_follow_link_light, .put_link = page_put_link, .setattr = ext2_setattr, #ifdef CONFIG_EXT2_FS_XATTR .setxattr = generic_setxattr, .getxattr = generic_getxattr, .listxattr = ext2_listxattr, .removexattr = generic_removexattr, #endif }; const struct inode_operations ext2_fast_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = ext2_follow_link, .setattr = ext2_setattr, #ifdef CONFIG_EXT2_FS_XATTR .setxattr = generic_setxattr, .getxattr = generic_getxattr, .listxattr = ext2_listxattr, .removexattr = generic_removexattr, #endif };.follow_linkコールバックで、シンボリック名称がnd->saved_names[nd->depth]に設定され、vfs_readlink()でchar __user *bufferのユーザ引数に複写します。
ext2_follow_link()の返値はNULL,page_follow_link_light()の返値はデータブロックをreadしたバッファのpageで、i_op->put_link()はデータブロックを読み込んだpageキャッシュとしてのpageを解放します。
int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen) { struct nameidata nd; void *cookie; int res; nd.depth = 0; cookie = dentry->d_inode->i_op->follow_link(dentry, &nd); if (IS_ERR(cookie)) return PTR_ERR(cookie); res = vfs_readlink(dentry, buffer, buflen, nd_get_link(&nd)); if (dentry->d_inode->i_op->put_link) dentry->d_inode->i_op->put_link(dentry, &nd, cookie); return res; } static inline char *nd_get_link(struct nameidata *nd) { return nd->saved_names[nd->depth]; } int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen, const char *link) { int len; len = PTR_ERR(link); if (IS_ERR(link)) goto out; len = strlen(link); if (len > (unsigned) buflen) len = buflen; if (copy_to_user(buffer, link, len)) len = -EFAULT; out: return len; }データブロックにシンボリックリンク名称が設定されている.follow_link
void *page_follow_link_light(struct dentry *dentry, struct nameidata *nd) { struct page *page = NULL; nd_set_link(nd, page_getlink(dentry, &page)); return page; } static inline void nd_set_link(struct nameidata *nd, char *path) { nd->saved_names[nd->depth] = path; } static char *page_getlink(struct dentry * dentry, struct page **ppage) { char *kaddr; struct page *page; struct address_space *mapping = dentry->d_inode->i_mapping; page = read_mapping_page(mapping, 0, NULL); if (IS_ERR(page)) return (char*)page; *ppage = page; kaddr = kmap(page); nd_terminate_link(kaddr, dentry->d_inode->i_size, PAGE_SIZE - 1); return kaddr; } static inline void nd_terminate_link(void *name, size_t len, size_t maxlen) { ((char *) name)[min(len, maxlen)] = '\0'; }inodeブロックにシンボリックリンク名称が設定されている.follow_link
static inline struct ext3_inode_info *EXT3_I(struct inode *inode) { return container_of(inode, struct ext3_inode_info, vfs_inode); } static void *ext2_follow_link(struct dentry *dentry, struct nameidata *nd) { struct ext2_inode_info *ei = EXT2_I(dentry->d_inode); nd_set_link(nd, (char *)ei->i_data); return NULL; } static inline void nd_set_link(struct nameidata *nd, char *path) { nd->saved_names[nd->depth] = path; }
補足
- fオプションのreadlinkコマンドは、readlinkシステムコールでなくgetcwdシステムコールで実装されます。
[root@localhost north]# touch babakaka [root@localhost north]# strace readlink babakaka : readlink("babakaka", 0x9f9c890, 64) = -1 EINVAL (Invalid argument) close(1) = 0 close(2) = 0 exit_group(1) = ? [root@localhost north]# strace readlink -f babakaka : getcwd("/home/north", 4096) = 11 lstat64("/home/north/babakaka", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7423000 write(1, "/home/north/babakaka\n", 21/home/north/babakaka) = 21 close(1) = 0 munmap(0xb7423000, 4096) = 0 close(2) = 0 exit_group(0) = ?