C 在linux 3.5.4中如何从自定义系统调用调用系统调用
我正在linux中实现自己的系统调用。它正在调用其中的重命名系统调用。它使用一个用户参数(下面是代码)将代码传递给重命名 以下是基本代码:C 在linux 3.5.4中如何从自定义系统调用调用系统调用,c,linux,filesystems,kernel,system-calls,C,Linux,Filesystems,Kernel,System Calls,我正在linux中实现自己的系统调用。它正在调用其中的重命名系统调用。它使用一个用户参数(下面是代码)将代码传递给重命名 以下是基本代码: int sys_mycall(const char __user * inputFile) { // // Code to generate my the "fileName" // // old_fs = get_fs(); set_fs(KERNEL_DS); ans = sys_renameat(AT_FDCWD, fileName
int sys_mycall(const char __user * inputFile) {
//
// Code to generate my the "fileName"
//
//
old_fs = get_fs();
set_fs(KERNEL_DS);
ans = sys_renameat(AT_FDCWD, fileName, AT_FDCWD, inputFile);
set_fs(old_fs);
return ans;
}
我有两个疑问
old_fs=get_fs()代码>,设置fs(内核DS)代码>和<代码>设置文件(旧文件)
绕过对sys\u rename
的实际调用,因为出现错误。我从这个问题中得到了答案:。。。这是一个正确的工作方法吗
int sys_myfunc(const char __user * inputFileUser) {
char inputFile[255];
int l = 0;
while(inputFileUser[l] != '\0') l++;
if(l==0)
return -10;
if(copy_from_user(inputFile,inputFileUser,l+1)< 0 ) return -20;
//
//GENERATE fileName here
//
//
char fileName[255];
return sys_renameat(AT_FDCWD, inputFile, AT_FDCWD, fileName);
}
int sys\u myfunc(const char\u user*inputFileUser){
字符输入文件[255];
int l=0;
while(inputFileUser[l]!='\0')l++;
如果(l==0)
返回-10;
if(从用户复制用户(inputFile,inputFileUser,l+1)<0)返回-20;
//
//在此处生成文件名
//
//
字符文件名[255];
返回sys_renameat(AT_FDCWD,inputFile,AT_FDCWD,fileName);
}
下面仍然返回-1。为什么?我将数据复制到内核空间。Hm
linux-3.6.2/fs/namei.c
包含许多类似的情况。例如,rename
syscall实际上定义为
SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newname)
{
return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname);
}
换句话说,从另一个系统调用调用一个系统调用没有问题。问题是指针参数是用户空间指针,而您正试图提供内核指针:您的文件名应该在用户空间中分配,但您的在内核空间中
正确的解决方案是从两个函数(您的函数和fs/namei.c
中的sys\u renameat()
)中分离出公共代码,然后从两个系统调用中调用该函数。假设您不想将其包含在上游——如果是的话,那么这是重构和重新思考的时间——您可以轻松地将sys\u renameat
的内容复制到您自己的函数中;没那么大。熟悉像这样的文件系统操作所需的检查和锁定也是一个有用的要点
为解释问题和解决方案而编辑:
在非常真实的意义上,由正常进程分配的内存(用户空间内存)和由内核分配的内存(内核空间)被内核用户空间屏障完全分开
你的代码忽略了这个障碍,根本不应该工作。(它可能在x86上工作,因为内核用户空间的障碍很容易从该体系结构的内核端突破。)您还可以使用256字节的堆栈作为文件名,这是一个禁忌:内核堆栈是一个非常有限的资源,应该少用
普通进程(用户空间进程)无法访问任何内核内存。你可以试试,它不会起作用的。这就是障碍存在的原因。(有些嵌入式系统的硬件根本不支持这样的障碍,但在本次讨论中,让我们忽略这些障碍。请记住,即使在x86上,障碍很容易从内核端突破,但并不意味着它不存在。不要像个傻瓜一样假设,因为它似乎对您有效,它确实存在不知怎么说是对的。)
屏障的本质是,在大多数体系结构上,内核也存在屏障
为了帮助内核程序员,指向用户空间的指针被标记为\uu user
。这意味着您不能仅仅取消对它们的引用并期望它们工作;您需要使用copy\u from\u user()
和copy\u to\u user()
。这不仅仅是系统调用参数:当您从内核访问用户空间数据时,您需要使用这两个函数
所有系统调用都处理用户空间数据。您看到的每个指针都(或应该!)标记为\uu user
。每个系统调用都执行从用户空间访问数据的所有必要工作
您的问题是试图向系统调用提供内核空间数据,inputFile
。它将不起作用,因为系统调用总是试图通过屏障到达,但是inputFile
位于屏障的同一侧
要将inputFile
复制到屏障的另一边,确实没有明智的方法。我的意思是,当然有办法做到这一点,这甚至没有那么困难,但这并不明智
因此,让我们探讨一下我上面描述的正确解决方案,footy已经拒绝过一次了
首先,让我们看看当前(3.6.2)Linux内核中的renameat
syscall实际上是什么样子的(请记住,此代码是在GPLv2下授权的)。rename
syscall只需使用sys\u rename调用它(AT\u FDCWD,oldname,AT\u 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\u put(&oldnd.path)释放;putname(来自)代码>
注意:在此之后,新目录和路径必须在使用后通过调用path\u 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\u drop\u 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;
其次,这两个问题是可怕的
oldnd.flags &= ~LOOKUP_PARENT;
newnd.flags &= ~LOOKUP_PARENT;
newnd.flags |= LOOKUP_RENAME_TARGET;
trap = lock_rename(new_dir, old_dir);
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;
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;
error = security_path_rename(&oldnd.path, old_dentry,
&newnd.path, new_dentry);
if (error)
goto exit5;
error = vfs_rename(old_dir->d_inode, old_dentry,
new_dir->d_inode, new_dentry);
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;
}
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);
SYSCALL_DEFINE1(myfunc, const char __user *, oldname)
{
struct dentry *old_dir, *new_dir;
struct dentry *old_dentry, *new_dentry;
struct dentry *trap;
struct nameidata oldnd, newnd;
char *from;
char *to = __getname();
int error;
from = getname(oldname);
if (IS_ERR(from)) {
error = PTR_ERR(from);
goto exit;
}
error = do_path_lookup(AT_FDCWD, from, LOOKUP_PARENT, &oldnd);
if (error)
goto exit0;
error = do_path_lookup(AT_FDCWD, to, LOOKUP_PARENT, &newnd);
if (error)
goto exit1;
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;
error = mnt_want_write(oldnd.path.mnt);
if (error)
goto exit2;
oldnd.flags &= ~LOOKUP_PARENT;
newnd.flags &= ~LOOKUP_PARENT;
newnd.flags |= LOOKUP_RENAME_TARGET;
trap = lock_rename(new_dir, old_dir);
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;
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;
error = security_path_rename(&oldnd.path, old_dentry,
&newnd.path, new_dentry);
if (error)
goto exit5;
error = vfs_rename(old_dir->d_inode, old_dentry,
new_dir->d_inode, new_dentry);
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);
exit1:
path_put(&oldnd.path);
exit0:
putname(from);
exit:
__putname(to);
return error;
}