我正在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, AT_FDCWD, inputFile);
set_fs(old_fs);
return ans;
}
我在这里有两个疑问。
old_fs = get_fs();
,set_fs(KERNEL_DS);
和set_fs(old_fs);
绕过对sys_rename
的实际调用,因为发生了错误。我从以下问题得到了答案:allocate user-space memory from kernel ...这是正确的解决方法吗? 编辑:
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);
}
以下内容仍返回-1。为什么?我将数据复制到内核空间。
最佳答案
我想确切地说明实现脚注所需的正确方法,但是最初的答案太长了,我决定将解决方案放在一个单独的答案中。我将代码分成几部分,并解释每个片段的作用。
请记住,由于我们重复使用了内核代码,因此本文中的代码和所产生的功能必须根据GPLv2许可进行许可。
首先,我们首先声明一个单参数syscall。
SYSCALL_DEFINE1(myfunc, const char __user *, oldname)
{
在内核中,堆栈空间是一种稀缺资源。您不创建本地数组;而是创建本地数组。您始终使用动态内存管理。幸运的是,有一些非常有用的功能,例如
__getname()
,因此几乎没有其他代码。重要的是要记住在完成使用后释放所有使用的内存。由于此系统调用基本上是
rename
的变体,因此我们几乎重用了所有fs/namei.c:sys_renameat()
代码。首先,局部变量声明。也有很多。就像我说的那样,堆栈在内核中很稀少,并且在任何syscall函数中您都不会看到比这更多的局部变量: 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;
sys_renameat()
的第一个更改已经在上面的char *to = __getname();
行上。它动态分配PATH_MAX+1
字节,并且在不再需要它之后必须使用__putname()
释放它。这是为文件或目录名称声明临时缓冲区的正确方法。要构建新路径(
to
),我们还需要能够直接访问旧名称(from
)。由于内核用户空间的障碍,我们不能直接访问oldname
。因此,我们为其创建内核内副本: from = getname(oldname);
if (IS_ERR(from)) {
error = PTR_ERR(from);
goto exit;
}
尽管许多C程序员都被告知
goto
是邪恶的,但这是异常(exception):错误处理。不必记住我们需要做的所有清理工作(并且我们已经至少需要做__putname(to)
了),我们将清理工作放在函数的末尾,然后跳到正确的位置,exit
是最后一个。 error
当然包含错误号。在我们的函数的这一点上,我们可以访问
from[0]
直到第一个'\0'
,或者访问(并包括)from[PATH_MAX]
,以第一个为准。它是普通的内核端数据,并且以与任何C代码相同的普通方式进行访问。您还保留了新名称
to[0]
直到包括to[PATH_MAX]
的内存。请记住要确保也使用\0
(在to[PATH_MAX] = '\0'
或更早的索引中)将其终止。在为
to
构建内容之后,我们需要进行路径查找。与renameat()
不同,我们不能使用user_path_parent()
。但是,我们可以看看user_path_parent()
做什么,并做同样的工作-当然,要适应我们自己的需求。事实证明,它仅通过错误检查调用do_path_lookup()
。因此,可以将两个user_path_parent()
调用及其错误检查替换为 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;
请注意,
exit0
是在原始renameat()
中找不到的新标签。我们需要一个新标签,因为在exit
处,我们只有to
;但是在exit0
处,我们同时拥有to
和from
。在exit0
之后,我们有to
,from
和oldnd
,依此类推。接下来,我们可以重用大部分
sys_renameat()
。它为重命名做了所有艰苦的工作。为了节省空间,我将完全忽略它的作用,因为您可以相信,如果rename()
有效,那么它也会起作用。 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);
至此,所有工作都已经完成,只剩下释放上面代码所占用的锁,内存等。如果此时一切都成功,则
error == 0
,我们将进行所有清理。如果遇到问题,error
将包含错误代码,并且我们已跳至正确的标签以对发生错误的位置进行必要的清除。如果vfs_rename()
失败-它会执行实际操作-我们将清除所有内容。但是,与原始代码相比,我们首先拥有
from
(exit
),紧随其后的是to
(exit0
),然后是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;
}
至此我们完成了。
当然,在上面我们从
sys_renameat()
复制的部分中,有很多细节需要考虑-就像我在另一个答案中所说的那样,您不仅应该复制这样的代码,还应该将通用代码重构为一个辅助函数。这使维护更加容易。幸运的是,由于我们保留了renameat()
中的所有检查-我们在复制任何renameat()
代码之前进行了路径操作-我们可以确保完成所有必要的检查。就像用户自己指定操纵路径并将其称为renameat()
一样。如果要在已经进行一些检查之后进行修改,则情况将更加复杂。您将不得不考虑这些检查是什么,您的修改如何影响它们,并且几乎总是要重新执行那些检查。
提醒任何读者,您不能仅在自己的syscall中创建文件名或任何其他字符串然后再调用另一个syscall的原因是,您刚创建的字符串位于内核用户空间边界的内核侧,而syscall期望数据驻留在另一用户空间侧。在x86上,您可能会意外地从内核侧刺穿边界,但这并不意味着您应该这样做:有
copy_from_user()
和copy_to_user()
及其派生词(例如strncpy_from_user()
),必须使用用于此目的。不必费劲地调用另一个系统调用,而是关于所提供数据的位置(内核或用户空间)的问题。
关于c - 如何在Linux 3.5.4中从自定义系统调用中调用系统调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12878228/