Different tack: Just install boot blocks

This isn't done, but it reflects largely starting over.  The goal right
now is to install the boot blocks.  To do this we need to:

* Find out if we need to install anything
* Download the disk image archive
* Extract the disk image from the archive
* Mount the image
* Copy the files out of the image
* Unmount the image
* Patch the files if they need patching
* Verify the files are patched correctly

The branching possibilities for installing the disk images are:

1. That the image is currently always a .sea.bin file.  In future, it
   might not be!  After all, the other six disk images are available
   multiple places and they're not stored as self-extracting images in
   MacBinary format.

2. That there are three different ways I know of to extract the HFS disk
   image:  Mount it as a loop filesystem (Linux), attach it as a disk
   image file (OS X), or extract its contents using hfsutils which
   should be able to run just about anywhere.  Of course hfsutils has an
   additional branch in that hfsutils "sanitizes" the files you copy out
   of it for you in ways we don't want, so we need to know the file
   names it uses so we can change them back to OS X expectations.

3. How are we installing these files?  If we're installing them for
   netatalk, we need to rename a file.  If just to a UNIX path, that's
   something different.  If there was some reason to package them in
   ProDOS format, again, we need to do something different.

At this time, since I'm developing this on a Mac, I'm assuming .sea.bin
with an 800k image, I'm implementing hdiutil to mount the image, and
I'll implement the UNIX naming first (so as Mac/Linux mount the files.)

Next I'll implement renaming for netatalk and hfsutils.  I'll check it
out on the pi and implement the Linux filesystem mounting probably last,
then clean it up to behave more like Python is supposed to if I can
before I start on the GS/OS install itself.

I need a better MacBinary unpacker than unar gives me.  The way that
works right now is just clumsy.
This commit is contained in:
T. Joseph Carter 2015-11-08 20:48:13 -08:00
parent 1796676df1
commit d9c4af34e0

View File

@ -2,6 +2,8 @@
import os, sys, subprocess
import tempfile
import hashlib
import xml.etree.cElementTree as ET
gsosDir = '/media/A2SHARED/FILES'
imagesDir = gsosDir + '/GSOS.INSTALLER/IMAGES'
@ -20,6 +22,38 @@ adtproDir = commDir + '/ADTPRO'
disk7_filename = 'Disk_7_of_7-Apple_II_Setup.sea.bin'
disk7_url = 'http://archive.org/download/download.info.apple.com.2012.11/download.info.apple.com.2012.11.zip/download.info.apple.com%2FApple_Support_Area%2FApple_Software_Updates%2FEnglish-North_American%2FApple_II%2FApple_IIGS_System_6.0.1%2F' + disk7_filename
a2boot_files = [
{
'unix': 'Apple ::e Boot Blocks',
'hfsutils': 'Apple_--e_Boot_Blocks.bin',
'netatalk': 'Apple :2f:2fe Boot Blocks',
'digest': 'cada362ac2eca3ffa506e9b4e76650ba031e0035',
'digest_patched': '6b7fc12fd118e1cb9e39c7a2b8cc870c844a3bac'
},
{
'unix': 'Basic.System',
'hfsutils': 'Basic.System.bin',
'digest': '4d53424f1451cd2e874cf792dbdc8cc6735dcd36'
},
{
'unix': 'ProDOS16 Boot Blocks',
'hfsutils': 'ProDOS16_Boot_Blocks.bin',
'digest': 'fab829e82e6662ed6aab119ad18e16ded7d43cda',
},
{
'unix': 'ProDOS16 Image',
'hfsutils': 'ProDOS16_Image.bin',
'digest': 'db4608067b9e7877f45eb557971c4d8c45b46be5',
'digest_patched': '5c35d5533901b292ab7c2f5a3c76cb3113f66085'
},
{
'unix': 'p8',
'hfsutils': 'p8.bin',
'digest': '36c288a5272cf01e0a64eed16786258959118e0e'
}
]
try:
stdin_input = raw_input # Python 2
except NameError:
@ -30,7 +64,6 @@ try:
except ImportError:
import urllib2 as urlrequest # Python 2
# Differing from the shell script in that we explicitly strip the / here
if 'A2SERVER_SCRIPT_URL' in os.environ:
scriptURL = os.environ['A2SERVER_SCRIPT_URL']
@ -40,6 +73,16 @@ if 'A2SERVER_SCRIPT_URL' in os.environ:
else:
scriptURL = 'http://appleii.ivanx.com/a2server'
def sha1file (filename, blocksize=65536):
f = open(filename, "rb")
digest = hashlib.sha1()
buf = f.read(blocksize)
while len(buf) > 0:
digest.update(buf)
buf = f.read(blocksize)
f.close()
return digest.hexdigest()
def download_url(url, filename):
html = urlrequest.urlopen(url)
data = html.read()
@ -51,13 +94,15 @@ def download_url(url, filename):
# self-extracting disk image files. The Unarchiver's unar is able
# to unwrap the MacBinary wrapper for us, but we have to extract the
# disk image oursselves. Fortunately, it's uncompressed.
def extract_800k_sea_bin(wrapper_name, image_name, sea_name = None):
def extract_800k_sea_bin(wrapper_name, image_name, extract_dir, sea_name = None):
# First we need to get rid of the MacBinary wrapper
# FIXME: Can we learn to read MacBinary? I bet we can!
cmdline = ['unar', '-q', '-k', 'skip', wrapper_name]
if not os.path.isfile(wrapper_name):
raise IOError('Archive file "' + wrapper_name + '" does not exist')
cmdline = ['unar', '-q', '-o', extract_dir, '-k', 'skip', wrapper_name]
ret = subprocess.call(cmdline)
if ret != 0:
raise IOError('unar returned with status ' + ret)
raise IOError('unar returned with status %i' % (ret))
# MAYBE we can guess the name?
if sea_name == None:
@ -68,9 +113,14 @@ def extract_800k_sea_bin(wrapper_name, image_name, sea_name = None):
'" doesn\'t end with .sea.bin')
# Do we have the right file?
sea_name = os.path.join(extract_dir, sea_name)
if not os.path.isfile(sea_name):
raise IOError('Expected image archive "' + sea_name + '" does not exist')
# Cowardly refuse to overwrite image_name
if os.path.exists(image_name):
raise IOError('"' + image_name + '" already exists')
# The image starts 84 bytes in, and is exactly 819200 bytes long
with open(sea_name, 'rb') as src, open(image_name, 'wb') as dst:
src.seek(84)
@ -82,8 +132,76 @@ def extract_800k_sea_bin(wrapper_name, image_name, sea_name = None):
os.unlink(sea_name)
os.unlink(wrapper_name)
def install_bootblocks(dir):
pass
def plist_keyvalue(plist_dict, key):
if plist_dict.tag != 'dict':
raise ValueError('not a plist dict')
found = False
for elem in plist_dict:
if found:
return elem
if elem.tag == 'key' and elem.text == key:
found = True
return None
def find_mountpoint(xmlstr):
plistroot = ET.fromstring(xmlstr)
if plistroot.tag != 'plist':
raise ValueError('xmlstr is not an XML-format plist')
if plistroot[0].tag == 'dict':
sys_entities = plist_keyvalue(plistroot[0], 'system-entities')
if sys_entities.tag != 'array':
raise ValueError('expected dict to contain an array')
for child in sys_entities:
if child.tag == 'dict':
mountpoint = plist_keyvalue(child, 'mount-point')
return mountpoint.text
else:
raise ValueError('system-entities should be an array of dict objects')
else:
raise ValueError('Root element is not a dict')
def install_bootblocks(installdir, installtype):
if installtype not in ['unix', 'netatalk']:
raise ValueError('Only basic UNIX and netatalk formats are supported for now')
if not os.path.isdir(installdir):
os.makedirs(installdir, mode=0755)
bootblock_tmp = tempfile.mkdtemp(prefix = "tmp-a2sv-bootblocks.")
unpacked_a2boot = False
for bootfile in a2boot_files:
if installtype == 'unix':
dst = bootfile['unix']
elif installtype == 'netatalk':
dst = bootfile['netatalk'] or bootfile['unix']
if not os.path.isfile(os.path.join(installdir, dst)):
# We need to fetch it
if not unpacked_a2boot:
disk7_file = os.path.join(bootblock_tmp, disk7_filename)
a2setup_img_name = os.path.join(bootblock_tmp, 'A2SETUP.img')
download_url(disk7_url, disk7_file)
# If file is wrapped as .sea.bin (always true for now)
if True:
sea_name = 'Disk 7 of 7-Apple II Setup.sea'
extract_800k_sea_bin(disk7_file, a2setup_img_name, bootblock_tmp, sea_name)
else:
# Implement non .sea.bin version
pass
if os.uname()[0] == 'Darwin':
xmlstr = subprocess.check_output(['hdiutil', 'attach', '-plist',
a2setup_img_name])
mountpoint = find_mountpoint(xmlstr)
print ('Contents of ' + mountpoint)
os.system('ls -l "' + mountpoint + '"')
os.system('hdiutil eject "' + mountpoint + '"')
unpacked_a2boot = True
os.unlink(a2setup_img_name)
os.removedirs(bootblock_tmp)
def do_install():
netboot_tmp = tempfile.mkdtemp(suffix = '.a2server-netboot')
@ -166,8 +284,7 @@ if __name__ == '__main__':
sys.exit(ret)
"""
reply = stdin_input("""
Do you want to set up A2SERVER to be able to boot Apple II
computers over the network? [y] """)
if reply.startswith('y') or reply.startswith('Y') or reply == '':
do_install()
install_bootblocks(os.path.join(os.getcwd(), 'a2boot'), 'unix')
#reply = stdin_input("""\nDo you want to set up A2SERVER to be able to boot Apple II\ncomputers over the network? [y] """)
#if reply.startswith('y') or reply.startswith('Y') or reply == '':
# do_install()