在 FreeBSD 上以 lsblk(8) 风格列出块设备

当我需要在 Linux 系统上工作时,我通常会怀念很多 FreeBSD 上很棒的工具,比如仅举几例:

  • sockstat

  • gstat

  • top -b -o res

  • top -m io -o total

  • usbconfig

  • rcorder

  • beadm/bectl

  • idprio/rtprio

……但有时候——虽然很少——Linux 上也有一些非常有用的工具,而 FreeBSD 上则没有。例子就是 lsblk(8),它专注于一件事,而且做得相当好——列出块设备及其内容。它也有一些问题,比如在完全应用 ZFS 池使用的磁盘上,它会显示两个分区,而不是直接显示 ZFS 的信息——但我们都知道,在某些圈子里,CDDL 许可的 ZFS 在那个 GPL 世界里是多么地“不受待见”。

来自 Linux 系统的 lsblk(8) 示例输出:

$ lsblk
NAME                         MAJ:MIN RM   SIZE RO TYPE   MOUNTPOINT
sr0                           11:0    1  1024M  0 rom
sda                            8:0    0 931.5G  0 disk
|-sda1                         8:1    0   500M  0 part   /boot
`-sda2                         8:2    0   931G  0 part
  |-vg_local-lv_root (dm-0)  253:0    0    50G  0 lvm    /
  |-vg_local-lv_swap (dm-1)  253:1    0  17.7G  0 lvm    [SWAP]
  `-vg_local-lv_home (dm-2)  253:2    0   1.8T  0 lvm    /home
sdc                            8:32   0 232.9G  0 disk
`-sdc1                         8:33   0 232.9G  0 part
  `-md1                        9:1    0 232.9G  0 raid10 /data
sdd                            8:48   0 232.9G  0 disk
`-sdd1                         8:49   0 232.9G  0 part
  `-md1                        9:1    0 232.9G  0 raid10 /data

而 FreeBSD 在这方面提供了什么?可以使用命令 camcontrol(8)geom(8)。你也可以使用 gpart(8) 命令来列出分区。下面是我在单磁盘笔记本上运行这些命令的输出。

它们以可接受的方式提供了所需信息,但仅适用于磁盘数量较少的系统。如果你想显示所有系统驱动器内容的汇总呢?这时 lsblk.sh 就派上用场了。虽然 lsblk(8) 拥有许多有趣的功能,如 --perms/--scsi/--inverse 模式,我这里重点提供的只是最基本的功能——列出系统块设备及其内容。由于我在编写 shell 脚本方面有长期且愉快的经验,例如 sysutils/beadmsysutils/automount,我认为编写 lsblk.sh 是一个不错的主意。我实际上在 2016 年就在这个主题 lsblk(8) Command for FreeBSDFreeBSD Forums 上‘开源’或说分享了这个项目/想法,但由于时间有限,这个“副项目”的开发进度非常缓慢。我最终重新回到它,完成了它。

lsblk.sh 是一个总体上小巧且简单的 shell 脚本,代码行数不到四百行。

image

下面是我在单硬盘笔记本上运行 lsblk.sh 命令的示例输出。

同样的输出在图形窗口中显示。

下面是来自一台拥有两块系统固态硬盘(da0/da1)和两块机械数据盘(da2/da3)的服务器的 lsblk.sh 输出示例。

下面是我在其他系统上测试 lsblk.sh 时的其他示例。

image

虽然 lsblk.sh 并不是地球上最快的脚本(因为需要进行大量解析),但它能够很好地完成工作。如果你想在系统中安装它,只需输入以下命令:

如果有时间,我可以考虑在 lsblk.sh 脚本中添加哪些其他原创的 Linux lsblk(8) 子命令/选项/参数呢?:🙂:

此致敬礼。

更新 1 – 添加 USAGE/HELP 信息

刚刚添加了一些用法信息,可以通过以下任意参数显示:

  • h

  • -h

  • --h

  • help

  • -help

  • --help

依我看,为这么一个简单的工具写 man 页面是没有必要的。我想等 lsblk.sh 工具在功能和选项上扩展到可与 Linux lsblk(8) 相媲美时,再创建专门的 man 页面。下面是它的显示效果。

此致敬礼。

更新 2 – 代码重组与重写 75%

……至少这是 git(1)commit 信息中告诉我的内容。

经过几个高效小时的工作,lsblk.sh 的新版本现已发布。

它的源代码行数类似,但现在缩小了四分之一……同时功能更多、准确性更高。这是 “少即是多” 的绝佳例子。

一些没有简单解决方案的问题如下所述。

其中之一是 FAT 文件系统的“双重”标签。我们既有 /dev/gpt/efiboot0 标签,也有 FAT 标签 EFISYS。必须在两者中做出选择。由于并非所有 FAT 文件系统都有标签,我选择了 GPT 标签。

我也无法覆盖 FUSE 挂载。当你挂载——例如——/dev/da0 设备为 NTFS(使用 ntfs-3g)或 exFAT(使用 mount.exfat)时,mount(8) 输出没有明显区别。

当我通过守护进程(如 sysutils/automount)挂载此类文件系统时,我会在 /var/run/automount.state 文件中记录设备挂载到的目录。然后,当我收到 /dev/da0 设备的 detach 事件时,我就知道该卸载哪个挂载点……但当只有 /dev/fuse 设备时,这是不可能的。

……或者,也许你知道有什么方法可以从 /dev/fuse(或 FUSE 一般)中提取设备挂载位置的信息吗?

下面展示更新后的效果。

这里是各种非 ZFS 文件系统的挂载情况:

……现在 lsblk.sh 显示它们的方式如下。

我为此使用了一些基于文件的内存设备。现在,默认情况下 lsblk.sh 也会显示内存磁盘的内容。

下面是在 xterm(1) 终端中的显示效果。

此致敬礼。

更新 3 – 添加 geli(8) 支持

我认为添加 geli(8) 支持可能会很有用。最新的 lsblk.sh 版本现在避免了 MOUNTLABEL 检测的代码重复(已移入单一统一函数)。同时添加了更多注释以提高代码可读性,并进行了一些小修复……而且脚本再次变得更小 :🙂:

此次更新大约修改了 40%(根据 git commit 显示:191 行新增,196 行删除)。

还忘了提到,现在得益于智能优化(比如避免重复操作,并将 grep(1) | awk(1) 管道聚合为单个 awk(1) 查询),lsblk.sh 的运行速度比最初版本快了三倍 :🙂:

下面是添加 geli(8) 支持后的新输出。

此致敬礼。

更新 4 – 添加 fuse(8) 支持

如我在 更新 2 中所述,跟踪 fuse(8) 下的挂载设备及其挂载位置非常困难,因为挂载完成后,所有挂载的设备都会神奇地变成 /dev/fuse

经过一些研究,我发现这个信息(在 FreeBSD 下通过 fuse(8) 接口实际挂载的设备位置)可以在挂载 procfs 文件系统于 /proc 后获取。你只需要查看所有 ntfs-3g 进程的 cmdline 条目。虽然不完美,但至少可以获取到这些信息。

这是用于检测 fuse(8) 挂载点的代码原型。

……我刚刚意识到,我找到了获取该信息的新方法(更好),无需挂载 /proc 文件系统——你只需要显示 ntfs-3g 进程及其命令行参数,例如如下方式:

因此,在我考虑到这最初仅针对 NTFS(ntfs-3g(8) 进程)后,我也添加了 exFAT 支持,通过搜索 mount.exfat 的 PID。现在 fuse(8) 挂载点检测可以同时支持 NTFS 和 exFAT 文件系统……而且支持该功能的代码甚至更简洁。

我还修改了 MAJOR 和 MINOR 号的显示方式——从 HEX 改为 DEC,就像 Linux 一样。FreeBSD Base Systemls(1) 会以 HEX 显示,例如你会得到 0x2af 值:

但使用 FreeBSD Ports 中的 GNU 等价工具 gls(1)(来自 sysutils/coreutils 包),则以 DEC 值显示 MAJOR 和 MINOR。gls(1) 只是 Linux 世界的 ls(1),由于 FreeBSD 的 Base System 已有 ls(1),开发者在名称前加了 ‘g’(代表 GNU)来区分。

使用 stat(1) 工具也可以更方便/快速获取:

最新的 lsblk.sh 如下所示:

这也是我还没有将 lsblk.sh 添加到 FreeBSD Ports 的原因——几天内就发布了几版带有重要新功能的版本 :🙂:

此致敬礼。

更新 5 – 再次重写 69%

在进一步研究 gpart(8) 后,我发现使用参数 -p 是个重大改变。使用 -p 参数后,它会直接显示带有分区名的输出,不再需要自己寻找 PREFIX 并“创建”分区名。

默认 gpart(8) 输出:

使用参数 -p 的输出:

这一发现导致 lsblk.sh 进行了相当大幅重写。git commit 估计此次重写达 69%:

最新 lsblk.sh 的特性如下:

  • 修复了之前的 BUG。

  • 可以检测 exFAT 标签。

  • 运行速度提升 20%。

  • SLOC 减少 10%。

  • 代码量减少 15%。

  • 正确处理整个设备上的 bsdlabel(8)

  • 正确处理整个设备上的 exFAT。

代码差异如下:

最新 lsblk.sh 看起来仍然如以前,但我现在用 ‘-’ 替代了以前的 ‘’ 标记:

更新 6 – 新的更新与修复版本

lsblk.sh 已更新至 3.4 版本——并已在 FreeBSD Ports 树中更新,在 sysutils/lsblk Port 中可用。

该版本的 变更日志 如下:

  • 在磁盘列表中添加 sysctl -n kern.disks

  • __gpart_present 函数中重置 LABEL

  • 修复 gpart(8)[bootme][bootonce] 标志行为。

  • 禁用 GPTID 显示标签。

  • 添加 -d|–disks 选项,仅列出整个磁盘。

请注意,lsblk.sh 使用 diskinfo(8),为了正常工作,你需要属于 operator 组。你可以这样将自己添加到该组:

……或者通过编辑 /etc/group 文件。

示例输出如下:

更新 7 – 更多修复

lsblk.sh 已更新至 3.5 版本——并已在 FreeBSD Ports 树中更新,在 sysutils/lsblk Port 中可用。

该版本的 变更日志 如下:

  • 列出磁盘时移除输出中的控制序列和颜色。

  • diskinfo(8) 仅用于 md(4) 磁盘,因为 geom(4) 不支持它们。

  • 添加新注释并重新整理部分旧注释。

  • 为 SIZE 收集和显示增加额外检查。

  • 当整个设备没有分区时,正确显示 exFAT 文件系统标签。

  • 修复 NTFS-3G 挂载点显示。

  • 检查 automount(8)/var/run/automount.state 以处理 fusefs(5) 文件系统。

md(4) 磁盘的大小需要属于 operator 组。所有其他磁盘大小现在通过 geom(8) 命令收集。

更新 8 – 优化标签

现在 3.9 版本的 lsblk(8) 比以往更好。

该版本的 变更日志 如下:

  • 改进 glabel(8) 标签搜索。

  • 改进 gpart(8) 输出标签处理。

  • 将冗长的 Microsoft 分区名称替换为合理名称。

  • 改进 exFAT 处理。

  • 改进 md(4) 磁盘大小处理。

  • 移除标签中的冗余空格。

  • -d 选项添加 TOTAL SYSTEM STORAGE

  • 移除主设备检查循环中的子 shell。

已提交 PR 283268 ,因此在 Ports 中更新前请稍作等待。

最后更新于

这有帮助吗?