使用完整文件系统代替传统压缩格式

将Squashfs用作日常归档压缩格式

Posted by wszqkzqk on December 16, 2023
本文字数:5160

前言

笔者曾经在Linux下的备份方式小结一文中总结过Linux下的备份方式,其中就提到过使用Squashfs进行数据存档与备份的方法。

事实上,Squashfs的适用场景不仅仅局限于备份,它还可以用于日常归档压缩,甚至可以用于替代传统的压缩格式。Squashfs的支持也非常广泛,几乎所有的Linux发行版都支持Squashfs,而Windows和macOS也都有Squashfs的解压工具;对于zstd等相对较新的算法,在其他平台下打开采用对应压缩方式的Squashfs则可能需要安装额外的软件,例如7-Zip-zstd

Squashfs的优势

Squashfs是一种只读文件系统,相较于传统的ziptar.gztar.xztar.zst等压缩格式,Squashfs的主要优势是它是一个完整的文件系统,在Linux下可以直接挂载使用,而不需要解压缩。同时,Squashfs在使用合适的压缩算法及分块大小时,能够兼有高压缩率与高访问速度。另外,Squashfs还支持文件级别重复数据删除,进一步节省空间。

基础用法

在不追求极致压缩率的场景下,笔者推荐使用zstd:3的压缩算法,这样可以节省空间,而且压缩速度非常快、CPU占用很小。根据zstd算法的特性,使用较高压缩等级的zstd算法可以在牺牲一定创建压缩的速度的情况下获得更高的压缩率,但是并不会明显降低透明的解压缩的速度,因此,如果需要更高的压缩率,可以考虑使用更高等级的zstd压缩算法,例如将压缩等级设置为6 - 15。

笔者个人不太推荐使用解压性能相对较差的gzipxz等算法,因为这些算法的压缩率提升相较于zstd有限,而且解压速度较慢,不适合创建后的快速访问。

如果需要比zstd更快的透明访问/解压速度,可以考虑使用lz4算法,lz4的解压速度非常快,但是在实际场景中,zstd的速度其实也已经足够快了,而且在压缩率上一般还有一定的优势。(PS. zstdlz4的作者其实是同一个人)

需要注意的是,mksquashfs的基本命令格式为:

mksquashfs source1 source2 ...  FILESYSTEM [OPTIONS]

[OPTIONS]必须放到FILESYSTEM之后,否则会报错。

文件/目录级归档

如果需要归档已经挂载的文件系统上的文件,可以直接使用mksquashfs,例如:

# 归档 /home 目录
sudo mksquashfs /home /XXX/目标目录/backup.sfs -comp zstd -Xcompression-level 3
# 归档 / 目录
sudo mksquashfs / /XXX/目标目录/backup.sfs -comp zstd -Xcompression-level 3 -e /dev -e /proc -e /sys -e /tmp -e /run -e /mnt -e /media -e /lost+found -e /boot/efi -e /efi
# 归档 / 子卷
sudo mksquashfs / /XXX/目标目录/backup.sfs -comp zstd -Xcompression-level 3 --one-file-system

这里只是用于演示较彻底的归档备份,实际使用中如果只用归档部分需要归档存储的内容,往往并不需要sudo权限,例如:

# 归档 abc 目录
mksquashfs abc /XXX/目标目录/backup.sfs -comp zstd -Xcompression-level 3

像这样创建的squashfs是一个直接包含归档文件的文件系统,可以直接挂载使用。

块设备级备份

有时候我们可能需要备份某些在Linux下不方便直接挂载并备份的分区,比如受BitLocker加密的NTFS分区等,这时,为了完成保留整个硬盘设备的信息,我们可以直接备份整个硬盘设备。如果需要备份整个硬盘设备,可以使用ddmksquashfs结合,即,用mksquashfs调用dd的输入流,例如:

sudo mksquashfs - /XXX/目标目录/backup.sfs -p "backup.img f 0644 root root dd if=/dev/nvme0n1 bs=1M" -p "/ d 0755 0 0" -comp zstd -Xcompression-level 3

这里的-表示标准输入流,-p表示添加文件,-p后面的内容是mksquashfs的文件描述符,f表示类型为文件,后面紧跟的是权限信息,dd if=/dev/nvme0n1 bs=1M表示调用dd的输入流,if=/dev/nvme0n1表示输入文件,bs=1M表示块大小;/表示根目录,d表示类型为目录,后面同样跟有权限信息。

整体上,这个命令的意思是,将/dev/nvme0n1的内容作为输入流,作为backup.img文件的内容,将backup.img文件放在创建的squashfs文件系统的根目录,然后压缩成squashfs文件系统。这样既保留了整个硬盘的完整存储信息,又启用压缩,节省了空间。不过,从这样的备份中读取内容时,需要先挂载squashfs文件系统,然后加载其中的backup.img文件为loop设备,再挂载所需要挂载的分区。

将其他归档压缩格式转换为Squashfs

有时候,我们现有的归档压缩文件可能并不是Squashfs,而是其他格式,例如tar.zsttar.gztar.xzzip等,这时,我们为了得到既节约空间,又不用解压缩就可以直接挂载使用的归档文件,往往需要将这些归档文件转换为Squashfs。

最容易想到的方法是,先解压缩归档文件,然后再使用mksquashfs创建Squashfs。然而,这样做会解压产生很多中间文件,占用大量的空间,而且需要花费大量的IO时间。

笔者在这里推荐充分利用Linux的管道特性,直接将归档文件的解压缩输出作为mksquashfs的输入流。这样,我们可以在不产生中间文件的情况下,将归档文件转换为Squashfs。

转化tar.xxx到Squashfs

mksquashfs工具支持从标准输入流读取数据,可以用-代表标准输入流stdin;同时,-tar参数可以指定从标准输入流读取tar格式的数据,因此,我们可以使用相应压缩软件gzzstd等命令的输出作为mksquashfs的输入流。

笔者在这里示出一个例子,小米发布的线刷包是一个tar.gz格式的归档文件,在使用时,一般需要先解压,然而,这势必会浪费大量的时间。而如果直接在本地存储解压后的文件,又会浪费大量的空间。因此,我们可以使用以下命令,将tar.gz格式的线刷包转换为Squashfs,既保证了压缩率,又不需要额外的解压缩过程:

gzip -cd zircon_images_V14.0.8.0.TNOCNXM_20231129.0000.00_13.0_cn_f63a143fa2.tgz | mksquashfs - zircon_images_V14.0.8.0.TNOCNXM_20231129.0000.00_13.0_cn_f63a143fa2.sfs -tar -p "/ d 0755 0 0" -comp zstd -Xcompression-level 6

这里的gzip -cd表示解压缩(-c参数)tar.gz文件并输出到标准输出流stdout-d参数),|表示管道,mksquashfs -表示将标准输入流作为mksquashfs的输入流,-tar表示从标准输入流读取未压缩的tar数据,-p表示定义Squashfs中的文件组织,/ d 0755 0 0表示指定Squashfs文件系统的根目录,d表示类型为目录,后面跟有权限信息,-comp zstd -Xcompression-level 6表示使用zstd压缩,压缩等级为6。另外需要注意的是,解压命令需要与原压缩包的压缩格式相匹配,例如,如果原压缩包是tar.xz格式,那么解压命令应该是xz -cd,如果原压缩包是tar.zst格式,那么解压命令应该是zstd -cd

这样,我们就在不产生中间文件的情况下,将tar.xxx格式的线刷包转换为了Squashfs,可以直接挂载使用。

当然,对于本身即未压缩的tar数据,我们也可以直接使用mksquashfs-tar参数,直接用cat传入即可,例如:

cat example.tar |mksquashfs - example.sfs -tar -comp zstd -Xcompression-level 6

此外,如果本地还没有下载tar.xxx格式的归档文件,我们也可以直接使用wget的输出流进行转化,例如:

wget -O - https://example.com/example.tar.gz | gzip -d | mksquashfs - example.sfs -tar -comp zstd -Xcompression-level 6

这样,我们就在不产生中间文件的情况下,将远程的tar.xxx格式的归档文件转换为了Squashfs,可以直接挂载使用。

转化zip到Squashfs

由于mksquashfs工具可以高效地从标准输入流stdin中读取未压缩的tar数据,我们可以用类似的方法处理zip格式的归档文件,但细节上有所不同。

一般的zip归档文件通常与tar.xxx不同,它的归档与压缩均由zip完成,内部并不含有未压缩的tar数据。因此,我们在这里不能直接使用unzip的输出流进行管道操作。

幸运的是,bsdtar这一强大的工具可以直接读取zip格式的归档文件(只需要在文件路径前面加上@),并将其转化为tar流,通过管道正常传输给mksquashfs。因此,我们可以使用以下命令,将zip格式的归档文件转换为Squashfs:

bsdtar -c @test.zip | mksquashfs - test.sfs -tar -p "/ d 0755 0 0" -comp zstd -Xcompression-level 6

这里的bsdtar -c @test.zip表示将test.zip文件转换为tar流并输出到标准输出流stdout|表示管道,mksquashfs -表示将标准输入流作为mksquashfs的输入流,-tar表示从标准输入流读取未压缩的tar数据,-p表示定义Squashfs中的文件组织,/ d 0755 0 0表示添加根目录,d表示类型为目录,后面跟有权限信息,-comp zstd -Xcompression-level 6表示使用zstd压缩,压缩等级为6

这样,我们就在不产生中间文件的情况下,将zip格式的归档文件转换为了Squashfs,可以直接挂载使用。

bsdtar直接读取归档的特性不仅仅适用于zip格式的归档文件,还适用于tar, pax, cpio, jar, ar, xar, rpm, 7z, ISO 9660 CDROM,以上格式均可利用类似方法转换为Squashfs。

Squashfs到Squashfs的转化

有时候,我们可能需要更换Squashfs的压缩算法、块大小、压缩等级等参数,这时,我们必须要另外创建新的Squashfs。虽然Squashfs可以直接挂载使用,即使先挂载,再使用mksquashfs也没有任何解压的中间文件产生;但是,这样的操作其实是在随机访问已挂载的Squashfs,IO性能可能较差。如果需要一次性访问Squashfs中的所有文件,先挂载再访问的方式一般会比使用unsquashfs慢。

然而,如果直接使用unsquashfs解压缩Squashfs,又会产生大量的中间文件,占用大量的空间,而且需要花费大量的IO时间。因此,我们可以使用类似的方法,将unsquashfs的输出作为mksquashfs的输入流,这样,我们就可以在不产生中间文件的情况下,将Squashfs转换为新的Squashfs。例如:

unsquashfs -pf - source.sfs | mksquashfs - output.sfs -pf - -comp zstd -Xcompression-level 6 -b 16K

这里的unsquashfs -pf - source.sfs表示将source.sfs文件解压缩到标准输出流stdout,同时也一并输出了文件组织的结构、权限等信息,|表示管道,mksquashfs -表示将标准输入流作为mksquashfs的输入流,-pf -表示从标准输入流读取文件组织的结构、权限等信息,-comp zstd -Xcompression-level 6表示使用zstd压缩,压缩等级为6-b 16K表示块大小为16K

这样,我们就在不产生中间文件的情况下,利用unsquashfs更优秀的IO性能,将原来的Squashfs快速转换为了新的Squashfs,并重新指定了压缩算法、块大小、压缩等级等参数。