嗯..在linux-3.6.2/fs/namei.c
中有许多相似的情况。例如,rename
系统调用实际上是这样定义的:
SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newname)
{
return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname);
}
换句话说,从另一个系统调用中调用系统调用没有问题。问题在于指针参数是用户空间指针,而你试图提供内核指针:你的 fileName 应该在用户空间分配,但实际上在内核空间分配了。
正确的解决方案是将两个函数(你自己的函数和 fs/namei.c 中的 sys_renameat())中的公共代码分离出来,然后从两个系统调用中调用该函数。假设你不想让此代码上游包含 - 如果是这样,那么现在是重构和重新思考的时候了 - 你可以轻松地将 sys_renameat 的内容复制到你自己的函数中;它并不大。这也是熟悉文件系统操作所需的必要检查和锁定的有用点。
为了解释问题和解决方案而进行编辑:
从非常真实的意义上讲,由正常进程(用户空间内存)分配的内存和由内核(内核空间)分配的内存完全被内核-用户空间屏障分隔开来。
你的代码忽略了这个屏障,根本不应该工作。(它可能在 x86 上部分工作,因为在该体系结构上从内核端很容易穿过内核-用户空间屏障。)你还使用了 256 字节的栈来存储文件名,这是不对的:内核栈是一种非常有限的资源,应该节约使用。
正常进程(用户空间进程)无法访问任何内核内存。你可以试试,但它行不通。这就是屏障存在的原因。(有些嵌入式系统具有硬件,根本不支持这样的屏障,但为了本次讨论,我们将忽略这些。请记住,即使在 x86 上,屏障很容易从内核端穿过,也并不意味着它不存在。不要表现得像个混蛋,认为因为它对你似乎有效,所以它是正确的。)
屏障的性质是,在大多数架构上,内核也存在屏障。
为了帮助内核程序员,指向用户空间的指针被标记为 __user。这意味着你不能只引用它们并期望它们工作;你需要使用 copy_from_user() 和 copy_to_user()。这不仅适用于系统调用参数:当你从内核访问用户空间数据时,你需要使用这两个函数。
所有系统调用都在用户空间数据上工作。你看到的每个指针都应该被标记为 __user。每个系统调用都会执行访问用户空间数据所需的所有工作。
你的问题在于,你试图向系统调用提供内核空间数据 inputFile。它不会工作,因为系统调用总是尝试穿过屏障,但 inputFile 在屏障的同一侧!
没有什么明智的方法可以将 inputFile
复制到屏障的另一侧。当然,有一些方法可以实现,而且也不是很难,但这并不明智。
因此,让我们探讨上述正确解决方案,这个方案已经被 footy 拒绝了一次。
首先,让我们看看当前(3.6.2)Linux内核中 renameat
系统调用的实际情况(请记住,该代码受 GPLv2 许可)。rename
系统调用只需使用 sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname)
调用它。我会插入对代码的解释:
SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
int, newdfd, const char __user *, newname)
{
struct dentry *old_dir, *new_dir;
struct dentry *old_dentry, *new_dentry;
struct dentry *trap;
struct nameidata oldnd, newnd;
char *from;
char *to;
int error;
在内核中,堆栈是一种有限的资源。你可以使用相当多的变量,但任何本地数组都会成为一个严重的问题。上述本地变量列表几乎是你在典型系统调用中能看到的最大的。
对于重命名调用,函数必须首先定位包含文件名的父目录:
error = user_path_parent(olddfd, oldname, &oldnd, &from);
if (error)
goto exit;
注意:在此之后,旧目录和路径必须通过调用
path_put(&oldnd.path); putname(from);
来释放使用。
error = user_path_parent(newdfd, newname, &newnd, &to);
if (error)
goto exit1;
注意:在此之后,必须通过调用
path_put(&newnd.path); putname(to);
来释放新目录和路径。
下一步是检查这两个是否驻留在同一文件系统中:
error = -EXDEV;
if (oldnd.path.mnt != newnd.path.mnt)
goto exit2;
目录中的最后一个组件必须是普通目录:
old_dir = oldnd.path.dentry;
error = -EBUSY;
if (oldnd.last_type != LAST_NORM)
goto exit2;
new_dir = newnd.path.dentry;
if (newnd.last_type != LAST_NORM)
goto exit2;
目录所在的挂载点必须是可写的。请注意,如果成功,则会对挂载点应用锁定,并且在系统调用返回之前必须始终与mnt_drop_write(oldnd.path.mnt)
调用配对。
error = mnt_want_write(oldnd.path.mnt);
if (error)
goto exit2;
接下来,nameidata查找标志被更新以反映目录已经被知道:
oldnd.flags &= ~LOOKUP_PARENT;
newnd.flags &= ~LOOKUP_PARENT;
newnd.flags |= LOOKUP_RENAME_TARGET;
接下来,两个目录在重命名期间被锁定。这必须与相应的解锁调用
unlock_rename(new_dir, old_dir)
配对使用。
trap = lock_rename(new_dir, old_dir);
接下来,将查找实际存在的文件。如果成功,需要通过调用dput(old_dentry)
来释放dentry:
old_dentry = lookup_hash(&oldnd);
error = PTR_ERR(old_dentry);
if (IS_ERR(old_dentry))
goto exit3;
/* source must exist */
error = -ENOENT;
if (!old_dentry->d_inode)
goto exit4;
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!S_ISDIR(old_dentry->d_inode->i_mode)) {
error = -ENOTDIR;
if (oldnd.last.name[oldnd.last.len])
goto exit4;
if (newnd.last.name[newnd.last.len])
goto exit4;
}
/* source should not be ancestor of target */
error = -EINVAL;
if (old_dentry == trap)
goto exit4;
新文件名的目录项也被查找(毕竟它可能已经存在)。如果成功,同样需要使用
dput(new_dentry)
来释放此目录项:
new_dentry = lookup_hash(&newnd);
error = PTR_ERR(new_dentry);
if (IS_ERR(new_dentry))
goto exit4;
/* target should not be an ancestor of source */
error = -ENOTEMPTY;
if (new_dentry == trap)
goto exit5;
此时,该函数已确定一切都正常。接下来,它必须调用security_path_rename(struct path *old_dir, struct dentry *old_dentry, struct path *new_dir, struct dentry *new_dentry)
检查操作是否可以进行(涉及访问模式等)。用户空间进程的身份详细信息由current
维护。
error = security_path_rename(&oldnd.path, old_dentry,
&newnd.path, new_dentry);
if (error)
goto exit5;
如果没有反对重命名,那么可以使用vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry)
进行实际的重命名:
error = vfs_rename(old_dir->d_inode, old_dentry,
new_dir->d_inode, new_dentry);
此时,所有的工作都已完成(如果error
为零,则成功),唯一剩下的就是释放各种查找。
exit5:
dput(new_dentry);
exit4:
dput(old_dentry);
exit3:
unlock_rename(new_dir, old_dir);
mnt_drop_write(oldnd.path.mnt);
exit2:
path_put(&newnd.path);
putname(to);
exit1:
path_put(&oldnd.path);
putname(from);
exit:
return error;
}
重命名操作就是这样了。正如您所看到的,没有明确的copy_from_user()
。 user_path_parent()
调用getname()
,后者调用getname_flags()
完成此操作。如果您忽略所有必要的检查,它可以简化为以下内容:
char *result = __getname();
in len;
len = strncpy_from_user(result, old/newname, PATH_MAX);
if (len <= 0) {
__putname(result);
}
if (len >= PATH_MAX) {
__putname(result);
}
audit_getname(result);
并且,在它不再被需要之后,
putname(result);
所以,footy,你的问题没有简单的解决方案。没有一个单一的函数调用可以神奇地使你的系统调用工作。你必须重新编写它,查看fs/namei.c
中正确处理的内容。这并不难,但你必须小心谨慎地去做,并且最重要的是接受这样一种方法:“只试图用最少的更改让这个简单的东西工作”对此无效。