付録:画像がコンテンツよりどれだけ大きいかを計算する方法

付録:画像がコンテンツよりどれだけ大きいかを計算する方法

既知のサイズのファイルを保存するために、FAT形式のディスクイメージを作成しています。これは1GiBファイルです。

たとえば、

# 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形式のディスクイメージを作成する方法は?

リソース:

編集1

私の仮定は、これがKiBの空き領域を持つ画像をmkfs.vfat -C ./disk.img N生成するということNでしたが、そうではありません。

答え1

十分なスペースがないファイルシステムにファイルを入れようとしているようです。これは意図的に設計されています!基本的に、「N kBが必要なファイルの場合は、ディスクイメージのサイズを正確にN kBにします」と言います。 FATはファイルメタデータ、ディレクトリテーブル、ボリュームディスクリプタを正確にどこに保存しますか?

FAT32、標準の反復スーパーブロック、一般的なディレクトリテーブル+長いファイル名テーブル、およびディスクのどこかに32の予約セクタを使用すると、私の考えには約4MBの余分なスペースが必要になると思います。大容量ファイルシステム。
また、1980年代と1990年代初頭に構築されたPCに適した構成である基本mkfs.vfatを使用しています。これは小さいセクターを意味するため、追跡するセクターが多いため、FATが保存されるとファイルがもう1つ消費されます。ファイルシステムのセクタサイズを最大化します。1GB以上の空き容量(それでディスクイメージは1GBよりはるかに大きいです!)、16kBしなければならない動作します(最小「FAT32-legal」クラスター数は65525で、4kBセクターを1GBで割ったものとし、-S 4096クラスターサイズを16セクターに最大化するため-s 16)。

また、注:1GBを使用すると、すでにFAT32の最大ファイルサイズである2GBに非常に近いです。したがって、バックアップやファイルシステムイメージなどの保存目的で使用したい場合は、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!

1:FAT32が導入されたとき、1GBのハードドライブはまだそれほど小さくありません。ファイルシステム構造は、1981年にFAT12から派生し、1GBハードドライブに必要なブロック数である360kBサイズのフロッピーディスク用に設計されています。維持することは約15年後にのみ増加します。実際、FAT32でフォーマットされたSDカードを搭載したスマートフォンには、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

編集する:

妻の声明。

仮想ディスクの最小サイズがコピーされるファイルサイズの2倍である理由を調査していません。

しかし、私はこれが1GBファイルを収容するには低すぎるか高すぎるブロックサイズに関連していると思います。

関連情報