import sys
import os
import subprocess
from msdosfs import *
from HexDump import HexDump
class LaunchError(Exception):
def __init__(self, returncode):
self.returncode = returncode
if returncode < 0:
self.message = "Program exited with signal %d" % -returncode
else:
self.message = "Program exited with status %d" % returncode
def __str__(self):
return self.message
class FailureExpected(Exception):
def __init__(self, s):
self.s = s
def __str__(self):
return self.s
def launch(args, **kwargs):
print "launch:", args, kwargs
p = subprocess.Popen(args, **kwargs)
stdout, stderr = p.communicate()
if p.returncode != 0:
raise LaunchError(p.returncode)
return stdout, stderr
def test_fat32(dir, fsck, newfs):
dmg = os.path.join(dir, 'Test20GB.sparseimage')
launch('hdiutil create -size 20g -type SPARSE -layout NONE'.split()+[dmg])
newfs_opts = "-F 32 -b 4096 -v TEST20GB".split()
disk = launch(['hdiutil', 'attach', '-nomount', dmg], stdout=subprocess.PIPE)[0].rstrip()
rdisk = disk.replace('/dev/disk', '/dev/rdisk')
test_empty(rdisk, fsck, newfs, newfs_opts)
orphan_clusters(rdisk, fsck, newfs, newfs_opts)
file_excess_clusters(rdisk, fsck, newfs, newfs_opts)
file_bad_clusters(rdisk, fsck, newfs, newfs_opts)
dir_bad_start(rdisk, fsck, newfs, newfs_opts)
root_bad_start(rdisk, fsck, newfs, newfs_opts) root_bad_first_cluster(rdisk, fsck, newfs, newfs_opts) dir_size_dots(rdisk, fsck, newfs, newfs_opts)
long_name(rdisk, fsck, newfs, newfs_opts)
past_end_of_dir(rdisk, fsck, newfs, newfs_opts)
fat_bad_0_or_1(rdisk, fsck, newfs, newfs_opts)
fat_mark_clean(rdisk, fsck, newfs, newfs_opts)
file_4GB(rdisk, fsck, newfs, newfs_opts)
file_4GB_excess_clusters(rdisk, fsck, newfs, newfs_opts)
launch(['diskutil', 'eject', disk])
os.remove(dmg)
def test_fat16(dir, fsck, newfs):
dmg = os.path.join(dir, 'Test160MB.dmg')
f = file(dmg, "w")
f.truncate(160*1024*1024)
f.close
newfs_opts = "-F 16 -b 4096 -v TEST160MB".split()
disk = launch(['hdiutil', 'attach', '-nomount', dmg], stdout=subprocess.PIPE)[0].rstrip()
rdisk = disk.replace('/dev/disk', '/dev/rdisk')
test_empty(rdisk, fsck, newfs, newfs_opts)
orphan_clusters(rdisk, fsck, newfs, newfs_opts)
file_excess_clusters(rdisk, fsck, newfs, newfs_opts)
file_bad_clusters(rdisk, fsck, newfs, newfs_opts)
dir_bad_start(rdisk, fsck, newfs, newfs_opts)
dir_size_dots(rdisk, fsck, newfs, newfs_opts)
long_name(rdisk, fsck, newfs, newfs_opts)
past_end_of_dir(rdisk, fsck, newfs, newfs_opts)
fat_bad_0_or_1(rdisk, fsck, newfs, newfs_opts)
fat_mark_clean(rdisk, fsck, newfs, newfs_opts)
launch(['diskutil', 'eject', disk])
os.remove(dmg)
def test_fat12(dir, fsck, newfs):
dmg = os.path.join(dir, 'Test15MB.dmg')
f = file(dmg, "w")
f.truncate(15*1024*1024)
f.close
newfs_opts = "-F 12 -b 4096 -v TEST15MB".split()
disk = launch(['hdiutil', 'attach', '-nomount', dmg], stdout=subprocess.PIPE)[0].rstrip()
rdisk = disk.replace('/dev/disk', '/dev/rdisk')
test_empty(rdisk, fsck, newfs, newfs_opts)
orphan_clusters(rdisk, fsck, newfs, newfs_opts)
file_excess_clusters(rdisk, fsck, newfs, newfs_opts)
file_bad_clusters(rdisk, fsck, newfs, newfs_opts)
dir_bad_start(rdisk, fsck, newfs, newfs_opts)
dir_size_dots(rdisk, fsck, newfs, newfs_opts)
long_name(rdisk, fsck, newfs, newfs_opts)
past_end_of_dir(rdisk, fsck, newfs, newfs_opts)
fat_bad_0_or_1(rdisk, fsck, newfs, newfs_opts)
launch(['diskutil', 'eject', disk])
os.remove(dmg)
def test_empty(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
launch([fsck, '-n', disk])
def orphan_clusters(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
v.allocate(7, 100)
v.allocate(23, 150)
v.allocate(1, 190)
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-p', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def file_excess_clusters(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
head=v.allocate(7)
v.root().mkfile('FOO', head=head, length=6*v.bytesPerCluster)
head=v.allocate(1)
v.root().mkfile('BAR', head=head, length=0)
clusters = v.fat.find(9)
head = v.fat.chain(clusters)
v.root().mkfile('LINK1', head=head, length=8*v.bytesPerCluster+1)
head = v.fat.allocate(3, last=clusters[7])
v.root().mkfile('LINK2', head=head, length=2*v.bytesPerCluster+3)
head = v.fat.allocate(5, last=clusters[8])
v.root().mkfile('LINK3', head=head, length=3*v.bytesPerCluster+5)
if v.fsinfo:
v.fsinfo.allocate(9+3+5)
head = v.allocate(11, last=CLUST_BAD)
v.root().mkfile('BAD3', head=head, length=8*v.bytesPerCluster+300)
head = v.allocate(8, last=CLUST_FREE)
v.root().mkfile('FREE1', head=head, length=6*v.bytesPerCluster+100)
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def file_bad_clusters(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
clusters = v.fat.find(5)
to_free = clusters[2]
head = v.fat.chain(clusters)
v.root().mkfile('FILE1', head=head, length=6*v.bytesPerCluster+111)
if v.fsinfo:
v.fsinfo.allocate(5)
clusters = v.fat.find(5)
head = v.fat.chain(clusters)
v.root().mkfile('FILE2', head=head, length=4*v.bytesPerCluster+222)
v.fat[clusters[2]] = CLUST_RSRVD
if v.fsinfo:
v.fsinfo.allocate(5)
clusters = v.fat.find(5)
head = v.fat.chain(clusters)
v.root().mkfile('FILE3', head=head, length=4*v.bytesPerCluster+333)
v.fat[clusters[2]] = 1
if v.fsinfo:
v.fsinfo.allocate(5)
clusters = v.fat.find(5)
head = v.fat.chain(clusters)
v.root().mkfile('FILE4', head=head, length=4*v.bytesPerCluster+44)
v.fat[clusters[2]] = clusters[1]
if v.fsinfo:
v.fsinfo.allocate(5)
v.root().mkfile('FILE5', head=CLUST_FREE, length=4*v.bytesPerCluster+55)
v.root().mkfile('FILE6', head=CLUST_BAD, length=4*v.bytesPerCluster+66)
v.root().mkfile('FILE7', head=CLUST_RSRVD-1, length=4*v.bytesPerCluster+77)
head = v.allocate(5)
v.root().mkfile('FOO', head=head, length=4*v.bytesPerCluster+99)
v.root().mkfile('FILE8', head=head, length=4*v.bytesPerCluster+88)
v.fat[to_free] = CLUST_FREE
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def dir_bad_start(disk, fsck, newfs, newfs_opts):
def mkdir(parent, name, head):
bytes = make_long_dirent(name, ATTR_DIRECTORY, head=head)
slots = len(bytes)/32
slot = parent.find_slots(slots, grow=True)
parent.write_slots(slot, bytes)
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
root = v.root()
mkdir(root, 'DIR1', CLUST_FREE)
mkdir(root, 'DIR2', CLUST_RSRVD)
mkdir(root, 'DIR3', CLUST_BAD)
mkdir(root, 'DIR4', CLUST_EOF)
mkdir(root, 'DIR5', 1)
mkdir(root, 'DIR6', v.clusters+2)
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def root_bad_start(disk, fsck, newfs, newfs_opts):
def set_root_start(disk, head):
dev = file(disk, "r+")
dev.seek(0)
bytes = dev.read(512)
bytes = bytes[0:44] + struct.pack("<I", head) + bytes[48:]
dev.seek(0)
dev.write(bytes)
dev.close()
del dev
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
clusters = v.clusters
v.flush()
del v
f.close()
del f
for head in [CLUST_FREE, CLUST_RSRVD, CLUST_BAD, CLUST_EOF, 1, clusters+2]:
set_root_start(disk, head)
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
try:
launch([fsck, '-y', disk])
except LaunchError:
pass
try:
launch(['/sbin/fsck_msdos', '-n', disk])
except LaunchError:
pass
def root_bad_first_cluster(disk, fsck, newfs, newfs_opts):
for link in [CLUST_FREE, CLUST_RSRVD, CLUST_BAD]:
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
v.fat[v.rootCluster] = link
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def dir_size_dots(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
root = v.root()
child = root.mkdir('CHILD')
grand = child.mkdir('GRAND')
dir = root.mkdir('BADSIZE', length=666)
dir = root.mkdir('BADDOT')
fields = parse_dirent(dir.read_slots(0))
fields['head'] = fields['head'] + 30
dir.write_slots(0, make_dirent(**fields))
dir = root.mkdir('DOTDOT.NZ')
fields = parse_dirent(dir.read_slots(0))
fields['head'] = 47
dir.write_slots(0, make_dirent(**fields))
dir = child.mkdir('DOTDOT.ZER')
fields = parse_dirent(dir.read_slots(0))
fields['head'] = 0
dir.write_slots(0, make_dirent(**fields))
dir = grand.mkdir('DOTDOT.BAD')
fields = parse_dirent(dir.read_slots(0))
fields['head'] = fields['head'] + 30
dir.write_slots(0, make_dirent(**fields))
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def long_name(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
root = v.root()
bytes = make_long_dirent('Test1GB', ATTR_VOLUME_ID)
root.write_slots(0, bytes)
root.mkfile('The quick brown fox jumped over the lazy dog')
root.mkfile('foo.bar')
bytes = make_long_dirent('Greetings and felicitations my friends', ATTR_ARCHIVE)
bytes = bytes[0:-32] + 'HELLO ' + bytes[-21:]
assert len(bytes) % 32 == 0
slots = len(bytes) / 32
slot = root.find_slots(slots)
root.write_slots(slot, bytes)
subdir = root.mkdir('SubDir')
bytes = make_long_dirent('To be or not to be', ATTR_ARCHIVE)[32:]
slots = len(bytes) / 32
slot = subdir.find_slots(slots)
subdir.write_slots(slot, bytes)
bytes = make_long_dirent('A Man a Plan a Canal Panama', ATTR_ARCHIVE)
bytes = bytes[:32] + bytes[64:]
slots = len(bytes) / 32
slot = subdir.find_slots(slots)
subdir.write_slots(slot, bytes)
bytes = make_long_dirent('We the People in order to form a more perfect union', ATTR_ARCHIVE)
bytes = bytes[0:-64] + bytes[-32:]
slots = len(bytes) / 32
slot = subdir.find_slots(slots)
subdir.write_slots(slot, bytes)
subdir = root.mkdir('Bad Orders')
bytes = make_long_dirent('One is the loneliest number', ATTR_ARCHIVE)
bytes = chr(ord(bytes[0])+7) + bytes[1:]
slots = len(bytes) / 32
slot = subdir.find_slots(slots)
subdir.write_slots(slot, bytes)
bytes = make_long_dirent('It takes two to tango or so they say', ATTR_ARCHIVE)
bytes = bytes[:32] + chr(ord(bytes[32])+7) + bytes[33:]
slots = len(bytes) / 32
slot = subdir.find_slots(slots)
subdir.write_slots(slot, bytes)
bytes = make_long_dirent('Threes Company becomes Threes A Crowd', ATTR_ARCHIVE)
bytes = bytes[:-64] + chr(ord(bytes[-64])+7) + bytes[-63:]
slots = len(bytes) / 32
slot = subdir.find_slots(slots)
subdir.write_slots(slot, bytes)
bytes = make_long_dirent('Four score and seven years ago', ATTR_ARCHIVE)
bytes = bytes[0:-32] assert len(bytes) % 32 == 0
slots = len(bytes) / 32
slot = root.find_slots(slots)
root.write_slots(slot, bytes)
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def past_end_of_dir(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
root = v.root()
subdir = root.mkdir('SubDir')
subdir.mkfile('Good Sub File')
root.mkfile('Good Root File')
slotEOF = root.find_slots(1)
root.mkfile('EOF')
root.mkfile('BADFILE')
root.mkdir('Bad Dir')
root.mkfile('Bad File 2')
root.write_slots(slotEOF, '\x00' * 32)
slotEOF = subdir.find_slots(1)
subdir.mkfile('EOF')
subdir.mkfile('BADFILE')
subdir.mkdir('Bad Dir')
subdir.mkfile('Bad File 2')
subdir.write_slots(slotEOF, '\x00' * 32)
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def fat_bad_0_or_1(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
v.fat[0] = 0
v.fat[1] = 1
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch(['/sbin/fsck_msdos', '-n', disk])
def fat_mark_clean(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
if v.type == 32:
v.fat[1] = v.fat[1] & 0x07FFFFFF
else:
v.fat[1] = v.fat[1] & 0x7FFF
v.allocate(3)
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
f = file(disk, "r")
v = msdosfs(f)
if v.type == 32:
clean = v.fat[1] & 0x08000000
else:
clean = v.fat[1] & 0x8000
if not clean:
raise RuntimeError("Volume still dirty!")
v.flush()
del v
f.close()
del f
launch(['/sbin/fsck_msdos', '-n', disk])
def file_4GB(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
four_GB = 4*1024*1024*1024
clusters = four_GB / v.bytesPerCluster
head = v.allocate(clusters)
v.root().mkfile('4GB', head=head, length=four_GB-100)
v.flush()
del v
f.close()
del f
launch([fsck, '-n', disk])
def file_4GB_excess_clusters(disk, fsck, newfs, newfs_opts):
launch([newfs]+newfs_opts+[disk])
f = file(disk, "r+")
v = msdosfs(f)
four_GB = 4*1024*1024*1024
clusters = four_GB / v.bytesPerCluster
head=v.allocate(clusters+7)
v.root().mkfile('FOO', head=head, length=5*v.bytesPerCluster-100)
head=v.allocate(clusters+3)
v.root().mkfile('BAR', head=head, length=four_GB-30)
v.flush()
del v
f.close()
del f
try:
launch([fsck, '-n', disk])
except LaunchError:
pass
launch([fsck, '-y', disk])
launch([fsck, '-n', disk])
if __name__ == '__main__':
dir = '/tmp'
fsck = 'fsck_msdos'
newfs = 'newfs_msdos'
if len(sys.argv) > 1:
fsck = sys.argv[1]
if len(sys.argv) > 2:
dir = sys.argv[2]
if len(sys.argv) > 3:
print "%s: Too many arguments!" % sys.argv[0]
print "Usage: %s [<fsck_msdos> [<tmp_dir>]]"
sys.exit(1)
test_fat32(dir, fsck, newfs)
test_fat16(dir, fsck, newfs)
test_fat12(dir, fsck, newfs)
print "\nSuccess!"