我正在努力创建一个 FAT 格式的磁盘映像来存储已知大小的文件。在本例中为 1 GiB 文件。
例如:
# Create a file that's 1 GiB in size.
dd if=/dev/zero iflag=count_bytes of=./large-file bs=1M count=1G
# Measure file size in KiB.
LARGE_FILE_SIZE_KIB="$(du --summarize --block-size=1024 large-file | cut --fields 1)"
# Create a FAT-formatted disk image.
mkfs.vfat -vv -C ./disk.img "${LARGE_FILE_SIZE_KIB}"
# Mount disk image using a loopback device.
mount -o loop ./disk.img /mnt
# Copy the large file to the disk image.
cp --archive ./large-file /mnt
该脚本失败并显示以下输出:
++ dd if=/dev/zero iflag=count_bytes of=./large-file bs=1M count=1G
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 39.6962 s, 27.0 MB/s
+++ du --summarize --block-size=1024 large-file
+++ cut --fields 1
++ LARGE_FILE_SIZE_KIB=1048580
++ mkfs.vfat -vv -C ./disk.img 1048580
mkfs.fat 4.2 (2021-01-31)
Auto-selecting FAT32 for large filesystem
Boot jump code is eb 58
Using 32 reserved sectors
Trying with 8 sectors/cluster:
Trying FAT32: #clu=261627, fatlen=2048, maxclu=262144, limit=65525/268435446
Using sector 6 as backup boot sector (0 = none)
./disk.img has 64 heads and 63 sectors per track,
hidden sectors 0x0000;
logical sector size is 512,
using 0xf8 media descriptor, with 2097144 sectors;
drive number 0x80;
filesystem has 2 32-bit FATs and 8 sectors per cluster.
FAT size is 2048 sectors, and provides 261627 clusters.
There are 32 reserved sectors.
Volume ID is f0de10c3, no volume label.
++ mount -o loop ./disk.img /mnt
++ cp --archive ./large-file /mnt
cp: error writing '/mnt/large-file': No space left on device
如何创建足够大的 FAT 格式磁盘映像来存储已知大小的文件?
资源:
- https://linux.die.net/man/1/dd
- https://linux.die.net/man/8/mkfs.vfat
- https://linux.die.net/man/8/mount
- https://linux.die.net/man/1/cp
- https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#Size_limits
编辑1
我的假设是这mkfs.vfat -C ./disk.img N
将创建一个具有N
KiB 可用空间的图像,但我想情况并非如此。
答案1
在我看来,您正试图将一个文件放入没有足够空间的文件系统上——这是设计造成的!您基本上是在说“对于需要 N kB 的文件,使磁盘映像的大小恰好为 N kB”。 FAT 到底在哪里存储文件元数据、目录表和卷描述符?
使用FAT32,标准的重复超级块,以及通常的目录表+长文件名表,再加上磁盘上某处的32个保留扇区,从我的角度来看,您将需要几MB的额外空间,大约4MB,我想,对于这么大的文件系统来说。
您还使用默认值的 mkfs.vfat,这对于 80 年代和 1990 年代初制造的 PC 来说是一个合理的配置,这意味着扇区更小,因此需要跟踪的扇区更多,因此 FAT 存储的消耗更多一个文件。最大化扇区大小;在文件系统上超过 1GB 可用空间(因此,磁盘映像明显大于 1GB!),16 kB应该工作(最小“FAT32-legal”簇计数为 65525,假设 4kB 扇区除以 1GB,所以-S 4096
,并将簇大小最大化为 16 个扇区,所以-s 16
)。
另请注意:对于 1 GB,您已经非常接近 FAT32 的最大文件大小 – 2 GB。因此,如果打算将其用作备份或文件系统映像等存储,您可能很快就会发现 FAT32 无法满足要求。它是非常旧文件系统。
附录:如何计算图像比内容大多少
如上所述,FAT 有点烦人,因为它对扇区数量有任意(而且出奇的低!)限制,这使得预测将产生多少开销变得有点困难。
然而,好处是Linux支持疏文件,这意味着您可以制作一个“空”图像,不会“消耗”任何存储空间。该图像可能比您需要的更大!
然后,您可以用所需的数据填充它,并将其缩小到您想要的大小。
一般来说,您的问题中的脚本会做一些有问题的事情,并且有更明智的方法来实现相同的目的;我将在代码中评论我的命令的等效内容。这个想法很简单:制作一个比必要的大得多,但在存储方面“免费”的图像文件,先用您需要的文件填充它,然后检查还剩多少可用空间。从图像大小中减去该空间,制作一个新图像,就完成了。
# File(s) we want to store
files=( *.data ) # whatever you want to store.
imgfile="fat32.img"
# First, figure out how much size we'll need
# use `stat` to get the size in bytes instead of parsing `du`'s output
# Replace the new line after each file size with a "+" and add the initial overhead.
initial_overhead=$(( 5 * 2**20 )) # 5 MiB
# Then use your shell's arithmetic evaluation $(( … )) to execute that sum
totalsize=$(( $(stat -c '%s' -- "${files[@]}" | tr '\n' '+') initial_overhead ))
# give an extra 20% (no floating point math in bash…), then round to 1 kiB blocks
img_size=$(( ( totalsize * 120 / 100 + 1023 ) / 1024 * 1024 ))
# Create a file of that size
fallocate -l ${img_size} -- "${img_file}"
mkfs.vfat -vv -- "${img_file}"
# set up loopback device as regular user, and extract loopback
# device name from result
loopback=$(udisksctl loop-setup - "${img_file}" | sed 's/.* \([^ ]*\)\.$/\1/')
# mount loopback device as regular user, and get mount path
mounted=$(udisksctl mount -b "${loopback}" | sed 's/^.* \([^ ]*\)$/\1/')
# make sure we're good so far
[[ -d "${mounted}" ]] || (echo "couldn't get mount"; exit -1)
# copy over files…
cp -- "${files[@]}" "${mounted}"
# … and unmount our file system image
udisksctl unmount -b "${loopback}"
udisksctl loop-delete -b "${loopback}"
# use df to directly get the amount of free space in kilobyte blocks
free_space=$(df --direct --block-size=1K --output=avail -- "${img_file}" | tail -n1)
# We no longer need our temporary image
rm -- "${img_file}"
# subtract 2 kB just to be on the safe side when making new image
new_img_size=$(( free_space - 2 ))
# Make a new image, copy over files
fallocate -l ${new_img_size} -- "${img_file}"
mkfs.vfat -vv -- "${img_file}"
loopback=$(udisksctl loop-setup - "${img_file}" | sed 's/.* \([^ ]*\)\.$/\1/')
mounted=$(udisksctl mount -b "${loopback}" | sed 's/^.* \([^ ]*\)$/\1/')
[[ -d "${mounted}" ]] || (echo "final copy: couldn't get mount"; exit -1)
cp -- "${files[@]}" "${mounted}"
udisksctl unmount -b "${loopback}"
udisksctl loop-delete -b "${loopback}"
# Done!
¹:当 FAT32 推出时,1 GB 的硬盘仍然不算小,文件系统结构源自 1981 年的 FAT12,设计用于 360 kB 大小的软盘;1 GB 硬盘可能需要保留的块数量在 15 年左右才会实现。实际上,将 SD 卡格式化为 FAT32 的智能手机会随身携带一个时间胶囊,用于存储大约 1997 年发明的文件系统,而这本身只是对 1980 年发明的文件系统进行了相对轻微的修改;所以,是的,用 44 年前的折衷解决方案解决了现代存储问题。
答案2
我修改了您的代码并添加了一些附加注释。
它的工作原理如下:
# Create a file that's 1 GiB in size.
dd if=/dev/zero iflag=count_bytes of=./large-file bs=1M count=1G
# Measure file size in KiB. BUT TWICE THE SIZE!
# Double parentheses are needed for arithmetic
LARGE_FILE_SIZE_KIB=$(($(du --summarize --block-size=1024 large-file | cut --fields 1) * 2))
# Create a FAT-formatted disk image.
sudo mkfs.vfat -vv -C ./disk.img "${LARGE_FILE_SIZE_KIB}"
# Create mount directory
sudo mkdir /mnt/test
# Mount disk image using a loopback device.
sudo mount -o loop -t vfat ./disk.img /mnt/test
# Copy the large file to the disk image.
# BUT do not use --archive option
sudo cp ./large-file /mnt/test
编辑:
免责声明。
我没有调查为什么虚拟磁盘的最小大小应该是正在复制的文件大小的两倍。
但我怀疑这与块大小有关,块大小要么太低要么太高,无法容纳 1GB 文件。