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] == '#' or lines
[0].startswith('label:'))
139 assert(lines
[1] == 'unit: sectors' or lines
[1].startswith('label-id:'))
140 assert(lines
[2] == '' or lines
[2].startswith('device:'))
141 if lines
[0][0] == '#' :
142 # Parsing an 'old style' dump oputput
143 # Line 4 has information about the first partition.
144 chunks
= lines
[3].split()
146 # Parsing a 'new style' dump oputput
147 # Line 6 has information about the first partition.
148 chunks
= lines
[5].split()
149 # The fourth chunk is the offset of the partition in sectors followed by
150 # a comma. We drop the comma and convert that to an integer.
151 sectors
= string
.atoi(chunks
[3][:-1])
152 # Free the loopback device and return an answer.
154 return sectors
* BlockSize
156 def mountPointToDev(mountPoint
):
157 (mountTable
, returncode
) = getOutput([findProg('mount')])
161 mountTable
= mountTable
.splitlines()
162 for line
in mountTable
:
163 chunks
= line
.split()
164 if os
.path
.samefile(chunks
[2], mountPoint
):
165 return LoopbackDevice(chunks
[0])
169 # Commands for the gem5img.py script
173 class Command(object):
174 def addOption(self
, *args
, **kargs
):
175 self
.parser
.add_option(*args
, **kargs
)
177 def __init__(self
, name
, description
, posArgs
):
179 self
.description
= description
181 self
.posArgs
= posArgs
182 commands
[self
.name
] = self
183 commandOrder
.append(self
.name
)
184 usage
= 'usage: %prog [options]'
186 for posArg
in posArgs
:
187 (argName
, argDesc
) = posArg
188 usage
+= ' %s' % argName
189 posUsage
+= '\n %s: %s' % posArg
191 self
.parser
= OptionParser(usage
=usage
, description
=description
)
192 self
.addOption('-d', '--debug', dest
='debug', action
='store_true',
193 help='Verbose output.')
195 def parseArgs(self
, argv
):
196 (self
.options
, self
.args
) = self
.parser
.parse_args(argv
[2:])
197 if len(self
.args
) != len(self
.posArgs
):
198 self
.parser
.error('Incorrect number of arguments')
200 if self
.options
.debug
:
205 exit('Unimplemented command %s!' % self
.name
)
206 self
.func(self
.options
, self
.args
)
209 # A command which prepares an image with an partition table and an empty file
211 initCom
= Command('init', 'Create an image with an empty file system.',
212 [('file', 'Name of the image file.'),
213 ('mb', 'Size of the file in MB.')])
214 initCom
.addOption('-t', '--type', dest
='fstype', action
='store',
216 help='Type of file system to use. Appended to mkfs.')
218 # A command to mount the first partition in the image.
219 mountCom
= Command('mount', 'Mount the first partition in the disk image.',
220 [('file', 'Name of the image file.'),
221 ('mount point', 'Where to mount the image.')])
223 def mountComFunc(options
, args
):
224 (path
, mountPoint
) = args
225 if not os
.path
.isdir(mountPoint
):
226 print "Mount point %s is not a directory." % mountPoint
228 dev
= LoopbackDevice()
229 if dev
.setup(path
, offset
=True) != 0:
232 if runPriv([findProg('mount'), str(dev
), mountPoint
]) != 0:
236 mountCom
.func
= mountComFunc
238 # A command to unmount the first partition in the image.
239 umountCom
= Command('umount', 'Unmount the first partition in the disk image.',
240 [('mount point', 'What mount point to unmount.')])
242 def umountComFunc(options
, args
):
244 if not os
.path
.isdir(mountPoint
):
245 print "Mount point %s is not a directory." % mountPoint
248 dev
= mountPointToDev(mountPoint
)
250 print "Unable to find mount information for %s." % mountPoint
252 # Unmount the loopback device.
253 if runPriv([findProg('umount'), mountPoint
]) != 0:
256 # Destroy the loopback device.
259 umountCom
.func
= umountComFunc
262 # A command to create an empty file to hold the image.
263 newCom
= Command('new', 'File creation part of "init".',
264 [('file', 'Name of the image file.'),
265 ('mb', 'Size of the file in MB.')])
267 def newImage(file, mb
):
268 (cylinders
, heads
, sectors
) = chsFromSize((mb
* MB
) / BlockSize
)
269 size
= cylinders
* heads
* sectors
* BlockSize
271 # We lseek to the end of the file and only write one byte there. This
272 # leaves a "hole" which many file systems are smart enough not to actually
273 # store to disk and which is defined to read as zero.
274 fd
= os
.open(file, os
.O_WRONLY | os
.O_CREAT
)
275 os
.lseek(fd
, size
- 1, os
.SEEK_SET
)
278 def newComFunc(options
, args
):
284 newCom
.func
= newComFunc
286 # A command to partition the image file like a raw disk device.
287 partitionCom
= Command('partition', 'Partition part of "init".',
288 [('file', 'Name of the image file.')])
290 def partition(dev
, cylinders
, heads
, sectors
):
291 # Use sfdisk to partition the device
292 # The specified options are intended to work with both new and old
293 # versions of sfdisk (see https://askubuntu.com/a/819614)
295 return runPriv([findProg('sfdisk'), '--no-reread', '-u', 'S', '-L', \
296 str(dev
)], inputVal
=comStr
)
298 def partitionComFunc(options
, args
):
301 dev
= LoopbackDevice()
302 if dev
.setup(path
) != 0:
305 # Figure out the dimensions of the file.
306 size
= os
.path
.getsize(path
)
307 if partition(dev
, *chsFromSize(size
/ BlockSize
)) != 0:
313 partitionCom
.func
= partitionComFunc
315 # A command to format the first partition in the image.
316 formatCom
= Command('format', 'Formatting part of "init".',
317 [('file', 'Name of the image file.')])
318 formatCom
.addOption('-t', '--type', dest
='fstype', action
='store',
320 help='Type of file system to use. Appended to mkfs.')
322 def formatImage(dev
, fsType
):
323 return runPriv([findProg('mkfs.%s' % fsType
, dev
), str(dev
)])
325 def formatComFunc(options
, args
):
328 dev
= LoopbackDevice()
329 if dev
.setup(path
, offset
=True) != 0:
333 if formatImage(dev
, options
.fstype
) != 0:
339 formatCom
.func
= formatComFunc
341 def initComFunc(options
, args
):
345 dev
= LoopbackDevice()
346 if dev
.setup(path
) != 0:
348 size
= os
.path
.getsize(path
)
349 if partition(dev
, *chsFromSize((mb
* MB
) / BlockSize
)) != 0:
353 if dev
.setup(path
, offset
=True) != 0:
355 if formatImage(dev
, options
.fstype
) != 0:
360 initCom
.func
= initComFunc
363 # Figure out what command was requested and execute it.
364 if len(argv
) < 2 or argv
[1] not in commands
:
365 print 'Usage: %s [command] <command arguments>'
366 print 'where [command] is one of '
367 for name
in commandOrder
:
368 command
= commands
[name
]
369 print ' %s: %s' % (command
.name
, command
.description
)
370 print 'Watch for orphaned loopback devices and delete them with'
371 print 'losetup -d. Mounted images will belong to root, so you may need'
372 print 'to use sudo to modify their contents.'
375 command
= commands
[argv
[1]]
376 command
.parseArgs(argv
)