1# 2# Run a variety of tests against fsck_msdos 3# 4# Usage: 5# python test_fsck.py [<fsck_msdos> [<tmp_dir>]] 6# 7# where <tmp_dir> is a path to a directory where disk images will be 8# temporarily created. If <path_to_fsck> is specified, it is used instead 9# of 'fsck_msdos' to invoke the fsck_msdos program (for example, to test 10# a new build that has not been installed). 11# 12 13from __future__ import with_statement 14 15import sys 16import os 17import subprocess 18import struct 19from msdosfs import * 20from HexDump import HexDump 21 22class LaunchError(Exception): 23 def __init__(self, returncode): 24 self.returncode = returncode 25 if returncode < 0: 26 self.message = "Program exited with signal %d" % -returncode 27 else: 28 self.message = "Program exited with status %d" % returncode 29 30 def __str__(self): 31 return self.message 32 33class FailureExpected(Exception): 34 def __init__(self, s): 35 self.s = s 36 def __str__(self): 37 return self.s 38 39# 40# launch -- A helper to run another process and collect the standard output 41# and standard error streams. If the process returns a non-zero exit 42# status, then raise an exception. 43# 44def launch(args, **kwargs): 45 print "launch:", args, kwargs 46 p = subprocess.Popen(args, **kwargs) 47 stdout, stderr = p.communicate() 48 if p.returncode != 0: 49 raise LaunchError(p.returncode) 50 return stdout, stderr 51 52# 53# 1. Make a disk image file 54# 2. Attach the image file, without mounting 55# ---- Begin per-test stuff ---- 56# 3. newfs_msdos the image 57# 4. Fill image with content 58# 5. fsck_msdos -n the image 59# 6. fsck_msdos -y the image 60# 7. Run /sbin/fsck_msdos against image 61# ---- End per-test stuff ---- 62# 8. Detach the image 63# 9. Delete the image file 64# 65 66# 67# Run tests on 20GiB FAT32 sparse disk image 68# 69def test_fat32(dir, fsck, newfs): 70 # 71 # Create a 20GB disk image in @dir 72 # 73 dmg = os.path.join(dir, 'Test20GB.sparseimage') 74 launch('hdiutil create -size 20g -type SPARSE -layout NONE'.split()+[dmg]) 75 newfs_opts = "-F 32 -b 4096 -v TEST20GB".split() 76 77 # 78 # Attach the image 79 # 80 disk = launch(['hdiutil', 'attach', '-nomount', dmg], stdout=subprocess.PIPE)[0].rstrip() 81 rdisk = disk.replace('/dev/disk', '/dev/rdisk') 82 83 # 84 # Run tests 85 # 86 # TODO: Known good disk 87 # empty file 88 # one cluster file 89 # larger file 90 # one cluster directory 91 # larger directory 92 # 93 test_quick(rdisk, fsck, newfs, newfs_opts) 94 test_bad_args(rdisk, fsck, newfs, newfs_opts) 95 test_maxmem(rdisk, fsck, newfs, newfs_opts) 96 test_empty(rdisk, fsck, newfs, newfs_opts) 97 test_boot_sector(rdisk, fsck, newfs, newfs_opts) 98 test_boot_fat32(rdisk, fsck, newfs, newfs_opts) # FAT32 only! 99 test_fsinfo(rdisk, fsck, newfs, newfs_opts) # FAT32 only! 100 fat_too_small(rdisk, fsck, newfs, newfs_opts) 101 orphan_clusters(rdisk, fsck, newfs, newfs_opts) 102 file_excess_clusters(rdisk, fsck, newfs, newfs_opts) 103 file_bad_clusters(rdisk, fsck, newfs, newfs_opts) 104 dir_bad_start(rdisk, fsck, newfs, newfs_opts) 105 root_bad_start(rdisk, fsck, newfs, newfs_opts) # FAT32 only! 106 root_bad_first_cluster(rdisk, fsck, newfs, newfs_opts) # FAT32 only! 107 dir_size_dots(rdisk, fsck, newfs, newfs_opts) 108 long_name(rdisk, fsck, newfs, newfs_opts) 109 past_end_of_dir(rdisk, fsck, newfs, newfs_opts) 110 fat_bad_0_or_1(rdisk, fsck, newfs, newfs_opts) 111 fat_mark_clean_corrupt(rdisk, fsck, newfs, newfs_opts) 112 fat_mark_clean_ok(rdisk, fsck, newfs, newfs_opts) 113 file_4GB(rdisk, fsck, newfs, newfs_opts) 114 file_4GB_excess_clusters(rdisk, fsck, newfs, newfs_opts) 115 116 # 117 # Detach the image 118 # 119 launch(['diskutil', 'eject', disk]) 120 121 # 122 # Delete the image file 123 # 124 os.remove(dmg) 125 126# 127# Run tests on 160MiB FAT16 image 128# 129def test_fat16(dir, fsck, newfs): 130 # 131 # Create a 160MB disk image in @dir 132 # 133 dmg = os.path.join(dir, 'Test160MB.dmg') 134 f = file(dmg, "w") 135 f.truncate(160*1024*1024) 136 f.close 137 newfs_opts = "-F 16 -b 4096 -v TEST160MB".split() 138 139 # 140 # Attach the image 141 # 142 disk = launch(['hdiutil', 'attach', '-nomount', dmg], stdout=subprocess.PIPE)[0].rstrip() 143 rdisk = disk.replace('/dev/disk', '/dev/rdisk') 144 145 # 146 # Run tests 147 # 148 # TODO: Known good disk 149 # empty file 150 # one cluster file 151 # larger file 152 # one cluster directory 153 # larger directory 154 # 155 test_quick(rdisk, fsck, newfs, newfs_opts) 156 test_bad_args(rdisk, fsck, newfs, newfs_opts) 157 test_maxmem(rdisk, fsck, newfs, newfs_opts) 158 test_empty(rdisk, fsck, newfs, newfs_opts) 159 test_boot_sector(rdisk, fsck, newfs, newfs_opts) 160 fat_too_small(rdisk, fsck, newfs, newfs_opts) 161 orphan_clusters(rdisk, fsck, newfs, newfs_opts) 162 file_excess_clusters(rdisk, fsck, newfs, newfs_opts) 163 file_bad_clusters(rdisk, fsck, newfs, newfs_opts) 164 dir_bad_start(rdisk, fsck, newfs, newfs_opts) 165 dir_size_dots(rdisk, fsck, newfs, newfs_opts) 166 long_name(rdisk, fsck, newfs, newfs_opts) 167 past_end_of_dir(rdisk, fsck, newfs, newfs_opts) 168 fat_bad_0_or_1(rdisk, fsck, newfs, newfs_opts) 169 fat_mark_clean_corrupt(rdisk, fsck, newfs, newfs_opts) 170 fat_mark_clean_ok(rdisk, fsck, newfs, newfs_opts) 171 172 # 173 # Detach the image 174 # 175 launch(['diskutil', 'eject', disk]) 176 177 # 178 # Delete the image file 179 # 180 os.remove(dmg) 181 182# 183# Run tests on 15MiB FAT12 image 184# 185def test_fat12(dir, fsck, newfs): 186 # 187 # Create a 15MB disk image in @dir 188 # 189 dmg = os.path.join(dir, 'Test15MB.dmg') 190 f = file(dmg, "w") 191 f.truncate(15*1024*1024) 192 f.close 193 newfs_opts = "-F 12 -b 4096 -v TEST15MB".split() 194 195 # 196 # Attach the image 197 # 198 disk = launch(['hdiutil', 'attach', '-nomount', dmg], stdout=subprocess.PIPE)[0].rstrip() 199 rdisk = disk.replace('/dev/disk', '/dev/rdisk') 200 201 # 202 # Run tests 203 # 204 # TODO: Known good disk 205 # empty file 206 # one cluster file 207 # larger file 208 # one cluster directory 209 # larger directory 210 # 211 test_quick(rdisk, fsck, newfs, newfs_opts) 212 test_bad_args(rdisk, fsck, newfs, newfs_opts) 213 test_maxmem(rdisk, fsck, newfs, newfs_opts) 214 test_empty(rdisk, fsck, newfs, newfs_opts) 215 test_boot_sector(rdisk, fsck, newfs, newfs_opts) 216 fat_too_small(rdisk, fsck, newfs, newfs_opts) 217 orphan_clusters(rdisk, fsck, newfs, newfs_opts) 218 file_excess_clusters(rdisk, fsck, newfs, newfs_opts) 219 file_bad_clusters(rdisk, fsck, newfs, newfs_opts) 220 dir_bad_start(rdisk, fsck, newfs, newfs_opts) 221 dir_size_dots(rdisk, fsck, newfs, newfs_opts) 222 long_name(rdisk, fsck, newfs, newfs_opts) 223 past_end_of_dir(rdisk, fsck, newfs, newfs_opts) 224 fat_bad_0_or_1(rdisk, fsck, newfs, newfs_opts) 225 226 # 227 # Detach the image 228 # 229 launch(['diskutil', 'eject', disk]) 230 231 # 232 # Delete the image file 233 # 234 os.remove(dmg) 235 236# 237# A minimal test -- make sure fsck_msdos runs on an empty image 238# 239def test_empty(disk, fsck, newfs, newfs_opts): 240 # 241 # newfs the disk 242 # 243 launch([newfs]+newfs_opts+[disk]) 244 245 # 246 # fsck the disk 247 # 248 launch([fsck, '-n', disk]) 249 250# 251# Make a volume with allocated but unreferenced cluster chains 252# 253def orphan_clusters(disk, fsck, newfs, newfs_opts): 254 launch([newfs]+newfs_opts+[disk]) 255 256 # 257 # Create some cluster chains not referenced by any file or directory 258 # 259 f = file(disk, "r+") 260 v = msdosfs(f) 261 v.allocate(7, 100) 262 v.allocate(23, 150) 263 v.allocate(1, 190) 264 v.flush() 265 del v 266 f.close() 267 del f 268 269 try: 270 launch([fsck, '-n', disk]) 271 except LaunchError: 272 pass 273 launch([fsck, '-p', disk]) 274 launch(['/sbin/fsck_msdos', '-n', disk]) 275 276# 277# Make a file with excess clusters allocated 278# One file with EOF == 0 279# One file with EOF != 0 280# Files with excess clusters that are cross-linked 281# First excess cluster is cross-linked 282# Other excess cluster is cross-linked 283# Excess clusters end with free/bad/reserved cluster 284# First excess cluster is free/bad/reserved 285# Other excess cluster is free/bad/reserved 286# 287def file_excess_clusters(disk, fsck, newfs, newfs_opts): 288 launch([newfs]+newfs_opts+[disk]) 289 290 # 291 # Create files with too many clusters for their size 292 # 293 f = file(disk, "r+") 294 v = msdosfs(f) 295 head=v.allocate(7) 296 v.root().mkfile('FOO', head=head, length=6*v.bytesPerCluster) 297 head=v.allocate(1) 298 v.root().mkfile('BAR', head=head, length=0) 299 300 # 301 # LINK1 is OK. 302 # LINK2 contains excess clusters; the first is cross-linked with LINK1 303 # LINK3 contains excess clusters; the second is cross-linked with LINK1 304 # 305 clusters = v.fat.find(9) 306 head = v.fat.chain(clusters) 307 v.root().mkfile('LINK1', head=head, length=8*v.bytesPerCluster+1) 308 head = v.fat.allocate(3, last=clusters[7]) 309 v.root().mkfile('LINK2', head=head, length=2*v.bytesPerCluster+3) 310 head = v.fat.allocate(5, last=clusters[8]) 311 v.root().mkfile('LINK3', head=head, length=3*v.bytesPerCluster+5) 312 if v.fsinfo: 313 v.fsinfo.allocate(9+3+5) 314 315 # 316 # FREE1 has its first excess cluster marked free 317 # BAD3 has its third excess cluster marked bad 318 # 319 head = v.allocate(11, last=CLUST_BAD) 320 v.root().mkfile('BAD3', head=head, length=8*v.bytesPerCluster+300) 321 head = v.allocate(8, last=CLUST_FREE) 322 v.root().mkfile('FREE1', head=head, length=6*v.bytesPerCluster+100) 323 324 v.flush() 325 del v 326 f.close() 327 del f 328 329 try: 330 launch([fsck, '-n', disk]) 331 except LaunchError: 332 pass 333 launch([fsck, '-y', disk]) 334 launch(['/sbin/fsck_msdos', '-n', disk]) 335 336# 337# Make files with bad clusters in their chains 338# FILE1 file with middle cluster free 339# FILE2 file with middle cluster bad/reserved 340# FILE3 file with middle cluster points to out of range cluster 341# FILE4 file with middle cluster that is cross-linked (to same file) 342# FILE5 file whose head is "free" 343# FILE6 file whose head is "bad" 344# FILE7 file whose head is out of range 345# FILE8 file whose head is cross-linked 346# 347def file_bad_clusters(disk, fsck, newfs, newfs_opts): 348 launch([newfs]+newfs_opts+[disk]) 349 350 f = file(disk, "r+") 351 v = msdosfs(f) 352 353 clusters = v.fat.find(5) 354 to_free = clusters[2] 355 head = v.fat.chain(clusters) 356 v.root().mkfile('FILE1', head=head, length=6*v.bytesPerCluster+111) 357 if v.fsinfo: 358 v.fsinfo.allocate(5) 359 360 clusters = v.fat.find(5) 361 head = v.fat.chain(clusters) 362 v.root().mkfile('FILE2', head=head, length=4*v.bytesPerCluster+222) 363 v.fat[clusters[2]] = CLUST_RSRVD 364 if v.fsinfo: 365 v.fsinfo.allocate(5) 366 367 clusters = v.fat.find(5) 368 head = v.fat.chain(clusters) 369 v.root().mkfile('FILE3', head=head, length=4*v.bytesPerCluster+333) 370 v.fat[clusters[2]] = 1 371 if v.fsinfo: 372 v.fsinfo.allocate(5) 373 374 clusters = v.fat.find(5) 375 head = v.fat.chain(clusters) 376 v.root().mkfile('FILE4', head=head, length=4*v.bytesPerCluster+44) 377 v.fat[clusters[2]] = clusters[1] 378 if v.fsinfo: 379 v.fsinfo.allocate(5) 380 381 v.root().mkfile('FILE5', head=CLUST_FREE, length=4*v.bytesPerCluster+55) 382 383 v.root().mkfile('FILE6', head=CLUST_BAD, length=4*v.bytesPerCluster+66) 384 385 v.root().mkfile('FILE7', head=CLUST_RSRVD-1, length=4*v.bytesPerCluster+77) 386 387 head = v.allocate(5) 388 v.root().mkfile('FOO', head=head, length=4*v.bytesPerCluster+99) 389 v.root().mkfile('FILE8', head=head, length=4*v.bytesPerCluster+88) 390 391 # Free the middle cluster of FILE1 now that we've finished allocating 392 v.fat[to_free] = CLUST_FREE 393 394 v.flush() 395 del v 396 f.close() 397 del f 398 399 try: 400 launch([fsck, '-n', disk]) 401 except LaunchError: 402 pass 403 launch([fsck, '-y', disk]) 404 launch(['/sbin/fsck_msdos', '-n', disk]) 405 406# 407# Make directories whose starting cluster number is free/bad/reserved/out of range 408# DIR1 start cluster is free 409# DIR2 start cluster is reserved 410# DIR3 start cluster is bad 411# DIR4 start cluster is EOF 412# DIR5 start cluster is 1 413# DIR6 start cluster is one more than max valid cluster 414# 415def dir_bad_start(disk, fsck, newfs, newfs_opts): 416 def mkdir(parent, name, head): 417 bytes = make_long_dirent(name, ATTR_DIRECTORY, head=head) 418 slots = len(bytes)/32 419 slot = parent.find_slots(slots, grow=True) 420 parent.write_slots(slot, bytes) 421 422 launch([newfs]+newfs_opts+[disk]) 423 424 f = file(disk, "r+") 425 v = msdosfs(f) 426 root = v.root() 427 428 mkdir(root, 'DIR1', CLUST_FREE) 429 mkdir(root, 'DIR2', CLUST_RSRVD) 430 mkdir(root, 'DIR3', CLUST_BAD) 431 mkdir(root, 'DIR4', CLUST_EOF) 432 mkdir(root, 'DIR5', 1) 433 mkdir(root, 'DIR6', v.clusters+2) 434 435 v.flush() 436 del v 437 f.close() 438 del f 439 440 try: 441 launch([fsck, '-n', disk]) 442 except LaunchError: 443 pass 444 launch([fsck, '-y', disk]) 445 launch(['/sbin/fsck_msdos', '-n', disk]) 446 447# 448# Root dir's starting cluster number is free/bad/reserved/out of range 449# 450# NOTE: This test is only applicable to FAT32! 451# 452def root_bad_start(disk, fsck, newfs, newfs_opts): 453 def set_root_start(disk, head): 454 dev = file(disk, "r+") 455 dev.seek(0) 456 bytes = dev.read(512) 457 bytes = bytes[0:44] + struct.pack("<I", head) + bytes[48:] 458 dev.seek(0) 459 dev.write(bytes) 460 dev.close() 461 del dev 462 463 launch([newfs]+newfs_opts+[disk]) 464 465 f = file(disk, "r+") 466 v = msdosfs(f) 467 clusters = v.clusters 468 v.flush() 469 del v 470 f.close() 471 del f 472 473 for head in [CLUST_FREE, CLUST_RSRVD, CLUST_BAD, CLUST_EOF, 1, clusters+2]: 474 set_root_start(disk, head) 475 476 try: 477 launch([fsck, '-n', disk]) 478 except LaunchError: 479 pass 480 try: 481 launch([fsck, '-y', disk]) 482 except LaunchError: 483 pass 484 try: 485 launch(['/sbin/fsck_msdos', '-n', disk]) 486 except LaunchError: 487 pass 488 489# 490# Root dir's first cluster is free/bad/reserved 491# 492# NOTE: This test is only applicable to FAT32! 493# 494def root_bad_first_cluster(disk, fsck, newfs, newfs_opts): 495 for link in [CLUST_FREE, CLUST_RSRVD, CLUST_BAD]: 496 launch([newfs]+newfs_opts+[disk]) 497 498 f = file(disk, "r+") 499 v = msdosfs(f) 500 v.fat[v.rootCluster] = link 501 v.flush() 502 del v 503 f.close() 504 del f 505 506 try: 507 launch([fsck, '-n', disk]) 508 except LaunchError: 509 pass 510 launch([fsck, '-y', disk]) 511 launch(['/sbin/fsck_msdos', '-n', disk]) 512 513# 514# Create subdirectories with the following problems: 515# Size (length) field is non-zero 516# "." entry has wrong starting cluster 517# ".." entry start cluster is non-zero, and parent is root 518# ".." entry start cluster is zero, and parent is not root 519# ".." entry start cluster is incorrect 520# 521def dir_size_dots(disk, fsck, newfs, newfs_opts): 522 launch([newfs]+newfs_opts+[disk]) 523 524 f = file(disk, "r+") 525 v = msdosfs(f) 526 root = v.root() 527 528 # Make a couple of directories without any problems 529 child = root.mkdir('CHILD') 530 grand = child.mkdir('GRAND') 531 532 # Directory has non-zero size 533 dir = root.mkdir('BADSIZE', length=666) 534 535 # "." entry has incorrect start cluster 536 dir = root.mkdir('BADDOT') 537 fields = parse_dirent(dir.read_slots(0)) 538 fields['head'] = fields['head'] + 30 539 dir.write_slots(0, make_dirent(**fields)) 540 541 # ".." entry has non-zero start cluster, but parent is root 542 dir = root.mkdir('DOTDOT.NZ') 543 fields = parse_dirent(dir.read_slots(0)) 544 fields['head'] = 47 545 dir.write_slots(0, make_dirent(**fields)) 546 547 # ".." entry has zero start cluster, but parent is not root 548 dir = child.mkdir('DOTDOT.ZER') 549 fields = parse_dirent(dir.read_slots(0)) 550 fields['head'] = 0 551 dir.write_slots(0, make_dirent(**fields)) 552 553 # ".." entry start cluster is incorrect (parent is not root) 554 dir = grand.mkdir('DOTDOT.BAD') 555 fields = parse_dirent(dir.read_slots(0)) 556 fields['head'] = fields['head'] + 30 557 dir.write_slots(0, make_dirent(**fields)) 558 559 v.flush() 560 del v 561 f.close() 562 del f 563 564 try: 565 launch([fsck, '-n', disk]) 566 except LaunchError: 567 pass 568 launch([fsck, '-y', disk]) 569 launch(['/sbin/fsck_msdos', '-n', disk]) 570 571def long_name(disk, fsck, newfs, newfs_opts): 572 launch([newfs]+newfs_opts+[disk]) 573 574 f = file(disk, "r+") 575 v = msdosfs(f) 576 root = v.root() 577 578 # Long name entries (valid or not!) preceding volume label 579 bytes = make_long_dirent('Test1GB', ATTR_VOLUME_ID) 580 root.write_slots(0, bytes) 581 582 # Create a file with a known good long name 583 root.mkfile('The quick brown fox jumped over the lazy dog') 584 585 # Create a file with a known good short name 586 root.mkfile('foo.bar') 587 588 # Create a file with invalid long name entries (bad checksums) 589 bytes = make_long_dirent('Greetings and felicitations my friends', ATTR_ARCHIVE) 590 bytes = bytes[0:-32] + 'HELLO ' + bytes[-21:] 591 assert len(bytes) % 32 == 0 592 slots = len(bytes) / 32 593 slot = root.find_slots(slots) 594 root.write_slots(slot, bytes) 595 596 subdir = root.mkdir('SubDir') 597 598 # Create a file with incomplete long name entries 599 # Missing first (LONG_NAME_LAST) entry 600 bytes = make_long_dirent('To be or not to be', ATTR_ARCHIVE)[32:] 601 slots = len(bytes) / 32 602 slot = subdir.find_slots(slots) 603 subdir.write_slots(slot, bytes) 604 605 # Missing middle (second) long entry 606 bytes = make_long_dirent('A Man a Plan a Canal Panama', ATTR_ARCHIVE) 607 bytes = bytes[:32] + bytes[64:] 608 slots = len(bytes) / 32 609 slot = subdir.find_slots(slots) 610 subdir.write_slots(slot, bytes) 611 612 # Missing last long entry 613 bytes = make_long_dirent('We the People in order to form a more perfect union', ATTR_ARCHIVE) 614 bytes = bytes[0:-64] + bytes[-32:] 615 slots = len(bytes) / 32 616 slot = subdir.find_slots(slots) 617 subdir.write_slots(slot, bytes) 618 619 subdir = root.mkdir('Bad Orders') 620 621 # Bad order value: first 622 bytes = make_long_dirent('One is the loneliest number', ATTR_ARCHIVE) 623 bytes = chr(ord(bytes[0])+7) + bytes[1:] 624 slots = len(bytes) / 32 625 slot = subdir.find_slots(slots) 626 subdir.write_slots(slot, bytes) 627 628 # Bad order value: middle 629 bytes = make_long_dirent('It takes two to tango or so they say', ATTR_ARCHIVE) 630 bytes = bytes[:32] + chr(ord(bytes[32])+7) + bytes[33:] 631 slots = len(bytes) / 32 632 slot = subdir.find_slots(slots) 633 subdir.write_slots(slot, bytes) 634 635 # Bad order value: last 636 bytes = make_long_dirent('Threes Company becomes Threes A Crowd', ATTR_ARCHIVE) 637 bytes = bytes[:-64] + chr(ord(bytes[-64])+7) + bytes[-63:] 638 slots = len(bytes) / 32 639 slot = subdir.find_slots(slots) 640 subdir.write_slots(slot, bytes) 641 642 # Long name entries (valid or not, with no short entry) at end of directory 643 bytes = make_long_dirent('Four score and seven years ago', ATTR_ARCHIVE) 644 bytes = bytes[0:-32] # Remove the short name entry 645 assert len(bytes) % 32 == 0 646 slots = len(bytes) / 32 647 slot = root.find_slots(slots) 648 root.write_slots(slot, bytes) 649 650 v.flush() 651 del v 652 f.close() 653 del f 654 655 try: 656 launch([fsck, '-n', disk]) 657 except LaunchError: 658 pass 659 launch([fsck, '-y', disk]) 660 launch(['/sbin/fsck_msdos', '-n', disk]) 661 662def past_end_of_dir(disk, fsck, newfs, newfs_opts): 663 launch([newfs]+newfs_opts+[disk]) 664 665 f = file(disk, "r+") 666 v = msdosfs(f) 667 root = v.root() 668 669 subdir = root.mkdir('SubDir') 670 subdir.mkfile('Good Sub File') 671 root.mkfile('Good Root File') 672 673 # Make an entry that will be replaced by end-of-directory 674 slotEOF = root.find_slots(1) 675 root.mkfile('EOF') 676 677 # Make some valid file entries past end of directory 678 root.mkfile('BADFILE') 679 root.mkdir('Bad Dir') 680 root.mkfile('Bad File 2') 681 682 # Overwrite 'EOF' entry with end-of-directory marker 683 root.write_slots(slotEOF, '\x00' * 32) 684 685 # Make an entry that will be replaced by end-of-directory 686 slotEOF = subdir.find_slots(1) 687 subdir.mkfile('EOF') 688 689 # Make some valid file entries past end of directory 690 subdir.mkfile('BADFILE') 691 subdir.mkdir('Bad Dir') 692 subdir.mkfile('Bad File 2') 693 694 # Overwrite 'EOF' entry with end-of-directory marker 695 subdir.write_slots(slotEOF, '\x00' * 32) 696 697 v.flush() 698 del v 699 f.close() 700 del f 701 702 try: 703 launch([fsck, '-n', disk]) 704 except LaunchError: 705 pass 706 launch([fsck, '-y', disk]) 707 launch(['/sbin/fsck_msdos', '-n', disk]) 708 709# 710# Stomp the first two FAT entries. 711# 712def fat_bad_0_or_1(disk, fsck, newfs, newfs_opts): 713 launch([newfs]+newfs_opts+[disk]) 714 715 f = file(disk, "r+") 716 v = msdosfs(f) 717 718 v.fat[0] = 0 719 v.fat[1] = 1 720 721 v.flush() 722 del v 723 f.close() 724 del f 725 726 try: 727 launch([fsck, '-n', disk]) 728 except LaunchError: 729 pass 730 launch([fsck, '-y', disk]) 731 launch(['/sbin/fsck_msdos', '-n', disk]) 732 733# 734# Mark the volume dirty, and cause some minor damage (orphan clusters). 735# Make sure the volume gets marked clean afterwards. 736# 737def fat_mark_clean_corrupt(disk, fsck, newfs, newfs_opts): 738 launch([newfs]+newfs_opts+[disk]) 739 740 f = file(disk, "r+") 741 v = msdosfs(f) 742 743 # Mark the volume "dirty" by clearing the "clean" bit. 744 if v.type == 32: 745 v.fat[1] = v.fat[1] & 0x07FFFFFF 746 else: 747 v.fat[1] = v.fat[1] & 0x7FFF 748 749 # Allocate some clusters, so there is something to repair. 750 v.allocate(3) 751 752 v.flush() 753 del v 754 f.close() 755 del f 756 757 try: 758 launch([fsck, '-n', disk]) 759 except LaunchError: 760 pass 761 launch([fsck, '-y', disk]) 762 763 f = file(disk, "r") 764 v = msdosfs(f) 765 766 # Make sure the "clean" bit is now set. 767 if v.type == 32: 768 clean = v.fat[1] & 0x08000000 769 else: 770 clean = v.fat[1] & 0x8000 771 if not clean: 772 raise RuntimeError("Volume still dirty!") 773 774 v.flush() 775 del v 776 f.close() 777 del f 778 779 launch(['/sbin/fsck_msdos', '-n', disk]) 780 781# 782# Mark the volume dirty (with no corruption). 783# Make sure the volume gets marked clean afterwards. 784# Make sure the exit status is 0, even with "-n". 785# 786def fat_mark_clean_ok(disk, fsck, newfs, newfs_opts): 787 launch([newfs]+newfs_opts+[disk]) 788 789 f = file(disk, "r+") 790 v = msdosfs(f) 791 792 # Mark the volume "dirty" by clearing the "clean" bit. 793 if v.type == 32: 794 v.fat[1] = v.fat[1] & 0x07FFFFFF 795 else: 796 v.fat[1] = v.fat[1] & 0x7FFF 797 798 v.flush() 799 del v 800 f.close() 801 del f 802 803 # Make sure that we ask the user to mark the disk clean, but don't return 804 # a non-zero exit status if the user declines. 805 stdout, stderr = launch([fsck, '-n', disk], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 806 assert "\nMARK FILE SYSTEM CLEAN? no\n" in stdout 807 assert "\n***** FILE SYSTEM IS LEFT MARKED AS DIRTY *****\n" in stdout 808 809 stdout, stderr = launch([fsck, '-y', disk], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 810 assert "\nMARK FILE SYSTEM CLEAN? yes\n" in stdout 811 assert "\nMARKING FILE SYSTEM CLEAN\n" in stdout 812 813 f = file(disk, "r") 814 v = msdosfs(f) 815 816 # Make sure the "clean" bit is now set. 817 if v.type == 32: 818 clean = v.fat[1] & 0x08000000 819 else: 820 clean = v.fat[1] & 0x8000 821 if not clean: 822 raise RuntimeError("Volume still dirty!") 823 824 v.flush() 825 del v 826 f.close() 827 del f 828 829 launch(['/sbin/fsck_msdos', '-n', disk]) 830 831# 832# Make a file whose physical size is 4GB. The logical size is 4GB-100. 833# This is actually NOT corrupt; it's here to verify that fsck_msdos does not 834# try to truncate the file due to overflow of the physical size. [4988133] 835# 836def file_4GB(disk, fsck, newfs, newfs_opts): 837 launch([newfs]+newfs_opts+[disk]) 838 839 # 840 # Create a file whose size is 4GB-100. That means its physical size will 841 # be rounded up to the next multiple of the cluster size, meaning the 842 # physical size will be 4GB. 843 # 844 print "# Creating a 4GiB file. This may take some time." 845 f = file(disk, "r+") 846 v = msdosfs(f) 847 four_GB = 4*1024*1024*1024 848 clusters = four_GB / v.bytesPerCluster 849 head = v.allocate(clusters) 850 v.root().mkfile('4GB', head=head, length=four_GB-100) 851 852 v.flush() 853 del v 854 f.close() 855 del f 856 857 launch([fsck, '-n', disk]) 858 859# 860# Make a file with excess clusters allocated: over 4GB worth of clusters 861# 862# TODO: What combination of files do we want to test with? 863# TODO: A smallish logical size 864# TODO: A logical size just under 4GB 865# TODO: Cross-linked files? 866# TODO: Cross linked beyond 4GB? 867# TODO: Cross linked before 4GB? 868# 869def file_4GB_excess_clusters(disk, fsck, newfs, newfs_opts): 870 launch([newfs]+newfs_opts+[disk]) 871 872 # 873 # Create files with too many clusters for their size 874 # 875 print "# Creating a 4GiB+ file. This may take some time." 876 f = file(disk, "r+") 877 v = msdosfs(f) 878 four_GB = 4*1024*1024*1024 879 clusters = four_GB / v.bytesPerCluster 880 head=v.allocate(clusters+7) 881 v.root().mkfile('FOO', head=head, length=5*v.bytesPerCluster-100) 882 head=v.allocate(clusters+3) 883 v.root().mkfile('BAR', head=head, length=four_GB-30) 884 885 v.flush() 886 del v 887 f.close() 888 del f 889 890 # TODO: Need a better way to assert that the disk is corrupt to start with 891 try: 892 launch([fsck, '-n', disk]) 893 except LaunchError: 894 pass 895 launch([fsck, '-y', disk]) 896 launch([fsck, '-n', disk]) 897 898# 899# Test the "-q" ("quick") option which reports whether the "dirty" flag in 900# the FAT has been set. The dirty flag is only defined for FAT16 and FAT32. 901# For FAT12, we actually do a full verify of the volume and return that the 902# volume is clean if it has no problems, dirty if a problem was detected. 903# 904# NOTE: Assumes newfs_opts[1] is "12", "16" or "32" to indicate which FAT 905# type is being tested. 906# 907def test_quick(disk, fsck, newfs, newfs_opts): 908 assert newfs_opts[1] in ["12", "16", "32"] 909 launch([newfs]+newfs_opts+[disk]) 910 911 # Try a quick check of a volume that is clean 912 launch([fsck, '-q', disk]) 913 914 # Make the volume dirty 915 f = file(disk, "r+") 916 v = msdosfs(f) 917 if newfs_opts[1] in ["16", "32"]: 918 if newfs_opts[1] == "16": 919 v.fat[1] &= 0x7FFF 920 else: 921 v.fat[1] &= 0x07FFFFFF 922 else: 923 # Corrupt a FAT12 volume so that it looks dirty. 924 # Allocate some clusters, so there is something to repair. 925 v.allocate(3) 926 v.flush() 927 del v 928 f.close() 929 del f 930 931 # Quick check a dirty volume 932 try: 933 launch([fsck, '-q', disk]) 934 except LaunchError: 935 pass 936 else: 937 raise FailureExpected("Volume not dirty?") 938 939# 940# Test the "-M" (memory limit) option. This just tests the argument parsing. 941# It does not verify that fsck_msdos actually limits its memory usage. 942# 943def test_maxmem(disk, fsck, newfs, newfs_opts): 944 launch([newfs]+newfs_opts+[disk]) 945 launch([fsck, '-M', '1m', disk]) 946 launch([fsck, '-M', '900k', disk]) 947 948# 949# Test several combinations of bad arguments. 950# 951def test_bad_args(disk, fsck, newfs, newfs_opts): 952 launch([newfs]+newfs_opts+[disk]) 953 954 try: 955 launch([fsck, '-M', 'foo', disk]) 956 except LaunchError: 957 pass 958 else: 959 raise FailureExpected("Expected bad argument: -M foo") 960 961 try: 962 launch([fsck, '-p', '-M', 'foo', disk]) 963 except LaunchError: 964 pass 965 else: 966 raise FailureExpected("Expected bad argument: -p -M foo") 967 968 try: 969 launch([fsck, '-M', '1x', disk]) 970 except LaunchError: 971 pass 972 else: 973 raise FailureExpected("Expected bad argument: -M 1x") 974 975 try: 976 launch([fsck, '-z', disk]) 977 except LaunchError: 978 pass 979 else: 980 raise FailureExpected("Expected bad argument: -z") 981 982 try: 983 launch([fsck]) 984 except LaunchError: 985 pass 986 else: 987 raise FailureExpected("Expected usage (no disk given)") 988 989# 990# Test several cases of bad values in the boot sector. 991# Assumes testing on a disk with 512 bytes per sector. 992# These are all fatal, so don't try repairing. 993# 994def test_boot_sector(disk, fsck, newfs, newfs_opts): 995 # Corrupt the jump instruction 996 def bad_jump(bytes): 997 return 'H+' + bytes[2:] 998 999 # Corrupt the sector size (set it to 0x03FF) 1000 def bad_sector_size(bytes): 1001 return bytes[0:11] + '\xff\x03' + bytes[13:] 1002 1003 # Corrupt the sectors per cluster value 1004 def bad_sec_per_clust(bytes): 1005 return bytes[0:13] + '\x07' + bytes[14:] 1006 1007 for func, reason in [(bad_jump,"Bad boot jump"), 1008 (bad_sector_size, "Bad sector size"), 1009 (bad_sec_per_clust, "Bad sectors per cluster")]: 1010 launch([newfs]+newfs_opts+[disk]) 1011 with open(disk, "r+") as f: 1012 bytes = f.read(512) 1013 f.seek(0) 1014 bytes = func(bytes) 1015 f.write(bytes) 1016 try: 1017 launch([fsck, '-n', disk]) 1018 except LaunchError: 1019 pass 1020 else: 1021 raise FailureExpected(reason) 1022 1023# 1024# Test several cases of bad values in the boot sector (FAT32 only). 1025# These are all fatal, so don't try repairing. 1026# 1027def test_boot_fat32(disk, fsck, newfs, newfs_opts): 1028 # Non-zero number of root directory entries 1029 def bad_root_count(bytes): 1030 return bytes[0:17] + '\x00\x02' + bytes[19:] 1031 1032 # Non-zero file system version 1033 def bad_version(bytes): 1034 return bytes[0:42] + '\x00\x01' + bytes[44:] 1035 1036 for func, reason in [(bad_root_count,"Bad root entry count"), 1037 (bad_version, "Bad filesystem version")]: 1038 launch([newfs]+newfs_opts+[disk]) 1039 with open(disk, "r+") as f: 1040 bytes = f.read(512) 1041 f.seek(0) 1042 bytes = func(bytes) 1043 f.write(bytes) 1044 try: 1045 launch([fsck, '-n', disk]) 1046 except LaunchError: 1047 pass 1048 else: 1049 raise FailureExpected(reason) 1050 1051# 1052# Test several cases of bad values in the boot sector (FAT32 only). 1053# 1054def test_fsinfo(disk, fsck, newfs, newfs_opts): 1055 def bad_leading_sig(bytes): 1056 return 'RRAA' + bytes[4:] 1057 1058 def bad_sig2(bytes): 1059 return bytes[0:484] + 'rraa' + bytes[488:] 1060 1061 def bad_trailing_sig(bytes): 1062 return bytes[0:508] + '\xff\x00\xaa\x55' + bytes[512:] 1063 1064 def bad_free_count(bytes): 1065 return bytes[0:488] + '\xfe\xed\xfa\xce' + bytes[492:] 1066 1067 # Figure out where the FSInfo sector ended up 1068 launch([newfs]+newfs_opts+[disk]) 1069 with open(disk, "r+") as f: 1070 bytes = f.read(512) 1071 fsinfo = ord(bytes[48]) + 256 * ord(bytes[49]) 1072 1073 # Test each of the FSInfo corruptions 1074 for func, reason in [(bad_leading_sig, "Bad leading signature"), 1075 (bad_sig2, "Bad structure signature"), 1076 (bad_trailing_sig, "Bad trailing signature"), 1077 (bad_free_count, "Bad free cluster count")]: 1078 launch([newfs]+newfs_opts+[disk]) 1079 with open(disk, "r+") as f: 1080 f.seek(fsinfo * 512) 1081 bytes = f.read(512) 1082 f.seek(fsinfo * 512) 1083 bytes = func(bytes) 1084 f.write(bytes) 1085 launch([fsck, '-y', disk]) 1086 launch([fsck, '-n', disk]) 1087 1088# 1089# Test when the FAT has too few sectors for the number of clusters. 1090# 1091# NOTE: Relies on the fact that newfs_msdos places a FAT32 root 1092# directory in cluster #2 (immediately following the FATs). 1093# 1094def fat_too_small(disk, fsck, newfs, newfs_opts): 1095 launch([newfs]+newfs_opts+[disk]) 1096 1097 with open(disk, "r+") as f: 1098 bytes = f.read(512) 1099 numFATs = ord(bytes[16]) 1100 reserved = ord(bytes[14]) + 256 * ord(bytes[15]) 1101 1102 # Decrement the number of sectors per FAT 1103 if bytes[22:24] != '\x00\x00': 1104 fat_sectors = struct.unpack("<H", bytes[22:24])[0] 1105 bytes = bytes[0:22] + struct.pack("<H", fat_sectors - 1) + bytes[24:] 1106 else: 1107 fat_sectors = struct.unpack("<I", bytes[36:40])[0] 1108 bytes = bytes[0:36] + struct.pack("<I", fat_sectors - 1) + bytes[40:] 1109 f.seek(0) 1110 f.write(bytes) 1111 1112 # Copy the root directory from old location to new location 1113 f.seek(512 * (reserved + numFATs * fat_sectors)) 1114 bytes = f.read(65536) 1115 f.seek(512 * (reserved + numFATs * fat_sectors - numFATs)) 1116 f.write(bytes) 1117 1118 # NOTE: FAT too small is NOT a fatal error 1119 launch([fsck, '-y', disk]) # Need a way to test for expected output 1120 launch([fsck, '-n', disk]) 1121 1122# 1123# When run as a script, run the test suite. 1124# 1125# Usage: 1126# python test_fsck.py [<fsck_msdos> [<tmp_dir>]] 1127# 1128if __name__ == '__main__': 1129 # 1130 # Set up defaults 1131 # 1132 dir = '/tmp' 1133 fsck = 'fsck_msdos' 1134 newfs = 'newfs_msdos' 1135 1136 if len(sys.argv) > 1: 1137 fsck = sys.argv[1] 1138 if len(sys.argv) > 2: 1139 dir = sys.argv[2] 1140 if len(sys.argv) > 3: 1141 print "%s: Too many arguments!" % sys.argv[0] 1142 print "Usage: %s [<fsck_msdos> [<tmp_dir>]]" 1143 sys.exit(1) 1144 1145 # 1146 # Run the test suite 1147 # 1148 test_fat32(dir, fsck, newfs) 1149 test_fat16(dir, fsck, newfs) 1150 test_fat12(dir, fsck, newfs) 1151 1152 print "\nSuccess!" 1153