use cp -r instead of -a
[livecd.git] / tools / liveimage-mount
blob185f070198a375b453f1d644f344c9e99be528b3
1 #!/usr/bin/python -tt
3 # liveimage-mount: Mount a LiveOS at the specified point, and log
4 # into a shell.
6 # Copyright 2011, Red Hat Inc.
7 # Code for Live mounting an attached LiveOS device added by Frederick Grose,
8 # <fgrose at sugarlabs.org>
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; version 2 of the License.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU Library General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 import os
24 import sys
25 import stat
26 import getopt
27 import tempfile
28 import subprocess
31 def usage(ecode):
32 print """Usage:
33 liveimage-mount [opts] ISO.iso|LiveOSdevice MOUNTPOINT [COMMAND] [ARGS]
35 where [opts] = [-h|--help
36 [--chroot
37 [--mount-hacks
38 [--persist]]]]]
40 and [ARGS] = [arg1[ arg2[ ...]]]\n"""
41 sys.exit(ecode)
44 def call(*popenargs, **kwargs):
45 '''
46 Calls subprocess.Popen() with the provided arguments. All stdout and
47 stderr output is sent to print. The return value is the exit
48 code of the command.
49 '''
50 p = subprocess.Popen(*popenargs, stdout=subprocess.PIPE,
51 stderr=subprocess.STDOUT, **kwargs)
52 rc = p.wait()
54 # Log output using logging module
55 while True:
56 # FIXME choose a more appropriate buffer size
57 buf = p.stdout.read(4096)
58 if not buf:
59 break
60 print buf
62 return rc
65 def rcall(args, env=None):
66 if env:
67 environ = os.environ.copy()
68 environ.update(env)
69 else:
70 environ = None
71 try:
72 p = subprocess.Popen(args, stdout=subprocess.PIPE,
73 stderr=subprocess.PIPE, env=environ)
74 out, err = p.communicate()
75 except OSError, e:
76 raise CreatorError(u"Failed to execute:\n'%s'\n'%s'" % (args, e))
77 except:
78 raise CreatorError(u"""Failed to execute:\n'%s'
79 \renviron: '%s'\nstdout: '%s'\nstderr: '%s'\nreturncode: '%s'""" %
80 (args, environ, out, err, p.returncode))
81 else:
82 if p.returncode != 0:
83 raise CreatorError(u"""Error in call:\n'%s'\nenviron: '%s'
84 \rstdout: '%s'\nstderr: '%s'\nreturncode: '%s'""" %
85 (args, environ, out, err, p.returncode))
86 return out
89 def get_device_mountpoint(path):
90 """Return the device and mountpoint for a file or device path."""
92 info = list()
93 info[0:5] = [None] * 6
94 if os.path.exists(path):
95 st_mode = os.stat(path).st_mode
96 if stat.S_ISBLK(st_mode) or os.path.ismount(path):
97 devinfo = rcall(['/bin/df', path]).splitlines()
98 info = devinfo[1].split(None)
99 if len(info) == 1:
100 info.extend(devinfo[2].split(None))
101 return [info[0], info[5]]
104 def main():
105 if os.geteuid() != 0:
106 print >> sys.stderr, """\n Exiting...
107 \r You must run liveimage-mount with root priviledges.\n"""
108 return 1
110 try:
111 opts,args = getopt.getopt(sys.argv[1:], 'h', ['help',
112 'chroot', 'persist',
113 'mount-hacks'])
114 except getopt.GetoptError, e:
115 usage(1)
117 img_type = None
118 chroot = False
119 persist = False
120 mount_hacks = False
121 for o,a in opts:
122 if o in ('-h', '--help'):
123 usage(0)
124 elif o in ('--chroot', ):
125 chroot = True
126 elif o in ('--mount-hacks', ):
127 mount_hacks = True
128 elif o in ('--persist', ):
129 """Option used to run a command in a spawned process."""
130 persist = True
132 if len(args) < 2:
133 usage(1)
135 liveos = args[0]
136 destmnt = args[1]
138 if not os.path.exists(liveos):
139 print """\n Exiting...
140 %s is not a file, directory, or device.\n""" % liveos
141 ecode = 1
142 return
144 if stat.S_ISBLK(os.stat(liveos).st_mode):
145 img_type = 'blk'
146 imgloop = None
147 overlayloop = None
148 else:
149 img_type = 'iso'
151 if img_type is 'blk':
152 liveosmnt = tempfile.mkdtemp(prefix='livemnt-device-')
153 if img_type is 'iso':
154 liveosmnt = tempfile.mkdtemp(prefix='livemnt-iso-')
156 liveosdir = os.path.join(liveosmnt, 'LiveOS')
157 homemnt = None
158 dm_cow = None
159 mntlive = None
161 command = args[2:]
162 verbose = not command
164 squashmnt = tempfile.mkdtemp(prefix='livemnt-squash-')
165 squashloop = None
166 imgloop = None
167 losetup_args = ['/sbin/losetup', '-f', '--show']
169 try:
170 if img_type is 'blk':
171 call(['/bin/mount', liveos, liveosmnt])
172 elif img_type is 'iso':
173 liveosloop = rcall(losetup_args + [liveos]).rstrip()
174 call(['/bin/mount', '-o', 'ro', liveosloop, liveosmnt])
176 squash_img = os.path.join(liveosdir, 'squashfs.img')
177 if not os.path.exists(squash_img):
178 ext3_img = os.path.join(liveosdir, 'ext3fs.img')
179 if not os.path.exists(ext3_img):
180 print """
181 \r\tNo LiveOS was found on %s\n\t Exiting...\n""" % liveos
182 ecode = 1
183 return
184 else:
185 squashloop = rcall(losetup_args + [squash_img]).rstrip()
186 call(['/bin/mount', '-o', 'ro', squashloop, squashmnt])
187 ext3_img = os.path.join(squashmnt, 'LiveOS', 'ext3fs.img')
189 if img_type is 'blk':
190 imgloop = rcall(losetup_args + [ext3_img]).rstrip()
191 imgsize = rcall(['/sbin/blockdev', '--getsz', imgloop]).rstrip()
192 files = os.listdir(liveosdir)
193 overlay = None
194 for f in files:
195 if f.find('overlay-') == 0:
196 overlay = f
197 break
198 overlayloop = rcall(['/sbin/losetup', '-f']).rstrip()
199 if overlay:
200 call(['/sbin/losetup', overlayloop, os.path.join(liveosdir,
201 overlay)])
202 dm_cow = 'live-rw'
203 else:
204 overlay = tempfile.NamedTemporaryFile(dir='/dev/shm')
205 print "\npreparing temporary overlay..."
206 call(['/bin/dd', 'if=/dev/null', 'of=%s' % overlay.name,
207 'bs=1024', 'count=1', 'seek=%s' % (512 * 1024)])
208 call(['/sbin/losetup', overlayloop, overlay.name])
209 dm_cow = 'live-ro'
210 call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',
211 'create', dm_cow, '--table=0 %s snapshot %s %s p 8' %
212 (imgsize, imgloop, overlayloop)])
213 call(['/bin/mount', os.path.join('/dev/mapper', dm_cow), destmnt])
214 home_path = os.path.join(liveosdir, 'home.img')
215 if os.path.exists(home_path):
216 homemnt = os.path.join(destmnt, 'home')
217 homeloop = rcall(losetup_args + [home_path]).rstrip()
218 call(['/bin/mount', homeloop, homemnt])
219 mntlive = os.path.join(destmnt, 'mnt', 'live')
220 if not os.path.exists(mntlive):
221 os.makedirs(mntlive)
222 call(['/bin/mount', '--bind', liveosmnt, mntlive])
223 elif img_type is 'iso':
224 imgloop = rcall(losetup_args + [ext3_img]).rstrip()
225 call(['/bin/mount', '-o', 'ro', imgloop, destmnt])
227 if mount_hacks:
228 subprocess.check_call(['mount', '-t', 'proc', 'proc', os.path.join(destmnt, 'proc')], stderr=sys.stderr)
229 subprocess.check_call(['mount', '-t', 'tmpfs', 'tmpfs', os.path.join(destmnt, 'var', 'run')], stderr=sys.stderr)
231 if len(command) > 0 and persist:
232 args = []
233 args.extend(command)
234 live = ''
235 if img_type is 'blk':
236 live = 'live-'
237 print """Starting process with this command line:
238 \r%s\n %s is %smounted.""" % (' '.join(command), liveos, live)
239 p = subprocess.Popen(args, close_fds=True)
240 print "Process id: %s" % p.pid
241 ecode = p.returncode
242 elif len(command) > 0:
243 args = ['chroot', destmnt]
244 args.extend(command)
245 ecode = subprocess.call(args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
246 elif chroot:
247 print "Starting subshell in chroot, press Ctrl-D to exit..."
248 ecode = subprocess.call(['chroot', destmnt], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
249 else:
250 if dm_cow == 'live-ro':
251 status = ' with NO LiveOS persistence,'
252 else:
253 status = ''
254 print "Entering subshell,%s press Ctrl-D to exit..." % status
255 ecode = subprocess.call([os.environ['SHELL']], cwd=destmnt, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
256 finally:
257 call(['/bin/sync'])
258 if not persist:
259 if verbose:
260 print """Cleaning up...
261 Please wait if large files were written."""
262 if mount_hacks:
263 subprocess.call(['umount', os.path.join(destmnt, 'var', 'run')])
264 subprocess.call(['umount', os.path.join(destmnt, 'proc')])
265 if homemnt:
266 call(['/bin/umount', homemnt])
267 call(['/sbin/losetup', '-d', homeloop])
268 if img_type is 'blk':
269 if mntlive:
270 call(['/bin/umount', mntlive])
271 os.rmdir(mntlive)
272 if os.path.ismount(destmnt):
273 call(['/bin/umount', destmnt])
274 if img_type is 'blk':
275 if dm_cow:
276 call(['/sbin/dmsetup', '--noudevrules', '--noudevsync',
277 'remove', dm_cow])
278 if overlayloop:
279 call(['/sbin/losetup', '-d', overlayloop])
280 if imgloop:
281 call(['/sbin/losetup', '-d', imgloop])
282 if squashloop:
283 call(['/bin/umount', squashloop])
284 call(['/sbin/losetup', '-d', squashloop])
285 call(['/bin/umount', liveosmnt])
286 if not img_type is 'blk':
287 call(['/sbin/losetup', '-d', liveosloop])
288 os.rmdir(squashmnt)
289 if not os.path.ismount(liveosmnt):
290 os.rmdir(liveosmnt)
291 if verbose:
292 print "Cleanup complete"
294 sys.exit(ecode)
296 if __name__ == '__main__':
297 main()