diff options
Diffstat (limited to 'util/gem5img.py')
-rwxr-xr-x | util/gem5img.py | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/util/gem5img.py b/util/gem5img.py new file mode 100755 index 000000000..f02ccb105 --- /dev/null +++ b/util/gem5img.py @@ -0,0 +1,372 @@ +#!/usr/bin/python +# +# gem5img.py +# Script for managing a gem5 disk image. +# + +from optparse import OptionParser +import os +from os import environ as env +import string +from subprocess import CalledProcessError, Popen, PIPE, STDOUT +from sys import exit, argv + + +# Some constants. +MaxLBACylinders = 16383 +MaxLBAHeads = 16 +MaxLBASectors = 63 +MaxLBABlocks = MaxLBACylinders * MaxLBAHeads * MaxLBASectors + +BlockSize = 512 +MB = 1024 * 1024 + +# Setup PATH to look in the sbins. +env['PATH'] += ':/sbin:/usr/sbin' + +# Whether to print debug output. +debug = False + +# Figure out cylinders, heads and sectors from a size in blocks. +def chsFromSize(sizeInBlocks): + if sizeInBlocks >= MaxLBABlocks: + sizeInMBs = (sizeInBlocks * BlockSize) / MB + print '%d MB is too big for LBA, truncating file.' % sizeInMBs + return (MaxLBACylinders, MaxLBAHeads, MaxLBASectors) + + sectors = sizeInBlocks + if sizeInBlocks > 63: + sectors = 63 + + headSize = sizeInBlocks / sectors + heads = 16 + if headSize < 16: + heads = sizeInBlocks + + cylinders = sizeInBlocks / (sectors * heads) + + return (cylinders, heads, sectors) + + +# Figure out if we should use sudo. +def needSudo(): + if not hasattr(needSudo, 'notRoot'): + needSudo.notRoot = (os.geteuid() != 0) + if needSudo.notRoot: + print 'You are not root. Using sudo.' + return needSudo.notRoot + +# Run an external command. +def runCommand(command, inputVal=''): + print "%>", ' '.join(command) + proc = Popen(command, stdin=PIPE) + proc.communicate(inputVal) + return proc.returncode + +# Run an external command and capture its output. This is intended to be +# used with non-interactive commands where the output is for internal use. +def getOutput(command, inputVal=''): + global debug + if debug: + print "%>", ' '.join(command) + proc = Popen(command, stderr=STDOUT, + stdin=PIPE, stdout=PIPE) + (out, err) = proc.communicate(inputVal) + return (out, proc.returncode) + +# Run a command as root, using sudo if necessary. +def runPriv(command, inputVal=''): + realCommand = command + if needSudo(): + realCommand = [findProg('sudo')] + command + return runCommand(realCommand, inputVal) + +def privOutput(command, inputVal=''): + realCommand = command + if needSudo(): + realCommand = [findProg('sudo')] + command + return getOutput(realCommand, inputVal) + +# Find the path to a program. +def findProg(program, cleanupDev=None): + (out, returncode) = getOutput(['which', program]) + if returncode != 0: + if cleanupDev: + cleanupDev.destroy() + exit("Unable to find program %s, check your PATH variable." % program) + return string.strip(out) + +class LoopbackDevice(object): + def __init__(self, devFile=None): + self.devFile = devFile + def __str__(self): + return str(self.devFile) + + def setup(self, fileName, offset=False): + assert not self.devFile + (out, returncode) = privOutput([findProg('losetup'), '-f']) + if returncode != 0: + print out + return returncode + self.devFile = string.strip(out) + command = [findProg('losetup'), self.devFile, fileName] + if offset: + off = findPartOffset(self.devFile, fileName, 0) + command = command[:1] + \ + ["-o", "%d" % off] + \ + command[1:] + return runPriv(command) + + def destroy(self): + assert self.devFile + returncode = runPriv([findProg('losetup'), '-d', self.devFile]) + self.devFile = None + return returncode + +def findPartOffset(devFile, fileName, partition): + # Attach a loopback device to the file so we can use sfdisk on it. + dev = LoopbackDevice() + dev.setup(fileName) + # Dump the partition information. + command = [findProg('sfdisk'), '-d', dev.devFile] + (out, returncode) = privOutput(command) + if returncode != 0: + print out + exit(returncode) + lines = out.splitlines() + # Make sure the first few lines of the output look like what we expect. + assert(lines[0][0] == '#') + assert(lines[1] == 'unit: sectors') + assert(lines[2] == '') + # This line has information about the first partition. + chunks = lines[3].split() + # The fourth chunk is the offset of the partition in sectors followed by + # a comma. We drop the comma and convert that to an integer. + sectors = string.atoi(chunks[3][:-1]) + # Free the loopback device and return an answer. + dev.destroy() + return sectors * BlockSize + +def mountPointToDev(mountPoint): + (mountTable, returncode) = getOutput([findProg('mount')]) + if returncode != 0: + print mountTable + exit(returncode) + mountTable = mountTable.splitlines() + for line in mountTable: + chunks = line.split() + if os.path.samefile(chunks[2], mountPoint): + return LoopbackDevice(chunks[0]) + return None + + +# Commands for the gem5img.py script +commands = {} +commandOrder = [] + +class Command(object): + def addOption(self, *args, **kargs): + self.parser.add_option(*args, **kargs) + + def __init__(self, name, description, posArgs): + self.name = name + self.description = description + self.func = None + self.posArgs = posArgs + commands[self.name] = self + commandOrder.append(self.name) + usage = 'usage: %prog [options]' + posUsage = '' + for posArg in posArgs: + (argName, argDesc) = posArg + usage += ' %s' % argName + posUsage += '\n %s: %s' % posArg + usage += posUsage + self.parser = OptionParser(usage=usage, description=description) + self.addOption('-d', '--debug', dest='debug', action='store_true', + help='Verbose output.') + + def parseArgs(self, argv): + (self.options, self.args) = self.parser.parse_args(argv[2:]) + if len(self.args) != len(self.posArgs): + self.parser.error('Incorrect number of arguments') + global debug + if self.options.debug: + debug = True + + def runCom(self): + if not self.func: + exit('Unimplemented command %s!' % self.name) + self.func(self.options, self.args) + + +# A command which prepares an image with an partition table and an empty file +# system. +initCom = Command('init', 'Create an image with an empty file system.', + [('file', 'Name of the image file.'), + ('mb', 'Size of the file in MB.')]) +initCom.addOption('-t', '--type', dest='fstype', action='store', + default='ext2', + help='Type of file system to use. Appended to mkfs.') + +# A command to mount the first partition in the image. +mountCom = Command('mount', 'Mount the first partition in the disk image.', + [('file', 'Name of the image file.'), + ('mount point', 'Where to mount the image.')]) + +def mountComFunc(options, args): + (path, mountPoint) = args + if not os.path.isdir(mountPoint): + print "Mount point %s is not a directory." % mountPoint + + dev = LoopbackDevice() + if dev.setup(path, offset=True) != 0: + exit(1) + + if runPriv([findProg('mount'), str(dev), mountPoint]) != 0: + dev.destroy() + exit(1) + +mountCom.func = mountComFunc + +# A command to unmount the first partition in the image. +umountCom = Command('umount', 'Unmount the first partition in the disk image.', + [('mount point', 'What mount point to unmount.')]) + +def umountComFunc(options, args): + (mountPoint,) = args + if not os.path.isdir(mountPoint): + print "Mount point %s is not a directory." % mountPoint + exit(1) + + dev = mountPointToDev(mountPoint) + if not dev: + print "Unable to find mount information for %s." % mountPoint + + # Unmount the loopback device. + if runPriv([findProg('umount'), mountPoint]) != 0: + exit(1) + + # Destroy the loopback device. + dev.destroy() + +umountCom.func = umountComFunc + + +# A command to create an empty file to hold the image. +newCom = Command('new', 'File creation part of "init".', + [('file', 'Name of the image file.'), + ('mb', 'Size of the file in MB.')]) + +def newImage(file, mb): + (cylinders, heads, sectors) = chsFromSize((mb * MB) / BlockSize) + size = cylinders * heads * sectors * BlockSize + + # We lseek to the end of the file and only write one byte there. This + # leaves a "hole" which many file systems are smart enough not to actually + # store to disk and which is defined to read as zero. + fd = os.open(file, os.O_WRONLY | os.O_CREAT) + os.lseek(fd, size - 1, os.SEEK_SET) + os.write(fd, '\0') + +def newComFunc(options, args): + (file, mb) = args + mb = string.atoi(mb) + newImage(file, mb) + + +newCom.func = newComFunc + +# A command to partition the image file like a raw disk device. +partitionCom = Command('partition', 'Partition part of "init".', + [('file', 'Name of the image file.')]) + +def partition(dev, cylinders, heads, sectors): + # Use fdisk to partition the device + comStr = '0,\n;\n;\n;\n' + return runPriv([findProg('sfdisk'), '--no-reread', '-D', \ + '-C', "%d" % cylinders, \ + '-H', "%d" % heads, \ + '-S', "%d" % sectors, \ + str(dev)], inputVal=comStr) + +def partitionComFunc(options, args): + (path,) = args + + dev = LoopbackDevice() + if dev.setup(path) != 0: + exit(1) + + # Figure out the dimensions of the file. + size = os.path.getsize(path) + if partition(dev, *chsFromSize(size / BlockSize)) != 0: + dev.destroy() + exit(1) + + dev.destroy() + +partitionCom.func = partitionComFunc + +# A command to format the first partition in the image. +formatCom = Command('format', 'Formatting part of "init".', + [('file', 'Name of the image file.')]) +formatCom.addOption('-t', '--type', dest='fstype', action='store', + default='ext2', + help='Type of file system to use. Appended to mkfs.') + +def formatImage(dev, fsType): + return runPriv([findProg('mkfs.%s' % fsType, dev), str(dev)]) + +def formatComFunc(options, args): + (path,) = args + + dev = LoopbackDevice() + if dev.setup(path, offset=True) != 0: + exit(1) + + # Format the device. + if formatImage(dev, options.fstype) != 0: + dev.destroy() + exit(1) + + dev.destroy() + +formatCom.func = formatComFunc + +def initComFunc(options, args): + (path, mb) = args + mb = string.atoi(mb) + newImage(path, mb) + dev = LoopbackDevice() + if dev.setup(path) != 0: + exit(1) + size = os.path.getsize(path) + if partition(dev, *chsFromSize((mb * MB) / BlockSize)) != 0: + dev.destroy() + exit(1) + dev.destroy() + if dev.setup(path, offset=True) != 0: + exit(1) + if formatImage(dev, options.fstype) != 0: + dev.destroy() + exit(1) + dev.destroy() + +initCom.func = initComFunc + + +# Figure out what command was requested and execute it. +if len(argv) < 2 or argv[1] not in commands: + print 'Usage: %s [command] <command arguments>' + print 'where [command] is one of ' + for name in commandOrder: + command = commands[name] + print ' %s: %s' % (command.name, command.description) + print 'Watch for orphaned loopback devices and delete them with' + print 'losetup -d. Mounted images will belong to root, so you may need' + print 'to use sudo to modify their contents.' + exit(1) + +command = commands[argv[1]] +command.parseArgs(argv) +command.runCom() |