Eu consegui encontrar uma maneira de fazer isso. Ele usa um script python que primeiro usa debugfs
para encontrar o número necessário de blocos (incluindo blocos indiretos) que o arquivo precisará. Em seguida, grava manualmente os blocos indiretos no disco e invoca debugfs
novamente para marcar os blocos usados e atualizar o inode do arquivo.
O único problema é que debugfs
aparentemente não atualiza a contagem de blocos livres do grupo de blocos quando você usa setb
. Embora eu possa definir esse parâmetro manualmente, parece não haver nenhuma maneira de imprimir o valor atual, portanto, não consigo calcular o valor correto. Tanto quanto eu posso dizer que não tem quaisquer consequências negativas reais, e fsck.ext3
pode ser usado para corrigir os valores, se necessário, então, para fins de benchmark, isso será suficiente.
Se houver algum outro problema de consistência do sistema de arquivos que eu tenha perdido, avise-me, mas como fsck.ext3
não informa nada além da contagem incorreta de blocos livres, eu devo estar seguro.
import sys
import tempfile
import struct
import subprocess
SECTOR_SIZE = 512
BLOCK_SIZE = 4096
DIRECT_BLOCKS = 12
BLOCKS_PER_INDIRECT_BLOCK = BLOCK_SIZE / 4
def write_indirect_block(device, indirect_block, blocks):
print "writing indirect block ", indirect_block
dev = open(device, "wb")
dev.seek(indirect_block * BLOCK_SIZE)
# Write blocks
for block in blocks:
bin_block = struct.pack("<I", int(block))
dev.write(bin_block)
zero = struct.pack("<I", 0)
# Zero out the rest of the block
for x in range(len(blocks), BLOCKS_PER_INDIRECT_BLOCK):
dev.write(zero)
dev.close()
def main(argv):
if len(argv) < 5:
print "Usage: ext3allocfile.py [device] [file] [sizeInMB] [offsetInMB]"
return
device = argv[1] # device containing the ext3 file system, e.g. "/dev/sdb1"
file = argv[2] # file name relative to the root of the device, e.g. "/myfile"
size = int(argv[3]) * 1024 * 1024 # Size in MB
offset = int(argv[4]) * 1024 * 1024 # Offset from the start of the device in MB
if size > 0xFFFFFFFF:
# Supporting this requires two things: triple indirect block support, and proper handling of size_high when changing the inode
print "Unable to allocate files over 4GB."
return
# Because size is specified in MB, it should always be exactly divisable by BLOCK_SIZE.
size_blocks = size / BLOCK_SIZE
# We need 1 indirect block for each 1024 blocks over 12 blocks.
ind_blocks = (size_blocks - DIRECT_BLOCKS) / BLOCKS_PER_INDIRECT_BLOCK
if (size_blocks - DIRECT_BLOCKS) % BLOCKS_PER_INDIRECT_BLOCK != 0:
ind_blocks += 1
# We need a double indirect block if we have more than one indirect block
has_dind_block = ind_blocks > 1
total_blocks = size_blocks + ind_blocks
if has_dind_block:
total_blocks += 1
# Find free blocks we can use at the offset
offset_block = offset / BLOCK_SIZE
print "Finding ", total_blocks, " free blocks from block ", offset_block
process = subprocess.Popen(["debugfs", device, "-R", "ffb %d %d" % (total_blocks, offset_block)], stdout=subprocess.PIPE)
output = process.stdout
# The first three entries after splitting are "Free", "blocks", "found:", so we skip those.
blocks = output.readline().split(" ")[3:]
output.close()
# The last entry may contain a line-break. Removing it this way to be safe.
blocks = filter(lambda x: len(x.strip(" \n")) > 0, blocks)
if len(blocks) != total_blocks:
print "Not enough free blocks found for the file."
return
# The direct blocks in the inode are blocks 0-11
# Write the first indirect block, listing the blocks for file blocks 12-1035 (inclusive)
if ind_blocks > 0:
write_indirect_block(device, int(blocks[DIRECT_BLOCKS]), blocks[DIRECT_BLOCKS + 1 : DIRECT_BLOCKS + 1 + BLOCKS_PER_INDIRECT_BLOCK])
if has_dind_block:
dind_block_index = DIRECT_BLOCKS + 1 + BLOCKS_PER_INDIRECT_BLOCK
dind_block = blocks[dind_block_index]
ind_block_indices = [dind_block_index+1+(i*(BLOCKS_PER_INDIRECT_BLOCK+1)) for i in range(ind_blocks-1)]
# Write the double indirect block, listing the blocks for the remaining indirect block
write_indirect_block(device, int(dind_block), [blocks[i] for i in ind_block_indices])
# Write the remaining indirect blocks, listing the relevant file blocks
for i in ind_block_indices:
write_indirect_block(device, int(blocks[i]), blocks[i+1:i+1+BLOCKS_PER_INDIRECT_BLOCK])
# Time to generate a script for debugfs
script = tempfile.NamedTemporaryFile(mode = "w", delete = False)
# Mark all the blocks as in-use
for block in blocks:
script.write("setb %s\n" % (block,))
# Change direct blocks in the inode
for i in range(DIRECT_BLOCKS):
script.write("sif %s block[%d] %s\n" % (file, i, blocks[i]))
# Change indirect block in the inode
if size_blocks > DIRECT_BLOCKS:
script.write("sif %s block[IND] %s\n" % (file, blocks[DIRECT_BLOCKS]))
# Change double indirect block in the inode
if has_dind_block:
script.write("sif %s block[DIND] %s\n" % (file, dind_block))
# Set total number of blocks in the inode (this value seems to actually be sectors
script.write("sif %s blocks %d\n" % (file, total_blocks * (BLOCK_SIZE / SECTOR_SIZE)))
# Set file size in the inode
# TODO: Need support of size_high for large files
script.write("sif %s size %d\n" % (file, size))
script.close()
# execute the script
print "Modifying file"
subprocess.call(["debugfs", "-w", device, "-f", script.name])
script.unlink(script.name)
if __name__ == "__main__":
main(sys.argv)
O script pode ser usado da seguinte forma para criar um arquivo de 1 GB no deslocamento de 200 GB (você precisa ser root):
touch /mount/point/myfile
sync
python ext3allocfile.py /dev/sdb1 /myfile 1024 204800
umount /dev/sdb1
mount /dev/sdb1
O umount / mount combo é necessário para fazer o sistema reconhecer a mudança. Você pode desmontar antes de invocar o script, mas isso invoca debugfs
mais devagar.
Se alguém quiser usar isso: não garanto que funcionará corretamente, não assumo a responsabilidade se você perder algum dado. Em geral, não use em um sistema de arquivos que contenha algo importante.