4 # Script for managing a gem5 disk image.
7 from optparse
import OptionParser
9 from os
import environ
as env
11 from subprocess
import CalledProcessError
, Popen
, PIPE
, STDOUT
12 from sys
import exit
, argv
16 MaxLBACylinders
= 16383
19 MaxLBABlocks
= MaxLBACylinders
* MaxLBAHeads
* MaxLBASectors
24 # Setup PATH to look in the sbins.
25 env
['PATH'] += ':/sbin:/usr/sbin'
27 # Whether to print debug output.
30 # Figure out cylinders, heads and sectors from a size in blocks.
31 def chsFromSize(sizeInBlocks
):
32 if sizeInBlocks
>= MaxLBABlocks
:
33 sizeInMBs
= (sizeInBlocks
* BlockSize
) / MB
34 print '%d MB is too big for LBA, truncating file.' % sizeInMBs
35 return (MaxLBACylinders
, MaxLBAHeads
, MaxLBASectors
)
37 sectors
= sizeInBlocks
41 headSize
= sizeInBlocks
/ sectors
46 cylinders
= sizeInBlocks
/ (sectors
* heads
)
48 return (cylinders
, heads
, sectors
)
51 # Figure out if we should use sudo.
53 if not hasattr(needSudo
, 'notRoot'):
54 needSudo
.notRoot
= (os
.geteuid() != 0)
56 print 'You are not root. Using sudo.'
57 return needSudo
.notRoot
59 # Run an external command.
60 def runCommand(command
, inputVal
=''):
61 print "%>", ' '.join(command
)
62 proc
= Popen(command
, stdin
=PIPE
)
63 proc
.communicate(inputVal
)
64 return proc
.returncode
66 # Run an external command and capture its output. This is intended to be
67 # used with non-interactive commands where the output is for internal use.
68 def getOutput(command
, inputVal
=''):
71 print "%>", ' '.join(command
)
72 proc
= Popen(command
, stderr
=STDOUT
,
73 stdin
=PIPE
, stdout
=PIPE
)
74 (out
, err
) = proc
.communicate(inputVal
)
75 return (out
, proc
.returncode
)
77 # Run a command as root, using sudo if necessary.
78 def runPriv(command
, inputVal
=''):
81 realCommand
= [findProg('sudo')] + command
82 return runCommand(realCommand
, inputVal
)
84 def privOutput(command
, inputVal
=''):
87 realCommand
= [findProg('sudo')] + command
88 return getOutput(realCommand
, inputVal
)
90 # Find the path to a program.
91 def findProg(program
, cleanupDev
=None):
92 (out
, returncode
) = getOutput(['which', program
])
96 exit("Unable to find program %s, check your PATH variable." % program
)
97 return string
.strip(out
)
99 class LoopbackDevice(object):
100 def __init__(self
, devFile
=None):
101 self
.devFile
= devFile
103 return str(self
.devFile
)
105 def setup(self
, fileName
, offset
=False):
106 assert not self
.devFile
107 (out
, returncode
) = privOutput([findProg('losetup'), '-f'])
111 self
.devFile
= string
.strip(out
)
112 command
= [findProg('losetup'), self
.devFile
, fileName
]
114 off
= findPartOffset(self
.devFile
, fileName
, 0)
115 command
= command
[:1] + \
116 ["-o", "%d" % off
] + \
118 return runPriv(command
)
122 returncode
= runPriv([findProg('losetup'), '-d', self
.devFile
])
126 def findPartOffset(devFile
, fileName
, partition
):
127 # Attach a loopback device to the file so we can use sfdisk on it.
128 dev
= LoopbackDevice()
130 # Dump the partition information.
131 command
= [findProg('sfdisk'), '-d', dev
.devFile
]
132 (out
, returncode
) = privOutput(command
)
136 lines
= out
.splitlines()
137 # Make sure the first few lines of the output look like what we expect.
138 assert(lines
[0][0] == '#')
139 assert(lines
[1] == 'unit: sectors')
140 assert(lines
[2] == '')
141 # This line has information about the first partition.
142 chunks
= lines
[3].split()
143 # The fourth chunk is the offset of the partition in sectors followed by
144 # a comma. We drop the comma and convert that to an integer.
145 sectors
= string
.atoi(chunks
[3][:-1])
146 # Free the loopback device and return an answer.
148 return sectors
* BlockSize
150 def mountPointToDev(mountPoint
):
151 (mountTable
, returncode
) = getOutput([findProg('mount')])
155 mountTable
= mountTable
.splitlines()
156 for line
in mountTable
:
157 chunks
= line
.split()
158 if os
.path
.samefile(chunks
[2], mountPoint
):
159 return LoopbackDevice(chunks
[0])
163 # Commands for the gem5img.py script
167 class Command(object):
168 def addOption(self
, *args
, **kargs
):
169 self
.parser
.add_option(*args
, **kargs
)
171 def __init__(self
, name
, description
, posArgs
):
173 self
.description
= description
175 self
.posArgs
= posArgs
176 commands
[self
.name
] = self
177 commandOrder
.append(self
.name
)
178 usage
= 'usage: %prog [options]'
180 for posArg
in posArgs
:
181 (argName
, argDesc
) = posArg
182 usage
+= ' %s' % argName
183 posUsage
+= '\n %s: %s' % posArg
185 self
.parser
= OptionParser(usage
=usage
, description
=description
)
186 self
.addOption('-d', '--debug', dest
='debug', action
='store_true',
187 help='Verbose output.')
189 def parseArgs(self
, argv
):
190 (self
.options
, self
.args
) = self
.parser
.parse_args(argv
[2:])
191 if len(self
.args
) != len(self
.posArgs
):
192 self
.parser
.error('Incorrect number of arguments')
194 if self
.options
.debug
:
199 exit('Unimplemented command %s!' % self
.name
)
200 self
.func(self
.options
, self
.args
)
203 # A command which prepares an image with an partition table and an empty file
205 initCom
= Command('init', 'Create an image with an empty file system.',
206 [('file', 'Name of the image file.'),
207 ('mb', 'Size of the file in MB.')])
208 initCom
.addOption('-t', '--type', dest
='fstype', action
='store',
210 help='Type of file system to use. Appended to mkfs.')
212 # A command to mount the first partition in the image.
213 mountCom
= Command('mount', 'Mount the first partition in the disk image.',
214 [('file', 'Name of the image file.'),
215 ('mount point', 'Where to mount the image.')])
217 def mountComFunc(options
, args
):
218 (path
, mountPoint
) = args
219 if not os
.path
.isdir(mountPoint
):
220 print "Mount point %s is not a directory." % mountPoint
222 dev
= LoopbackDevice()
223 if dev
.setup(path
, offset
=True) != 0:
226 if runPriv([findProg('mount'), str(dev
), mountPoint
]) != 0:
230 mountCom
.func
= mountComFunc
232 # A command to unmount the first partition in the image.
233 umountCom
= Command('umount', 'Unmount the first partition in the disk image.',
234 [('mount point', 'What mount point to unmount.')])
236 def umountComFunc(options
, args
):
238 if not os
.path
.isdir(mountPoint
):
239 print "Mount point %s is not a directory." % mountPoint
242 dev
= mountPointToDev(mountPoint
)
244 print "Unable to find mount information for %s." % mountPoint
246 # Unmount the loopback device.
247 if runPriv([findProg('umount'), mountPoint
]) != 0:
250 # Destroy the loopback device.
253 umountCom
.func
= umountComFunc
256 # A command to create an empty file to hold the image.
257 newCom
= Command('new', 'File creation part of "init".',
258 [('file', 'Name of the image file.'),
259 ('mb', 'Size of the file in MB.')])
261 def newImage(file, mb
):
262 (cylinders
, heads
, sectors
) = chsFromSize((mb
* MB
) / BlockSize
)
263 size
= cylinders
* heads
* sectors
* BlockSize
265 # We lseek to the end of the file and only write one byte there. This
266 # leaves a "hole" which many file systems are smart enough not to actually
267 # store to disk and which is defined to read as zero.
268 fd
= os
.open(file, os
.O_WRONLY | os
.O_CREAT
)
269 os
.lseek(fd
, size
- 1, os
.SEEK_SET
)
272 def newComFunc(options
, args
):
278 newCom
.func
= newComFunc
280 # A command to partition the image file like a raw disk device.
281 partitionCom
= Command('partition', 'Partition part of "init".',
282 [('file', 'Name of the image file.')])
284 def partition(dev
, cylinders
, heads
, sectors
):
285 # Use fdisk to partition the device
286 comStr
= '0,\n;\n;\n;\n'
287 return runPriv([findProg('sfdisk'), '--no-reread', '-D', \
288 '-C', "%d" % cylinders
, \
289 '-H', "%d" % heads
, \
290 '-S', "%d" % sectors
, \
291 str(dev
)], inputVal
=comStr
)
293 def partitionComFunc(options
, args
):
296 dev
= LoopbackDevice()
297 if dev
.setup(path
) != 0:
300 # Figure out the dimensions of the file.
301 size
= os
.path
.getsize(path
)
302 if partition(dev
, *chsFromSize(size
/ BlockSize
)) != 0:
308 partitionCom
.func
= partitionComFunc
310 # A command to format the first partition in the image.
311 formatCom
= Command('format', 'Formatting part of "init".',
312 [('file', 'Name of the image file.')])
313 formatCom
.addOption('-t', '--type', dest
='fstype', action
='store',
315 help='Type of file system to use. Appended to mkfs.')
317 def formatImage(dev
, fsType
):
318 return runPriv([findProg('mkfs.%s' % fsType
, dev
), str(dev
)])
320 def formatComFunc(options
, args
):
323 dev
= LoopbackDevice()
324 if dev
.setup(path
, offset
=True) != 0:
328 if formatImage(dev
, options
.fstype
) != 0:
334 formatCom
.func
= formatComFunc
336 def initComFunc(options
, args
):
340 dev
= LoopbackDevice()
341 if dev
.setup(path
) != 0:
343 size
= os
.path
.getsize(path
)
344 if partition(dev
, *chsFromSize((mb
* MB
) / BlockSize
)) != 0:
348 if dev
.setup(path
, offset
=True) != 0:
350 if formatImage(dev
, options
.fstype
) != 0:
355 initCom
.func
= initComFunc
358 # Figure out what command was requested and execute it.
359 if len(argv
) < 2 or argv
[1] not in commands
:
360 print 'Usage: %s [command] <command arguments>'
361 print 'where [command] is one of '
362 for name
in commandOrder
:
363 command
= commands
[name
]
364 print ' %s: %s' % (command
.name
, command
.description
)
365 print 'Watch for orphaned loopback devices and delete them with'
366 print 'losetup -d. Mounted images will belong to root, so you may need'
367 print 'to use sudo to modify their contents.'
370 command
= commands
[argv
[1]]
371 command
.parseArgs(argv
)