基于inode的高性能文件查寻删除(name_to_handle_at && open_by_handle_at)
场景
如果我们对某个文件在系统中建立了非常多的硬链接,而我们需要清理掉他们。
例如像是关闭基于硬链接的反勒索诱捕能力这种场景。
问题
此时大部分人可能和我最初想的一样,使用:find ./ -inum 【inode号】 -exec rm -rf {} \;
但此时如果面临的是几十上百万的文件系统呢?上面这种方式是非常浪费时间和性能的。
即使你在建立硬链接的过程中,记录了每个硬链接的存储位置,在一些目录被重命名的情况下,又会出现缓存失效的问题。
最后就会导致文件系统一地鸡毛……
解决方案
所以有没有一种性能又高,还能无视路径被重命名的方式呢?
有的有的!
此时就要用到 name_to_handle_at 和 open_by_handle_at 这对孪生兄弟了。
相较于 find 命令基于应用层文件系统 自顶向下 的遍历,这对孪生兄弟则是在内核中通过 inode 信息进行 自底向上 的遍历,速度极快。
使用这两个接口,仅需要满足三个条件:
- 一:至少要知道一个硬链接文件所在位置,用于提取句柄信息;
- 二:要知道硬链接所在分区路径,这个简单的
df命令就可以看到,况且不通的分区不允许互相搭建硬链接; - 三:需要
CAP_DAC_READ_SEARCH权限,这个用root权限即可无视;
使用说明
其中 name_to_handle_at 主要是用来获取文件句柄信息;
struct file_handle *fhp = (struct file_handle *)malloc(sizeof(struct file_handle) + 128);
fhp->handle_bytes = 128;
// 获取目标文件的底层句柄
if (name_to_handle_at(AT_FDCWD, target_path, fhp, &mount_id, 0) == -1) {
perror("name_to_handle_at 失败");
free(fhp);
exit(EXIT_FAILURE);
}
open_by_handle_at 则是利用文件句柄信息去打开文件。
// 打开挂载点文件描述符(一般传文件所在分区挂载路径,例如 /、/home/、/data……)
int mount_fd = open(mount_point, O_RDONLY | O_DIRECTORY);
if (mount_fd == -1) {
perror("打开挂载点失败");
free(fhp);
exit(EXIT_FAILURE);
}
// 通过底层句柄直接打开文件 (使用 O_PATH 轻量级打开以提高性能,不需要读写权限)
int fd = open_by_handle_at(mount_fd, fhp, O_PATH);
if (fd == -1) {
perror("open_by_handle_at 失败,文件可能已被删除或被跨分区移动");
close(mount_fd);
free(fhp);
exit(EXIT_FAILURE);
}
在文件成功打开之后,就可以用readlink去查看"/proc/self/fd/【被打开的fd】"所指向的路径了。
char fd_path[64];
char real_path[PATH_MAX];
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd);
ssize_t len = readlink(fd_path, real_path, sizeof(real_path) - 1);
if (len == -1) {
perror("获取路径失败");
close(fd); close(mount_fd); free(fhp);
exit(EXIT_FAILURE);
}
real_path[len] = '\0';
如上查寻删除后,循环往复,便达到了删除所有硬链接的目的
如果仅仅是查寻,可能需要使用先删除再搭建硬链接的方式了
因为该方法在不删除 fd 指向的目标文件的情况下,也无法获取其它硬链接路径……
浙公网安备 33010602011771号