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) = ?





