前言
上文(醉卧沙场:新一代mount系统调用(2)——fsopen实现)我们讲到了新一代mount类系统调用中fsopen的实现。我们还提到了挂载一个文件系统被分成的六个步骤:
- 创建一个文件系统上下文实例。
- 解析挂载的参数,把他们附在文件系统上下文实例里。这些参数是通过用户层分开传送进来的。
- 预处理这个文件系统上下文实例,验证各参数间的合理关系(这步原来单独成步,后被合并到此步中)。创建(得到)一个超级块实例,以及一个root dentry。
- 实际执行挂载操作。
- 把返回的可能的错误信息附在文件系统上下文实例里。
- 销毁文件系统上下文实例
现在我们继续围绕挂载一个普通文件系统的过程讲述第二系统调用:
int main(int argc, char *argv[])
{
int fsfd, mfd;
fsfd = fsopen("xfs", FSOPEN_CLOEXEC);
if (fsfd == -1) {
perror("fsopen");
exit(1);
}
fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "/dev/sdb1", 0);
fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
....
....
}
fsopen的主要目的就是创建一个文件系统上下文,然后把它和一个文件描述符挂钩,返回文件描述符。fsopen后面就是fsconfig,从字面意思应该可以猜到,我们上面通过fsopen创建了一个文件系统上下文,下面的fsconfig可能就是用来配置文件系统上下文里的内容的。事实上fsconfig确实主要是做这个配置工作的,除了文件系统上下文,同时它还支持其它的工作。下面我们就来具体看一下fsconfig。
fsconfig(上)
我们下面依据mainline upstream linux 5.5-rc4,看一下fsconfig的实现。因为有点长,为了方便讲述,我们将其分成三个部分来讲。第一部分如下:
SYSCALL_DEFINE5(fsconfig,
int, fd,
unsigned int, cmd,
const char __user *, _key,
const void __user *, _value,
int, aux)
{
struct fs_context *fc;
struct fd f;
int ret;
struct fs_parameter param = {
.type = fs_value_is_undefined,
};
if (fd < 0)
return -EINVAL;
switch (cmd) {
case FSCONFIG_SET_FLAG:
if (!_key || _value || aux)
return -EINVAL;
break;
case FSCONFIG_SET_STRING:
if (!_key || !_value || aux)
return -EINVAL;
break;
case FSCONFIG_SET_BINARY:
if (!_key || !_value || aux <= 0 || aux > 1024 * 1024)
return -EINVAL;
break;
case FSCONFIG_SET_PATH:
case FSCONFIG_SET_PATH_EMPTY:
if (!_key || !_value || (aux != AT_FDCWD && aux < 0))
return -EINVAL;
break;
case FSCONFIG_SET_FD:
if (!_key || _value || aux < 0)
return -EINVAL;
break;
case FSCONFIG_CMD_CREATE:
case FSCONFIG_CMD_RECONFIGURE:
if (_key || _value || aux)
return -EINVAL;
break;
default:
return -EOPNOTSUPP;
}
f = fdget(fd);
if (!f.file)
return -EBADF;
ret = -EINVAL;
if (f.file->f_op != &fscontext_fops)
goto out_f;
fc = f.file->private_data;
if (fc->ops == &legacy_fs_context_ops) {
switch (cmd) {
case FSCONFIG_SET_BINARY:
case FSCONFIG_SET_PATH:
case FSCONFIG_SET_PATH_EMPTY:
case FSCONFIG_SET_FD:
ret = -EOPNOTSUPP;
goto out_f;
}
}
...
...
这一部分其实很简单,我们主要说一下这里出现的几个重要数据结构和宏定义。首先是一个结构体变量,如下:
struct fs_parameter param = {
.type = fs_value_is_undefined,
};
这个变量将用于下面的一系列操作,我们先看一下其定义:
struct fs_parameter {
const char *key; /* Parameter name */
enum fs_value_type type:8; /* The type of value here */
union {
char *string;
void *blob;
struct filename *name;
struct file *file;
};
size_t size;
int dirfd;
};
- const char *key;
用来接受fsconfig的key参数。对于挂载一个文件系统来说,在挂载时可以指定很多参数,比如文件系统所在位置(如一个本地设备)和各种挂载选项等。它们构成很多个[key:value]的组合。而key就是索引,用来在这些诸多可配参数中指明要操作的对象,比如key="source",代表要对源对象进行设置,key="allocsize",代表要对“allocsize”这个挂载选项(如果面向的文件系统有的话)进行设置。
- enum fs_value_type type:8;
这个表示要设置的参数的类型,比如字符串,布尔变量,一个flag等等。目前可使用的参数类型如下:
enum fs_parameter_type {
__fs_param_wasnt_defined,
fs_param_is_flag,
fs_param_is_bool,
fs_param_is_u32,
fs_param_is_u32_octal,
fs_param_is_u32_hex,
fs_param_is_s32,
fs_param_is_u64,
fs_param_is_enum,
fs_param_is_string,
fs_param_is_blob,
fs_param_is_blockdev,
fs_param_is_path,
fs_param_is_fd,
nr__fs_parameter_type,
};
- union {
- char *string;
- void *blob;
- struct filename *name;
- struct file *file;
- };
这是一个联合体,它对应的是fsconfig的value参数,根据我们上面说的[key:value]组合,我们有了key,总要有一个地方指向value。而这里就是保存value用的。因为我们上面也说了参数可能有很多种type,所以这里value有多种不同的可供使用的类型(和type并不一一对应)。
- size_t size;
这个size一般用于指明上面的value的有效范围。比如value是一个string,这个size就是字符串长度。
- int dirfd;
这是一个可能被使用的域,常对应fsconfig的aux参数。aux的全称是“Additional information for the value”,也就是说是辅助value而使用的一个参数。比如这样的使用方式:
dirfd = open("/dev/", O_PATH);
fsconfig(sfd, FSCONFIG_SET_PATH, "journal", "sdd4", dirfd);
或者
fd = open("/overlays/mine/", O_PATH);
fsconfig(sfd, FSCONFIG_SET_PATH_EMPTY, "lower_dir", "", fd);
我们既然说完了fsconfig的key, value和aux参数,那还剩一个cmd参数。cmd其实就是command,代表当前要求fsconfig做什么操作指令。目前可使用的操作种类包括:
enum fsconfig_command {
FSCONFIG_SET_FLAG = 0, /* Set parameter, supplying no value */
FSCONFIG_SET_STRING = 1, /* Set parameter, supplying a string value */
FSCONFIG_SET_BINARY = 2, /* Set parameter, supplying a binary blob value */
FSCONFIG_SET_PATH = 3, /* Set parameter, supplying an object by path */
FSCONFIG_SET_PATH_EMPTY = 4, /* Set parameter, supplying an object by (empty) path */
FSCONFIG_SET_FD = 5, /* Set parameter, supplying an object by fd */
FSCONFIG_CMD_CREATE = 6, /* Invoke superblock creation */
FSCONFIG_CMD_RECONFIGURE = 7, /* Invoke superblock reconfiguration */
};
其中我们在本文前言,或者说 醉卧沙场:新一代mount系统调用(1)——接口初探 文章中使用的例子中,使用了FSCONFIG_SET_STRING和FSCONFIG_CMD_CREATE两个参数,如下:
fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "/dev/sdb1", 0);
fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
说完这几个必要数据结构和宏定义后(后面还有),我们继续看这第一部分的fsconfig实现。
在定义了一个struct fs_parameter param变量后,一大堆的switch判断是针对不同的cmd而做的参数检查,这里就不多说了。参数检查后,通过fdget(fd)得到fd所对应的文件对象。
f = fdget(fd);
其实这里这个f不是文件实例(struct file)本身,而是struct fd,它区别于整数类型的纯数字表示的fd,它是一个结构体,里面饱含了struct file。
下面这句:
if (f.file->f_op != &fscontext_fops)
goto out_f;
呼应了我们在上一篇(醉卧沙场:新一代mount系统调用(2)——fsopen实现)临近结尾时说到的用来对应文件系统上下文的文件实例,其文件方法列表不同于一般的文件,而是使用一个叫fscontext_fops的方法集。这里就是判断当前我们通过fd得到的文件实例的方法集是不是fscontext_fops。也就是说判断当前我们得到的文件实例是不是一个文件系统上下文对应的文件实例。
下面一步还是呼应我们在上一篇临近结尾时讲到的将文件实例和文件系统上下文实例关联起来的时候,是将文件系统上下文实例放到了文件实例的private_data域里。现在我们就将其取出来:
fc = f.file->private_data;
后面对于&legacy_fs_context_ops的判断可以忽略,第一部分就到这里。在第一部分里我们主要就是定义了一个struct fs_parameter param变量,以及通过文件描述符(fd)得到了文件实例(struct file),进而也得到了文件系统上下文(fs_context)。
fsconfig(中)
fsconfig的第二部分代码(单纯是因为长,所以我拆开说,实际这还是在一个fsconfig函数里)主要就是对struct fs_parameter param进行设置:
...
if (_key) {
param.key = strndup_user(_key, 256);
if (IS_ERR(param.key)) {
ret = PTR_ERR(param.key);
goto out_f;
}
}
switch (cmd) {
case FSCONFIG_SET_FLAG:
param.type = fs_value_is_flag;
break;
case FSCONFIG_SET_STRING:
param.type = fs_value_is_string;
param.string = strndup_user(_value, 256);
if (IS_ERR(param.string)) {
ret = PTR_ERR(param.string);
goto out_key;
}
param.size = strlen(param.string);
break;
case FSCONFIG_SET_BINARY:
param.type = fs_value_is_blob;
param.size = aux;
param.blob = memdup_user_nul(_value, aux);
if (IS_ERR(param.blob)) {
ret = PTR_ERR(param.blob);
goto out_key;
}
break;
case FSCONFIG_SET_PATH:
param.type = fs_value_is_filename;
param.name = getname_flags(_value, 0, NULL);
if (IS_ERR(param.name)) {
ret = PTR_ERR(param.name);
goto out_key;
}
param.dirfd = aux;
param.size = strlen(param.name->name);
break;
case FSCONFIG_SET_PATH_EMPTY:
param.type = fs_value_is_filename_empty;
param.name = getname_flags(_value, LOOKUP_EMPTY, NULL);
if (IS_ERR(param.name)) {
ret = PTR_ERR(param.name);
goto out_key;
}
param.dirfd = aux;
param.size = strlen(param.name->name);
break;
case FSCONFIG_SET_FD:
param.type = fs_value_is_file;
ret = -EBADF;
param.file = fget(aux);
if (!param.file)
goto out_key;
break;
default:
break;
}
...
...
首先处理key。这里也没多处理什么,就是如果参数给了key,就把这个字符串赋值到param.key里。
param.key = strndup_user(_key, 256);
后面就是根据fsconfig的不同cmd,结合传进来的 value和aux对param进行不同的处理,来继续设置struct fs_parameter param变量。因为我们在例子中使用的是:
fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "/dev/sdb1", 0);
我们就以此为重点说一下。
首先,在明知是SET_STRING的前提下,我们将参数的类型明确标识为是一个字符串
param.type = fs_value_is_string;
然后将value以字符串对待,赋值给param中的联合体成员的string成员。
param.string = strndup_user(_value, 256);
最后设置param.size来表示上面字符串的长度:
param.size = strlen(param.string);
因为这个用法不需要使用aux参数,所以也没有设置param.dirfd这个域。
回头看一下struct fs_parameter的定义,我们在这部分就将其全部初始化了。
结语
由于fsconfig的逻辑过长,而且新的mount API的主要改变就集中在fsconfig上,所以为了过多内容造成本篇文章阅读时的困扰,本篇暂到这里,我们将在下一篇文章继续讲解fsconfig代码的剩下部分,下一篇的篇幅将很长。到此我们还处于挂载一个文件系统的六步中的第二步中。