长话短说
尝试在我的 FUSE 文件系统上格式化块设备EPERM
在open
系统调用时失败。权限设置为777,必要的ioctl
s 被存根,但 FUSE 处理程序中不会打印任何日志。
背景
我正在编写一个程序来创建虚拟磁盘映像。我的标准之一是它必须能够以零超级用户访问权限运行,这意味着我无法安装环回设备、更改文件所有者甚至编辑/etc/fuse.conf。出于这个原因,我的方法最终变得相当冗长。具体来说,为了格式化磁盘上的各个分区,我希望能够使用系统工具,因为这为我提供了更大范围的可能文件系统。这涉及将VDisk上的各个分区作为块设备暴露给系统。然而,我发现的所有可能的方法都需要nbd
s 或环回设备。两者都需要超级用户访问权限。
自己实现FUSE
然而,在 FUSE 中实现块设备不仅是可能的,而且是受支持的。不幸的是,我无法找到关于此事的太多文档,而且由于我是在 Rust 中完成所有这些工作,因此相关文档更加稀缺。
我已经实现了以下 FUSE 方法:
init
lookup
getattr
open
read
write
readdir
ioctl
BLKGETSIZE
BLKFLSBUF
BLKSSZGET
我可以列出文件系统的内容并获取目录/文件信息。我故意忽略创建或修改资源的方法,因为这是通过构建过程完成的。
错误
如前所述,我收到权限被拒绝( EPERM
) 错误。strace
调用该mkfs
调用表明对open
块设备的调用在内核端失败。完整strace
结果。
execve("/usr/sbin/mkfs.fat", ["mkfs.fat", "out/partitions/EFI"], 0x7ffd42f64ab8 /* 76 vars */) = 0
--- snip ---
openat(AT_FDCWD, "out/partitions/EFI", O_RDWR|O_EXCL) = -1 EACCES (Permission denied)
write(2, "mkfs.fat: unable to open out/par"..., 63mkfs.fat: unable to open out/partitions/EFI: Permission denied
) = 63
exit_group(1) = ?
为了清楚起见,我的目录结构如下所示:
out
├── minimal.qcow2 [raw disk image] (shadows minimal.qcow2 [qcow2 file] with qemu-storage-daemon)
├── partitions
│ ├── EFI [Block device]
│ └── System [Block device]
└── qemu-monitor.sock [UNIX domain socket]
当然,有跟踪每个方法的日志记录功能。我在列出分区时确实看到了日志,但在格式化时却看不到日志。
正如我所提到的,我发现关于实际可能导致此错误的原因的文档很少。
进一步的见解
感谢@orenkishon 的见解,我发现了更多令我困惑的细节。
fuser
我发现了一些有趣的选项:MountOption::Dev
启用特殊字符和块设备MountOption::DefaultPermission
在内核中启用权限检查MountOption::RW
读写文件系统(显然不是默认选项)
不幸的是,这些组合都没有解决我的问题。
不会立即调用日志函数。它们似乎与某种冲洗操作有关。我可以运行该
mkfs.fat
命令,查看一两个日志,切换回 IDE,然后查看显示的一页日志。这可能是因为我生成文件的目录位于项目目录内,因此它对 IDE 是可见的,但它让我觉得非常不寻常。
函数中的日志
access
永远不可见,但在statfs
函数中是可见的,但前提是从目录mkfs
外部调用并且是所有调用中的第一个调用。out
mkfs
project > cd ./out project/out > mkfs.fat partitions/EFI mkfs.fat 4.2 (2021-01-31) mkfs.fat: unable to open partitions/EFI: Permission denied # No logs project > mkfs.fat out/partitions/EFI mkfs.fat 4.2 (2021-01-31) mkfs.fat: unable to open out/partitions/EFI: Permission denied # No logs project > cargo run ... project > mkfs.fat out/partitions mkfs.fat 4.2 (2021-01-31) mkfs.fat: unable to open out/partitions/EFI: Permission denied # Logs appear after switching to IDE
- 我今天第一次看到这条日志消息:
[2024-04-21T16:58:24Z DEBUG fuser::mnt::fuse_pure] fusermount:
[2024-04-21T16:58:24Z DEBUG fuser::mnt::fuse_pure] fusermount: fusermount3: unsafe option dev ignored
有一个MountOption::Dev
特定的,据说它增加了对块和字符设备的支持。但我似乎无法解释为什么它被拒绝。我希望我可以使用修补版本,libfuse3
但似乎没有。
可能有用的额外信息
系统规格
直接从 KDE 的系统信息复制
- 操作系统:Kubuntu 23.10
- KDE 等离子版本:5.27.8
- KDE 框架版本:5.110.0
- Qt 版本:5.15.10
- 内核版本:6.5.0-28-generic(64位)
- 图形平台:Wayland
- 处理器:32 个第 13 代智能英特尔® 酷睿™ i9-13900
- 内存:31.1 GiB RAM
- 图形处理器:AMD Radeon RX 7900 XT
- 制造商: 华硕
通用写入操作也会失败
一个建议是检查是否在不支持块设备的mkfs
情况下失败。fat32
然而,情况似乎并非如此,因为使用任何其他文件系统进行格式化都会产生相同的结果。
我还使用它mkfs
作为测试平台,因为我目前不知道有任何其他现成的系统实用程序可以直接写入块设备,并且mkfs
无论如何我都打算使用它。
坏消息 :(
在阅读手册页时,我看到了这一段,这让我的心一沉:
支持 mount 中描述的大多数通用挂载选项(ro、rw、suid、nosuid、dev、nodev、exec、noexec、atime、noatime、sync、async、dirsync)。文件系统默认使用nodev,nosuid挂载,只能由特权用户覆盖。
所以看起来这是不可能的。尽管如此,这里的任何见解——任何一线希望——都将受到高度赞赏。
如果我理解正确的话,您通过 FUSE 文件系统公开了一个块设备,出于安全原因,这不起作用。
在类 Unix 系统中,文件不仅仅是文件。让非特权用户访问任意块设备是有问题的,因为通过授予对 rootfs 设备对应的块设备的访问权限,系统可能会受到损害。
就您而言,最好将分区公开为常规文件,因为您已经在使用 FUSE 并且
mkfs
无论如何都可以很好地使用这些文件。附言。如果有其他工具确实需要分区成为块设备,则可以使用与程序类似的方法来模拟这一点
fakeroot
(即挂钩 libc 调用并模拟其结果)AFAICT,您正在尝试做的事情(或者显然是,因为您现在发现了“常规到常规”设置没问题),即创建一个代表常规文件而不是实际设备的块特殊文件,这是根本不可能的“按设计”。
确实,
EACCES
(notEPERM
) 来自真正的“高层”,即包含块文件的挂载不允许打开块文件:https://github.com/torvalds/linux/blob/v6.8/fs/namei.c#L3257 https://github.com/torvalds/linux/blob/v6.8/fs/namei.c#L3230
因为修复这个问题无论如何都不会真正帮助你实现目标(而且我对那部分也不感兴趣),所以我不会调查为什么你无法得到修复,即摆脱
nodev
, apply 。(附注:我错过了你的编辑,因为即使这个修复本身也是不可能的。)真正阻止您实现目标的是,正如名称“设备文件”或“块特殊文件”所暗示的那样,它们必须与设备关联(好吧,更准确地说,设置一些主要和次要数字) ,对于两个,因为它们在某种意义上是特殊的,所以它们不需要特定于文件系统的处理,而是需要特定于设备文件的处理,通常可以(或应该)使用,无论块文件是什么类型的文件系统居住在.
或者换句话说,你可能认为存在一个“陷阱”或“旁路”,确保至少对文件的某些操作永远不会进入文件系统实现,无论是 ext4、btrfs、fuse,都没关系。(这里我真正的意思是,通常相应的内核驱动程序会利用“陷阱”,而fuse确实是其中一种情况,但如果您自己实现内核文件系统驱动程序,我想从技术上讲您可以选择不这样做,尽管你写的内容可能永远不会在树中,除非有合法的原因)。
https://github.com/torvalds/linux/blob/v6.8/fs/fuse/inode.c#L392 https://github.com/torvalds/linux/blob/v6.8/fs/inode.c #L2315 https://github.com/torvalds/linux/blob/v6.8/block/fops.c#L853 https://github.com/torvalds/linux/blob/v6.8/block/fops.c #L600
正如您所看到的,“陷阱”将确保当块文件打开时,有一个特殊的句柄将其
rdev
(由您在块文件上设置的“主要”和“次要”数字组成)解析为设备并打开设备。换句话说,即使您使用的任何库似乎都允许您将块文件“映射”到常规文件左右,但最终,块文件上设置的主要和次要编号决定了它所指的内容,只要它的类型是块文件。由于这些数字很可能被初始化为一些不引用系统中任何实际设备的值,因此您将得到一个
ENXIO
(“没有这样的设备或地址”):https://github.com/torvalds/linux/blob/v6.8/block/bdev.c#L840
也许您比我更熟悉 FUSE,但无论哪种方式,此图都可以帮助说明这个想法:
https://commons.wikimedia.org/wiki/File:FUSE_struct.svg
基本上,在块文件的情况下,“箭头”不会从内核部分指向用户空间
libfuse
部分(它是或相当于导致您的自定义文件系统实现的部分)FUSE
,而是可能会指向其他相关的内核部分并转到返回VFS
并转到用户空间到ls
请求操作的任何程序(例如图中)。