373 lines
11 KiB
Python
373 lines
11 KiB
Python
|
#!/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()
|