嗯..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
应该在用户空间中分配,但您的在内核空间中。
正确的解决方案是从两个函数(your 和sys_renameat()
in fs/namei.c
)中提取出公共代码,然后从两个系统调用中调用该函数。假设您没有尝试将其包含在上游——如果是,那么它是重构和重新思考的时间——您可以轻松地将内容复制sys_renameat
到您自己的函数中;它不是那么大。熟悉诸如此类的文件系统操作所需的必要检查和锁定也是一个有用的点。
编辑以解释问题和解决方案:
在非常真实的意义上,普通进程分配的内存(用户空间内存)和内核分配的内存(内核空间)完全被内核-用户空间屏障隔开。
您的代码忽略了这个障碍,根本不应该工作。(它可能在 x86 上有点工作,因为在该体系结构上,内核用户空间屏障很容易从内核端穿透。)您还使用 256 字节的堆栈作为文件名,这是一个禁忌:内核堆栈是一个资源非常有限,应谨慎使用。
普通进程(用户空间进程)无法访问任何内核内存。你可以试试,不会的。这就是障碍存在的原因。(有些嵌入式系统的硬件根本不支持这种屏障,但为了讨论的目的,让我们忽略这些。记住,即使在 x86 上,屏障很容易从内核端突破,但这并不意味着它不存在。不要因为它似乎对你有用而假设,它在某种程度上是正确的。)
屏障的性质使得在大多数架构上,内核也存在屏障。
为了帮助内核程序员,指向用户空间屏障的指针被标记为__user
. 这意味着您不能仅仅取消引用它们并期望它们起作用;您需要使用copy_from_user()
和copy_to_user()
。它不仅仅是系统调用参数:当您从内核访问用户空间数据时,您需要使用这两个函数。
所有系统调用都适用于用户空间数据。您看到的每个指针都已(或应该!)标记__user
。每个系统调用都会完成所有必要的工作以从用户空间访问数据。
您的问题是您正在尝试向inputFile
系统调用提供内核空间数据。它不会起作用,因为系统调用总是试图穿过屏障,但在屏障inputFile
的同一侧!
真的没有理智的方法可以复制inputFile
到障碍的另一边。我的意思是,当然有办法做到这一点,甚至没有那么困难,但它就是不理智的。
那么,让我们探索一下我上面描述的正确解决方案,以及哪个足球已经拒绝了一次。
首先,让我们看看renameat
系统调用在当前 (3.6.2) Linux 内核中的实际样子(请记住,此代码是在 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);
接下来,查找实际存在的文件。如果成功,则必须通过调用释放 dentry dput(old_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;
还会查找新文件名的条目(毕竟它可能存在)。同样,如果成功,这个 dentry 也必须在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
为零则成功),剩下的就是释放各种lookup
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(); /* Reserve PATH_MAX+1 bytes of kernel memory for one file name */
in len;
len = strncpy_from_user(result, old/newname, PATH_MAX);
if (len <= 0) {
__putname(result);
/* An error occurred, abort! */
}
if (len >= PATH_MAX) {
__putname(result);
/* path is too long, abort! */
}
/* Finally, add it to the audit context for the current process. */
audit_getname(result);
并且,在不再需要它之后,
putname(result);
所以,footy,你的问题没有简单的解决方案。没有一个函数调用可以神奇地使您的系统调用工作。您将不得不重写它,看看这些东西是如何在fs/namei.c
. 这并不难,但是你必须小心谨慎地去做——而且最重要的是接受“只是试图让这个简单的事情以最小的改变工作”的方法不适用于此。