1. 引言
当我们在终端输入mkdir newdir时,一个崭新的目录就在文件系统中诞生了。这个看似简单的操作,背后却是操作系统内核中VFS(虚拟文件系统)、具体文件系统(如ext4)、日志系统、缓存管理等多个模块协作的结果。本文将基于Linux内核6.8.12版本的源码,逐层剖析mkdir系统调用的实现细节。为了让读者更容易理解,我们会在关键代码行添加中文注释,解释每一段代码的意图和作用。
全文将沿着如下路径展开:
-
用户态系统调用入口
-
核心函数
do_mkdirat:路径解析与准备 -
VFS通用创建函数
vfs_mkdir:权限与一致性检查 -
ext4文件系统的
ext4_mkdir:inode分配、日志事务、目录初始化 -
父目录中添加目录项:
ext4_add_entry与add_dirent_to_buf -
错误处理和重试机制
通过本文,你将看到内核如何保证即使在系统崩溃、磁盘空间紧张等异常情况下,mkdir操作依然能做到"要么全部成功,要么全部失败"(原子性)。
2. 系统调用入口:sys_mkdir
mkdir系统调用在内核中定义为SYSCALL_DEFINE2(mkdir, ...)。这是内核用来定义系统调用的标准宏,数字2表示有两个参数。
c
// 系统调用: mkdir(const char *pathname, umode_t mode)
SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)
{
// 使用 AT_FDCWD 表示相对路径基于当前工作目录
// getname() 将用户空间路径字符串安全拷贝到内核空间
return do_mkdirat(AT_FDCWD, getname(pathname), mode);
}
中文注释:
-
SYSCALL_DEFINE2:定义一个系统调用函数,名称mkdir,两个参数。 -
const char __user *pathname:用户空间传来的路径指针,__user标记表示不能直接解引用。 -
umode_t mode:新建目录的权限模式(如0755),但实际会被当前进程的umask过滤。 -
getname(pathname):复制用户路径到内核,返回struct filename *,该结构体内含引用计数和路径字符串。 -
AT_FDCWD:是一个特殊的文件描述符值(-100),表示"当前工作目录",用于相对路径解析。
3. 核心处理:do_mkdirat
do_mkdirat是VFS层中处理目录创建的核心函数,它不依赖于特定文件系统。
c
int do_mkdirat(int dfd, struct filename *name, umode_t mode)
{
struct dentry *dentry;
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_DIRECTORY; // 要求路径最后分量不能是文件
retry:
// 文件名创建: 查找父目录,并创建最后分量的dentry(但尚未关联inode)
dentry = filename_create(dfd, name, &path, lookup_flags);
error = PTR_ERR(dentry); // 如果返回错误码,转换
if (IS_ERR(dentry)) // 检查dentry是否有效指针
goto out_putname;
// 安全模块检查(SELinux等),同时使用mode_strip_umask过滤umask
error = security_path_mkdir(&path, dentry,
mode_strip_umask(path.dentry->d_inode, mode));
if (!error) {
// 调用VFS创建函数,传入id映射(用于用户命名空间)
error = vfs_mkdir(mnt_idmap(path.mnt), path.dentry->d_inode,
dentry, mode);
}
// 释放路径和dentry的引用,并解锁父目录
done_path_create(&path, dentry);
// 如果错误是 ESTALE(陈旧句柄,常见于NFS),则重试并强制重新验证
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out_putname:
putname(name); // 释放内核路径名结构
return error;
}
中文注释:
-
LOOKUP_DIRECTORY标志:要求路径最终分量必须是目录,如果已存在且为普通文件则会报错。 -
filename_create():这是关键函数,它完成路径查找,返回父目录的path和新目录的dentry(该dentry处于未链接状态)。 -
mode_strip_umask():根据父目录的默认ACL和当前进程的umask,调整用户传入的mode,得到最终权限。 -
retry_estale():检测-ESTALE错误并决定是否需要重试(网络文件系统常用)。 -
done_path_create():释放filename_create中获取的引用,防止内存泄漏。
4. VFS通用创建:vfs_mkdir
vfs_mkdir负责所有文件系统通用的检查,然后调用具体文件系统的mkdir回调。
c
/**
* vfs_mkdir - 创建目录的统一入口
* @idmap: id映射(用于用户命名空间)
* @dir: 父目录的inode
* @dentry: 新目录的dentry(尚未关联inode)
* @mode: 最终权限模式(已去除umask)
*/
int vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
int error;
unsigned max_links = dir->i_sb->s_max_links; // 文件系统允许的最大链接数
// 检查是否可以在父目录中创建条目(写权限、不可变属性、配额等)
error = may_create(idmap, dir, dentry);
if (error)
return error;
// 父目录的inode操作表必须实现mkdir回调
if (!dir->i_op->mkdir)
return -EPERM;
// 最后调整模式(保留常规权限位和粘滞位)
mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0);
// 第二个安全钩子:基于inode和dentry的检查
error = security_inode_mkdir(dir, dentry, mode);
if (error)
return error;
// 如果父目录的链接数达到上限(例如65000),不能增加子目录
if (max_links && dir->i_nlink >= max_links)
return -EMLINK;
// 调用具体文件系统的mkdir函数(ext4_mkdir, xfs_mkdir等)
error = dir->i_op->mkdir(idmap, dir, dentry, mode);
if (!error)
fsnotify_mkdir(dir, dentry); // 通知inotify/fanotify目录创建事件
return error;
}
中文注释:
-
max_links:文件系统限制,避免目录嵌套过深导致链接数溢出。ext4中最大为65000。 -
may_create():检查父目录是否可写,以及文件是否具有不可变属性(S_IMMUTABLE)。 -
vfs_prepare_mode():根据父目录的idmap和当前进程的fs->umask,计算最终mode。第三个参数是允许的位掩码。 -
security_inode_mkdir():LSM框架的第二次检查,某些模块需要在此进行更细粒度的控制。 -
fsnotify_mkdir():文件系统通知机制,用于监听目录变化。
5. ext4文件系统中的mkdir:ext4_mkdir
ext4是Linux上最广泛使用的日志文件系统。它的目录inode操作表中包含了.mkdir = ext4_mkdir。
c
// ext4目录的inode操作函数表
const struct inode_operations ext4_dir_inode_operations = {
.create = ext4_create,
.lookup = ext4_lookup,
.link = ext4_link,
.unlink = ext4_unlink,
.symlink = ext4_symlink,
.mkdir = ext4_mkdir, // 就是下面这个函数
.rmdir = ext4_rmdir,
.mknod = ext4_mknod,
// ... 其他操作
};
下面是ext4_mkdir的完整带注释实现:
c
static int ext4_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
handle_t *handle; // 日志事务句柄
struct inode *inode; // 新分配的inode
int err, err2 = 0, credits, retries = 0;
// 检查父目录链接数是否达到ext4上限
if (EXT4_DIR_LINK_MAX(dir))
return -EMLINK;
// 初始化磁盘配额(如果启用了quota)
err = dquot_initialize(dir);
if (err)
return err;
// 计算这次操作需要预留多少日志块(元数据修改块数)
credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) +
EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3);
retry:
// 分配新的inode并启动日志事务
// 参数:idmap, 父目录, 文件类型S_IFDIR|mode, 文件名, ...
inode = ext4_new_inode_start_handle(idmap, dir, S_IFDIR | mode,
&dentry->d_name,
0, NULL, EXT4_HT_DIR, credits);
handle = ext4_journal_current_handle(); // 获取当前事务的handle
err = PTR_ERR(inode);
if (IS_ERR(inode))
goto out_stop;
// 设置新目录的inode操作函数表(目录专用)
inode->i_op = &ext4_dir_inode_operations;
inode->i_fop = &ext4_dir_operations; // 目录的file_operations
// 初始化新目录的内容:创建 '.' 和 '..' 条目
err = ext4_init_new_dir(handle, dir, inode);
if (err)
goto out_clear_inode;
// 将新目录的inode标记为脏,等待写入磁盘
err = ext4_mark_inode_dirty(handle, inode);
if (!err)
// 在父目录中添加目录项(文件名 -> 新inode的映射)
err = ext4_add_entry(handle, dentry, inode);
if (err) {
out_clear_inode:
// 出错处理:清除链接数,将inode加入孤儿列表(日志会跟踪)
clear_nlink(inode);
ext4_orphan_add(handle, inode);
unlock_new_inode(inode); // 释放新inode的锁
err2 = ext4_mark_inode_dirty(handle, inode);
if (unlikely(err2))
err = err2;
ext4_journal_stop(handle); // 停止事务
iput(inode); // 释放inode引用(由于nlink=0会被删除)
goto out_retry;
}
// 成功:父目录链接数加1(因为新目录有 '..' 指向它)
ext4_inc_count(dir);
// 如果目录项数量达到阈值,可能需要启用dx(索引)特性
ext4_update_dx_flag(dir);
err = ext4_mark_inode_dirty(handle, dir);
if (err)
goto out_clear_inode;
// 将dentry与inode关联,并标记dentry为有效(放入dcache)
d_instantiate_new(dentry, inode);
ext4_fc_track_create(handle, dentry); // 记录fast-commit日志(如果启用)
// 如果父目录要求同步(O_SYNC或dirsync挂载选项),则立即提交事务
if (IS_DIRSYNC(dir))
ext4_handle_sync(handle);
out_stop:
if (handle)
ext4_journal_stop(handle); // 停止事务(可能触发提交)
out_retry:
// 如果错误是ENOSPC(空间不足)且文件系统允许重试,则重新尝试
if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries))
goto retry;
return err;
}
中文注释重点:
-
ext4_new_inode_start_handle():这是最关键的步骤,它会在磁盘上分配一个空闲inode,填充基本属性(uid、gid、时间、模式等),并同时启动一个日志事务,返回handle。 -
ext4_init_new_dir():新目录必须包含.和..两个特殊目录项,该函数负责分配目录的第一个数据块并写入这两项。 -
ext4_add_entry():将dentry的名称和新inode的编号写入父目录的目录文件中。 -
错误处理中的
ext4_orphan_add():如果创建过程失败,将未完成的inode添加到孤儿列表,日志回放时会自动删除它,防止磁盘inode泄露。 -
d_instantiate_new():建立VFS层面dentry与inode的关联,之后lookup就能找到该目录。
6. 在父目录中添加目录项:ext4_add_entry
父目录本质上是一个特殊文件,内容由ext4_dir_entry_2结构组成。ext4_add_entry负责将新目录的条目插入到这个文件中。
c
static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
struct inode *inode)
{
struct inode *dir = d_inode(dentry->d_parent); // 父目录的inode
struct buffer_head *bh = NULL;
struct ext4_dir_entry_2 *de;
struct super_block *sb;
struct ext4_filename fname;
int retval;
int dx_fallback = 0;
unsigned blocksize;
ext4_lblk_t block, blocks;
int csum_size = 0;
// 如果启用了元数据校验和,目录块尾部会保留校验和空间
if (ext4_has_metadata_csum(inode->i_sb))
csum_size = sizeof(struct ext4_dir_entry_tail);
sb = dir->i_sb;
blocksize = sb->s_blocksize; // 通常为4096字节
// 如果文件名被加密但未提供密钥,无法操作
if (fscrypt_is_nokey_name(dentry))
return -ENOKEY;
#if IS_ENABLED(CONFIG_UNICODE)
// 如果文件系统支持Unicode且目录启用了大小写折叠,验证文件名是否为有效UTF-8
if (sb_has_strict_encoding(sb) && IS_CASEFOLDED(dir) &&
utf8_validate(sb->s_encoding, &dentry->d_name))
return -EINVAL;
#endif
// 根据目录的加密/折叠设置,预处理文件名(如转换为规范形式)
retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname);
if (retval)
return retval;
// 首先尝试内联目录(目录数据存储在inode内,适用于小目录)
if (ext4_has_inline_data(dir)) {
retval = ext4_try_add_inline_entry(handle, &fname, dir, inode);
if (retval < 0)
goto out;
if (retval == 1) { // 成功添加到内联数据
retval = 0;
goto out;
}
// retval == 0 表示内联区已满,需要转换为普通块目录
}
// 如果目录启用了索引(htree/dx),尝试通过索引添加
if (is_dx(dir)) {
retval = ext4_dx_add_entry(handle, &fname, dir, inode);
if (!retval || (retval != ERR_BAD_DX_DIR))
goto out;
// 如果索引目录损坏,回退到线性模式
if (ext4_has_metadata_csum(sb)) {
EXT4_ERROR_INODE(dir, "Directory has corrupted htree index.");
retval = -EFSCORRUPTED;
goto out;
}
ext4_clear_inode_flag(dir, EXT4_INODE_INDEX); // 清除索引标志
dx_fallback++;
retval = ext4_mark_inode_dirty(handle, dir);
if (unlikely(retval))
goto out;
}
// 线性扫描现有目录块(非索引模式)
blocks = dir->i_size >> sb->s_blocksize_bits; // 目录文件包含的块数
for (block = 0; block < blocks; block++) {
bh = ext4_read_dirblock(dir, block, DIRENT);
if (bh == NULL) {
// 如果块不存在(文件空洞),创建新块并跳转到添加新块流程
bh = ext4_bread(handle, dir, block,
EXT4_GET_BLOCKS_CREATE);
goto add_to_new_block;
}
if (IS_ERR(bh)) {
retval = PTR_ERR(bh);
bh = NULL;
goto out;
}
// 尝试将目录项添加到当前缓冲区
retval = add_dirent_to_buf(handle, &fname, dir, inode, NULL, bh);
if (retval != -ENOSPC) // 不是空间不足的错误就退出
goto out;
// 如果目录只有一个块且尚未使用索引,但文件系统支持索引,则转换为索引目录
if (blocks == 1 && !dx_fallback &&
ext4_has_feature_dir_index(sb)) {
retval = make_indexed_dir(handle, &fname, dir,
inode, bh);
bh = NULL; // make_indexed_dir 已经释放了bh
goto out;
}
brelse(bh); // 释放当前块,继续下一个块
}
// 所有现有块都没有空间,在目录文件末尾追加一个新块
bh = ext4_append(handle, dir, &block);
add_to_new_block:
if (IS_ERR(bh)) {
retval = PTR_ERR(bh);
bh = NULL;
goto out;
}
de = (struct ext4_dir_entry_2 *) bh->b_data;
de->inode = 0; // 初始化为空目录项
// 设置rec_len为整个块减去校验和尾巴的长度
de->rec_len = ext4_rec_len_to_disk(blocksize - csum_size, blocksize);
if (csum_size)
ext4_initialize_dirent_tail(bh, blocksize); // 初始化尾部校验和结构
// 最后调用通用的添加函数,将条目写入新块
retval = add_dirent_to_buf(handle, &fname, dir, inode, de, bh);
out:
ext4_fname_free_filename(&fname);
brelse(bh);
if (retval == 0)
ext4_set_inode_state(inode, EXT4_STATE_NEWENTRY);
return retval;
}
中文注释:
-
ext4_filename:封装了经过加密/折叠处理的文件名,以及其哈希值等。 -
ext4_try_add_inline_entry():尝试将目录项添加到inode的内联数据区(如果目录很小)。 -
is_dx(dir):判断目录是否使用了索引树(提升大目录查找性能)。 -
make_indexed_dir():将单块线性目录转换为索引目录(htree),这是ext4的优化特性。 -
ext4_append():在目录文件末尾新增一个块,并返回映射到该块的buffer_head。
7. 实际写入目录块:add_dirent_to_buf
这个函数负责在已经确定有空闲空间的缓冲区中,实际修改目录块数据,插入新的目录项。
c
static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
struct inode *dir, struct inode *inode,
struct ext4_dir_entry_2 *de,
struct buffer_head *bh)
{
unsigned int blocksize = dir->i_sb->s_blocksize;
int csum_size = 0;
int err, err2;
if (ext4_has_metadata_csum(inode->i_sb))
csum_size = sizeof(struct ext4_dir_entry_tail);
// 如果调用方没有预先指定de(空闲位置),就在当前缓冲区内查找合适位置
if (!de) {
err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
blocksize - csum_size, fname, &de);
if (err)
return err;
}
// 告诉日志系统我们要修改这个缓冲区,需要将其加入事务
BUFFER_TRACE(bh, "get_write_access");
err = ext4_journal_get_write_access(handle, dir->i_sb, bh,
EXT4_JTR_NONE);
if (err) {
ext4_std_error(dir->i_sb, err);
return err;
}
/* 现在缓冲区已经安全地加入事务,可以修改内容 */
// 插入目录项:调整相邻项的rec_len,在de处填充新的条目
ext4_insert_dentry(dir, inode, de, blocksize, fname);
// 更新父目录的时间戳(修改时间和变更时间)
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
// 如果目录需要启用索引标志(比如大小增长到阈值),更新之
ext4_update_dx_flag(dir);
inode_inc_iversion(dir); // 增加inode版本号
err2 = ext4_mark_inode_dirty(handle, dir); // 标记父目录的inode为脏
// 将修改后的目录块标记为脏,并记录到事务中
BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_dirblock(handle, dir, bh);
if (err)
ext4_std_error(dir->i_sb, err);
// 返回错误:如果err或err2有错,优先返回err(目录块错误)
return err ? err : err2;
}
中文注释:
-
ext4_find_dest_de():扫描一个目录块(从bh->b_data开始,长度blocksize - csum_size),寻找一块能够容纳新目录项的空闲区域。它会考虑现有目录项的空隙,以及尾部可能存在的未使用空间。 -
ext4_journal_get_write_access():对于ext3/4日志,修改缓冲区前必须调用此函数,它会将缓冲区内容复制到日志的副本中(写前拷贝),以便事务回滚时恢复。 -
ext4_insert_dentry():核心内存操作:根据fname的长度(可能经过加密后变长)计算需要占用的空间,调整前一个或后一个目录项的rec_len,然后在de位置写入新目录项的inode号、文件名长度、文件类型、校验等。这个函数还会更新目录块的校验和(如果启用)。 -
ext4_handle_dirty_dirblock():将脏目录块加入日志事务的待写列表,等待事务提交时写入磁盘。
8. 错误处理与重试机制
整个mkdir调用链中,内核设计了多层错误处理和重试逻辑,确保高可靠性。
8.1 VFS层的-ESTALE重试
在do_mkdirat中:
c
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
-ESTALE通常发生在网络文件系统(NFS)中,表示客户端缓存的目录项已经过期。此时内核会重新验证路径,再次尝试。
8.2 ext4层的-ENOSPC重试
在ext4_mkdir的末尾:
c
if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries))
goto retry;
ext4_should_retry_alloc()会检查是否有其他进程正在释放磁盘空间,或者是否正在进行在线碎片整理。如果有可能获得空间,就返回true,重新尝试整个创建过程。retries参数限制了最大重试次数(通常内部实现为最多尝试几次后放弃)。
8.3 孤儿inode清理
如果在创建过程中ext4_add_entry失败,代码会跳转到out_clear_inode:
c
clear_nlink(inode);
ext4_orphan_add(handle, inode);
unlock_new_inode(inode);
...
iput(inode);
-
clear_nlink()将inode的链接数设为零,这样iput()时就会触发删除。 -
ext4_orphan_add()将该inode添加到"孤儿列表",该列表记录在日志中。如果系统在此时崩溃,日志回放时会看到孤儿inode并自动将其删除,避免inode泄漏。
8.4 日志回滚
如果事务中的任何一步失败,ext4_journal_stop()会终止当前事务。由于我们之前调用了ext4_journal_get_write_access(),日志系统拥有所有修改过的数据块的旧副本,因此事务回滚时能够恢复所有元数据到操作前的状态。这保证了mkdir的原子性。
9. 完整流程总结
下图(文字描述)展示了mkdir的完整调用链:
text
用户态: mkdir("test", 0755)
|
v
系统调用: sys_mkdir -> do_mkdirat(AT_FDCWD, name, mode)
|
v
VFS路径解析: filename_create() 获取父目录path和新目录dentry
|
v
安全检查: security_path_mkdir()
|
v
VFS通用操作: vfs_mkdir()
| - may_create() 权限检查
| - dir->i_op->mkdir 回调
v
ext4具体操作: ext4_mkdir()
| - 计算日志credits
| - ext4_new_inode_start_handle() 分配inode并启动事务
| - ext4_init_new_dir() 初始化新目录(.和..)
| - ext4_add_entry() 在父目录中添加条目
| | - 处理内联/索引/线性模式
| | - add_dirent_to_buf() 实际写入目录块
| - d_instantiate_new() 关联dentry和inode
| - ext4_journal_stop() 结束事务(可能提交)
v
返回用户态
整个过程中,日志机制保证了元数据更新的原子性,而重试机制提高了在资源竞争下的成功率。最终,一个新目录在磁盘上被创建,并在VFS的dcache中留下缓存,供后续访问使用。
10. 延伸讨论与思考
通过阅读内核源码,我们可以获得一些深入的洞察:
-
目录是一种特殊的文件 :在ext4中,目录文件的内容是由
ext4_dir_entry_2组成的记录列表。ls命令就是通过读取目录文件的内容来获取子项的名称和inode号,然后再根据inode去获取更多属性(如类型、大小)。 -
性能优化的双刃剑:ext4引入了目录索引(htree),使得大目录的查找复杂度从O(n)降为O(log n)。但索引本身也需要维护,增加了创建条目时的开销。为此,内核只在目录大小超过一定阈值时才自动建立索引。
-
日志的写前拷贝 :
ext4_journal_get_write_access()并不立即复制整个块,而是采用"预留空间+延迟复制"的策略,只有在真正修改前才会进行拷贝,这减少了日志带宽消耗。 -
umask的时机 :用户传递的mode会在
mode_strip_umask和vfs_prepare_mode中被两次调整,最终影响到磁盘上inode的i_mode。为什么不在用户态就调整好?因为文件系统可能位于挂载了idmap的命名空间中,需要根据映射后的用户ID和组ID重新计算umask。 -
dentry与inode的生命周期 :
d_instantiate_new()将dentry与inode绑定,这样后续的路径查找就能通过dcache直接命中。如果创建失败,这个dentry会被标记为负(negative),不会与inode关联。 -
安全性 :Linux安全模块(LSM)在
security_path_mkdir和security_inode_mkdir两个钩子处拦截,分别提供路径和inode粒度的访问控制。这种双重检查允许灵活的策略(例如,某些LSM可能需要在知道最终路径名后才能决策)。
11. 结语
本文基于Linux内核6.8.12源码,逐行分析了mkdir系统调用的完整实现路径。我们从用户态入口出发,穿越VFS抽象层,深入到ext4的磁盘数据结构修改,最后又回到错误处理和事务提交。通过添加详细的中文注释,希望能帮助读者更容易地理解内核代码的意图。
学习内核源码需要耐心和细致,但每一次深入都会带来莫大的收获。无论是调试文件系统问题,还是开发新的内核特性,掌握这些基础实现都将是你最坚实的后盾。
#源码
cpp
83 common mkdir sys_mkdir
SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)
{
return do_mkdirat(AT_FDCWD, getname(pathname), mode);
}
int do_mkdirat(int dfd, struct filename *name, umode_t mode)
{
struct dentry *dentry;
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_DIRECTORY;
retry:
dentry = filename_create(dfd, name, &path, lookup_flags);
error = PTR_ERR(dentry);
if (IS_ERR(dentry))
goto out_putname;
error = security_path_mkdir(&path, dentry,
mode_strip_umask(path.dentry->d_inode, mode));
if (!error) {
error = vfs_mkdir(mnt_idmap(path.mnt), path.dentry->d_inode,
dentry, mode);
}
done_path_create(&path, dentry);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out_putname:
putname(name);
return error;
}
/**
* vfs_mkdir - create directory
* @idmap: idmap of the mount the inode was found from
* @dir: inode of @dentry
* @dentry: pointer to dentry of the base directory
* @mode: mode of the new directory
*
* Create a directory.
*
* If the inode has been found through an idmapped mount the idmap of
* the vfsmount must be passed through @idmap. This function will then take
* care to map the inode according to @idmap before checking permissions.
* On non-idmapped mounts or if permission checking is to be performed on the
* raw inode simply pass @nop_mnt_idmap.
*/
int vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
int error;
unsigned max_links = dir->i_sb->s_max_links;
error = may_create(idmap, dir, dentry);
if (error)
return error;
if (!dir->i_op->mkdir)
return -EPERM;
mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0);
error = security_inode_mkdir(dir, dentry, mode);
if (error)
return error;
if (max_links && dir->i_nlink >= max_links)
return -EMLINK;
error = dir->i_op->mkdir(idmap, dir, dentry, mode);
if (!error)
fsnotify_mkdir(dir, dentry);
return error;
}
EXPORT_SYMBOL(vfs_mkdir);
/*
* directories can handle most operations...
*/
const struct inode_operations ext4_dir_inode_operations = {
.create = ext4_create,
.lookup = ext4_lookup,
.link = ext4_link,
.unlink = ext4_unlink,
.symlink = ext4_symlink,
.mkdir = ext4_mkdir,
.rmdir = ext4_rmdir,
.mknod = ext4_mknod,
.tmpfile = ext4_tmpfile,
.rename = ext4_rename2,
.setattr = ext4_setattr,
.getattr = ext4_getattr,
.listxattr = ext4_listxattr,
.get_inode_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
.fiemap = ext4_fiemap,
.fileattr_get = ext4_fileattr_get,
.fileattr_set = ext4_fileattr_set,
};
static int ext4_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
handle_t *handle;
struct inode *inode;
int err, err2 = 0, credits, retries = 0;
if (EXT4_DIR_LINK_MAX(dir))
return -EMLINK;
err = dquot_initialize(dir);
if (err)
return err;
credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) +
EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3);
retry:
inode = ext4_new_inode_start_handle(idmap, dir, S_IFDIR | mode,
&dentry->d_name,
0, NULL, EXT4_HT_DIR, credits);
handle = ext4_journal_current_handle();
err = PTR_ERR(inode);
if (IS_ERR(inode))
goto out_stop;
inode->i_op = &ext4_dir_inode_operations;
inode->i_fop = &ext4_dir_operations;
err = ext4_init_new_dir(handle, dir, inode);
if (err)
goto out_clear_inode;
err = ext4_mark_inode_dirty(handle, inode);
if (!err)
err = ext4_add_entry(handle, dentry, inode);
if (err) {
out_clear_inode:
clear_nlink(inode);
ext4_orphan_add(handle, inode);
unlock_new_inode(inode);
err2 = ext4_mark_inode_dirty(handle, inode);
if (unlikely(err2))
err = err2;
ext4_journal_stop(handle);
iput(inode);
goto out_retry;
}
ext4_inc_count(dir);
ext4_update_dx_flag(dir);
err = ext4_mark_inode_dirty(handle, dir);
if (err)
goto out_clear_inode;
d_instantiate_new(dentry, inode);
ext4_fc_track_create(handle, dentry);
if (IS_DIRSYNC(dir))
ext4_handle_sync(handle);
out_stop:
if (handle)
ext4_journal_stop(handle);
out_retry:
if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries))
goto retry;
return err;
}
/*
* ext4_add_entry()
*
* adds a file entry to the specified directory, using the same
* semantics as ext4_find_entry(). It returns NULL if it failed.
*
* NOTE!! The inode part of 'de' is left at 0 - which means you
* may not sleep between calling this and putting something into
* the entry, as someone else might have used it while you slept.
*/
static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
struct inode *inode)
{
struct inode *dir = d_inode(dentry->d_parent);
struct buffer_head *bh = NULL;
struct ext4_dir_entry_2 *de;
struct super_block *sb;
struct ext4_filename fname;
int retval;
int dx_fallback=0;
unsigned blocksize;
ext4_lblk_t block, blocks;
int csum_size = 0;
if (ext4_has_metadata_csum(inode->i_sb))
csum_size = sizeof(struct ext4_dir_entry_tail);
sb = dir->i_sb;
blocksize = sb->s_blocksize;
if (fscrypt_is_nokey_name(dentry))
return -ENOKEY;
#if IS_ENABLED(CONFIG_UNICODE)
if (sb_has_strict_encoding(sb) && IS_CASEFOLDED(dir) &&
utf8_validate(sb->s_encoding, &dentry->d_name))
return -EINVAL;
#endif
retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname);
if (retval)
return retval;
if (ext4_has_inline_data(dir)) {
retval = ext4_try_add_inline_entry(handle, &fname, dir, inode);
if (retval < 0)
goto out;
if (retval == 1) {
retval = 0;
goto out;
}
}
if (is_dx(dir)) {
retval = ext4_dx_add_entry(handle, &fname, dir, inode);
if (!retval || (retval != ERR_BAD_DX_DIR))
goto out;
/* Can we just ignore htree data? */
if (ext4_has_metadata_csum(sb)) {
EXT4_ERROR_INODE(dir,
"Directory has corrupted htree index.");
retval = -EFSCORRUPTED;
goto out;
}
ext4_clear_inode_flag(dir, EXT4_INODE_INDEX);
dx_fallback++;
retval = ext4_mark_inode_dirty(handle, dir);
if (unlikely(retval))
goto out;
}
blocks = dir->i_size >> sb->s_blocksize_bits;
for (block = 0; block < blocks; block++) {
bh = ext4_read_dirblock(dir, block, DIRENT);
if (bh == NULL) {
bh = ext4_bread(handle, dir, block,
EXT4_GET_BLOCKS_CREATE);
goto add_to_new_block;
}
if (IS_ERR(bh)) {
retval = PTR_ERR(bh);
bh = NULL;
goto out;
}
retval = add_dirent_to_buf(handle, &fname, dir, inode,
NULL, bh);
if (retval != -ENOSPC)
goto out;
if (blocks == 1 && !dx_fallback &&
ext4_has_feature_dir_index(sb)) {
retval = make_indexed_dir(handle, &fname, dir,
inode, bh);
bh = NULL; /* make_indexed_dir releases bh */
goto out;
}
brelse(bh);
}
bh = ext4_append(handle, dir, &block);
add_to_new_block:
if (IS_ERR(bh)) {
retval = PTR_ERR(bh);
bh = NULL;
goto out;
}
de = (struct ext4_dir_entry_2 *) bh->b_data;
de->inode = 0;
de->rec_len = ext4_rec_len_to_disk(blocksize - csum_size, blocksize);
if (csum_size)
ext4_initialize_dirent_tail(bh, blocksize);
retval = add_dirent_to_buf(handle, &fname, dir, inode, de, bh);
out:
ext4_fname_free_filename(&fname);
brelse(bh);
if (retval == 0)
ext4_set_inode_state(inode, EXT4_STATE_NEWENTRY);
return retval;
}
/*
* Add a new entry into a directory (leaf) block. If de is non-NULL,
* it points to a directory entry which is guaranteed to be large
* enough for new directory entry. If de is NULL, then
* add_dirent_to_buf will attempt search the directory block for
* space. It will return -ENOSPC if no space is available, and -EIO
* and -EEXIST if directory entry already exists.
*/
static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
struct inode *dir,
struct inode *inode, struct ext4_dir_entry_2 *de,
struct buffer_head *bh)
{
unsigned int blocksize = dir->i_sb->s_blocksize;
int csum_size = 0;
int err, err2;
if (ext4_has_metadata_csum(inode->i_sb))
csum_size = sizeof(struct ext4_dir_entry_tail);
if (!de) {
err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
blocksize - csum_size, fname, &de);
if (err)
return err;
}
BUFFER_TRACE(bh, "get_write_access");
err = ext4_journal_get_write_access(handle, dir->i_sb, bh,
EXT4_JTR_NONE);
if (err) {
ext4_std_error(dir->i_sb, err);
return err;
}
/* By now the buffer is marked for journaling */
ext4_insert_dentry(dir, inode, de, blocksize, fname);
/*
* XXX shouldn't update any times until successful
* completion of syscall, but too many callers depend
* on this.
*
* XXX similarly, too many callers depend on
* ext4_new_inode() setting the times, but error
* recovery deletes the inode, so the worst that can
* happen is that the times are slightly out of date
* and/or different from the directory change time.
*/
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
ext4_update_dx_flag(dir);
inode_inc_iversion(dir);
err2 = ext4_mark_inode_dirty(handle, dir);
BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_dirblock(handle, dir, bh);
if (err)
ext4_std_error(dir->i_sb, err);
return err ? err : err2;
}