161
101
self._failure(_('An uncaught exception was raised:\n%s') % str(e))
163
103
# Helpers for core routines.
165
def initialize_progress_thread(self):
166
logging.debug('initialize_progress_thread')
167
if os.path.isfile(self.source):
168
s_total = os.path.getsize(self.source)
170
s_total, s_free = fs_size(self.source)
171
t_total, t_free = fs_size(self.target)
172
# We don't really care if we can't write the entire persistence
174
if s_total > t_total:
175
s_total = s_total / 1024 / 1024
176
t_total = t_total / 1024 / 1024
177
self._failure(_('Insufficient free space to write the image:\n'
178
'%s\n\n(%d MB) > %s (%d MB)') %
179
(self.source, s_total, self.target, t_total))
180
# TODO evand 2009-07-24: Make sure dd.exe doesn't do something
181
# stupid, like write past the end of the device.
182
damage = s_total + (self.persist * 1024 * 1024)
183
self.progress_thread = progress(t_free, damage, self.target)
184
self.progress_thread.progress = self.progress
185
self.progress_thread.start()
188
def remove_extras(self):
189
logging.debug('remove_extras')
190
'''Remove files created by usb-creator.'''
191
casper = os.path.join(self.target, 'casper-rw')
192
if os.path.exists(casper):
194
syslinux = os.path.join(self.target, 'syslinux')
195
if os.path.exists(syslinux):
196
shutil.rmtree(syslinux)
197
ldlinux = os.path.join(self.target, 'ldlinux.sys')
198
if os.path.exists(ldlinux):
202
"""Return a tuple of the target OS version and our OS version."""
205
from debian import debian_support
207
from debian_bundle import debian_support
210
our_os_ver = debian_support.Version(
211
lsb_release.get_distro_information()['RELEASE'])
213
if os.path.exists(os.path.join(self.target, '.disk', 'info')):
214
with open(os.path.join(self.target, '.disk', 'info'),'r') as f:
215
contents = f.readline().split()
216
if len(contents) > 2:
217
# Consider point releases the same as the initial release
219
target_os_ver = debian_support.Version(contents[1])
221
return target_os_ver, our_os_ver
223
def need_syslinux_legacy(self):
224
target_os_ver, our_os_ver = self.os_vers()
225
return our_os_ver >= '10.10' and target_os_ver <= '10.04'
227
def install_bootloader(self, grub_location=''):
228
logging.debug('install_bootloader')
229
self.progress_pulse()
230
self.progress_message(_('Installing the bootloader...'))
231
message = _('Failed to install the bootloader.')
232
if sys.platform == 'win32':
233
# TODO evand 2009-07-23: Zero out the MBR. Check to see if the
234
# first 446 bytes are all NULs, and if not, ask the user if they
235
# want to wipe it. Confirm with a USB disk that never has had an
236
# OS installed to it.
238
dev = str(os.path.splitdrive(self.target)[0])
240
popen(['syslinux', opts, dev])
241
except (USBCreatorProcessException, IOError):
242
self._failure(message)
246
bus = dbus.SystemBus()
247
obj = bus.get_object('com.ubuntu.USBCreator',
248
'/com/ubuntu/USBCreator')
249
obj.InstallBootloader(self.device, self.allow_system_internal,
251
self.need_syslinux_legacy(),
252
dbus_interface='com.ubuntu.USBCreator',
253
timeout=MAX_DBUS_TIMEOUT)
254
except dbus.DBusException:
255
self._failure(message)
256
self.progress_pulse_stop()
259
def mangle_syslinux(self):
260
logging.debug('mangle_syslinux')
261
self.progress_message(_('Modifying configuration...'))
263
# Syslinux expects syslinux/syslinux.cfg.
264
os.renames(os.path.join(self.target, 'isolinux'),
265
os.path.join(self.target, 'syslinux'))
266
os.renames(os.path.join(self.target, 'syslinux', 'isolinux.cfg'),
267
os.path.join(self.target, 'syslinux', 'syslinux.cfg'))
268
except (OSError, IOError) as e:
269
# Failure here probably means the source was not really an Ubuntu
270
# image and did not have the files we wanted to move, see
271
# <https://bugs.launchpad.net/launchpad-code/+bug/513432>
272
self._failure(_('Could not move syslinux files in "%s": %s. '
273
'Maybe "%s" is not an Ubuntu image?') %
274
(self.target, e, self.source))
277
# Mangle the configuration files based on the options we've selected.
279
for filename in glob.iglob(os.path.join(self.target, 'syslinux', '*.cfg')):
280
if os.path.basename(filename) == 'gfxboot.cfg':
283
target_os_ver, our_os_ver = self.os_vers()
285
f = open(filename, 'r')
288
for line in f.readlines():
289
line = line.strip('\n\t').split()
290
if len(line) and len(line[0]):
292
if command.lower() == 'label':
293
label = line[1].strip()
294
elif command.lower() == 'append':
295
if label not in ('check', 'memtest', 'hd'):
296
if self.persist != 0:
297
line.insert(1, 'persistent')
298
line.insert(1, 'cdrom-detect/try-usb=true')
299
if label not in ('memtest', 'hd'):
300
line.insert(1, 'noprompt')
301
#OS version specific mangles
302
#The syntax in syslinux changed with the version
303
#shipping in Ubuntu 10.10
304
elif (target_os_ver and our_os_ver and
305
target_os_ver != our_os_ver):
306
#10.10 or newer image, burning on 10.04 or lower
307
if (command.lower() == 'ui' and
308
our_os_ver <= '10.04' and
309
target_os_ver >= '10.10'):
312
to_write.append(' '.join(line) + '\n')
314
f = open(filename, 'w')
315
f.writelines(to_write)
316
except (KeyboardInterrupt, SystemExit):
319
# TODO evand 2009-07-28: Fail? Warn?
320
logging.exception('Unable to add persistence support to %s:' %
327
def create_persistence(self):
328
logging.debug('create_persistence')
329
if self.persist != 0:
330
dd_cmd = ['dd', 'if=/dev/zero', 'bs=1M', 'of=%s' %
331
os.path.join(str(self.target), 'casper-rw'),
332
'count=%d' % self.persist]
333
if sys.platform == 'win32':
334
# XXX evand 2009-07-30: Do not read past the end of the device.
335
# See http://www.chrysocome.net/dd for details.
336
dd_cmd.append('--size')
337
if sys.platform != 'win32':
338
mkfs_cmd = ['mkfs.ext3', '-F', '%s/casper-rw' % str(self.target)]
340
# FIXME evand 2009-07-23: Need a copy of mke2fs.exe.
343
self.progress_message(_('Creating a persistence file...'))
346
self.progress_message(_('Creating an ext2 filesystem in the '
347
'persistence file...'))
348
if sys.platform != 'win32':
353
logging.debug('sync')
354
# FIXME evand 2009-07-27: Use FlushFileBuffers on the volume (\\.\e:)
355
# http://msdn.microsoft.com/en-us/library/aa364439(VS.85).aspx
356
if sys.platform != 'win32':
357
self.progress_pulse()
358
self.progress_message(_('Finishing...'))
359
# I would try to unmount the device using umount here to get the
360
# pretty GTK+ message, but umount now returns 1 when you do that.
361
# We could call udisk's umount method over dbus, but I now think
362
# that this would look a lot cleaner if done in the usb-creator UI.
365
bus = dbus.SystemBus()
366
obj = bus.get_object('com.ubuntu.USBCreator',
367
'/com/ubuntu/USBCreator')
368
obj.UnmountFile(self.device,
369
dbus_interface='com.ubuntu.USBCreator',
370
timeout=MAX_DBUS_TIMEOUT)
371
except dbus.DBusException:
372
# TODO: Notify the user.
373
logging.exception('Unable to unmount:')
377
104
def diskimage_install(self):
378
# TODO evand 2009-09-02: Disabled until we can find a cross-platform
379
# way of determining dd progress.
380
#self.initialize_progress_thread()
381
105
self.progress_message(_('Writing disk image...'))
382
failure_msg = _('Could not write the disk image (%s) to the device'
383
' (%s).') % (self.source, self.device)
385
cmd = ['dd', 'if=%s' % str(self.source), 'of=%s' % str(self.device),
387
if sys.platform == 'win32':
391
except USBCreatorProcessException:
392
self._failure(failure_msg)
396
bus = dbus.SystemBus()
397
obj = bus.get_object('com.ubuntu.USBCreator',
398
'/com/ubuntu/USBCreator')
399
obj.Image(self.source, self.device, self.allow_system_internal,
400
dbus_interface='com.ubuntu.USBCreator',
401
timeout=MAX_DBUS_TIMEOUT)
402
except dbus.DBusException:
403
self._failure(failure_msg)
405
def _fastboot_command(self, cmd):
406
fastboot_cmd = ['fastboot', '-s', self.target]
407
fastboot_cmd.extend(cmd)
410
def fastboot_install(self):
411
self.progress(0, 3*60+9, True)
412
self.progress_message(_('Erasing boot partition...'))
413
self._fastboot_command(['erase', 'boot'])
414
self.progress(5, 3*60+7, True)
415
self.progress_message(_('Erasing user partition...'))
416
self._fastboot_command(['erase', 'userdata'])
417
self.progress(10, 3*60+5, True)
418
self.progress_message(_('Flashing boot partition...'))
419
self._fastboot_command(['flash', 'boot', self.bootimg])
420
self.progress(15, 3*60+3, True)
421
self.progress_message(_('Flashing user partition...'))
422
self.progress_pulse()
423
self._fastboot_command(['flash', 'userdata', self.source])
424
self.progress_pulse_stop()
425
self.progress(95, 1, True)
426
self.progress_message(_('Rebooting device...'))
427
self._fastboot_command(['reboot'])
428
self.progress(100, 0, True)
430
def cdimage_install(self):
433
cmd = ['7z', 'l', self.source]
434
output = popen(cmd, stderr=None)
437
for line in output.splitlines():
438
if line.startswith('----------'):
439
processing = not processing
443
listing.append(line.split())
448
self.progress_message(_('Removing files...'))
451
assert length == 3 or length == 5
452
t = os.path.join(self.target, line[-1])
453
if os.path.exists(t):
455
if os.path.isfile(t):
456
logging.debug('Removing %s' % t)
458
elif os.path.isdir(t):
459
logging.debug('Removing %s' % t)
464
self.initialize_progress_thread()
468
cmd = ['7z', 'x', self.source, 'md5sum.txt', '-so']
471
output = popen(cmd, stderr=None)
472
for line in output.splitlines():
473
md5sum, filename = line.split()
474
filename = os.path.normpath(filename[2:])
475
md5sums[filename] = md5sum
477
logging.error('Could not generate the md5sum list from md5sum.txt.')
479
self.progress_message(_('Copying files...'))
481
# TODO evand 2009-07-27: Because threads cannot kill other threads
482
# in Python, and because it takes a significant amount of time to
483
# copy the filesystem.sqaushfs file, we'll end up with a long wait
484
# after the user presses the cancel button. This is far from ideal
485
# and should be resolved.
486
# One possibility is to deal with subprocesses asynchronously.
491
logging.debug('Writing %s' % os.path.join(self.target, path))
492
cmd = ['7z', 'x', '-o%s' % self.target, self.source, path]
501
targetfh = open(os.path.join(self.target, path), 'rb')
503
buf = targetfh.read(16 * 1024)
506
targethash.update(buf)
507
if targethash.hexdigest() != md5sums[path]:
508
self._failure(_('md5 checksums do not match.'))
509
# TODO evand 2009-07-27: Recalculate md5 hash.
514
logging.warn('md5 hash not available for %s' % path)
515
# TODO evand 2009-07-27: Recalculate md5 hash.
517
# TODO evand 2009-07-27: Update mtime with line[0] (YYYY-MM-DD)
518
# and line[1] (HH:MM:SS).
519
logging.debug('mkdir %s' % os.path.join(self.target, line[2]))
520
os.mkdir(os.path.join(self.target, line[2]))
524
grub = os.path.join(self.target, 'boot', 'grub', 'i386-pc')
525
if os.path.isdir(grub):
526
self.install_bootloader(grub)
528
self.install_bootloader()
529
self.mangle_syslinux()
531
self.create_persistence()
534
def install_efi(self):
535
logging.debug('install_efi')
536
self.progress_pulse()
537
self.progress_message(_('Installing the EFI bootloader...'))
538
message = _('Failed to install the EFI bootloader.')
539
efi_file = os.path.join(self.target, 'efi', 'boot', 'bootx64.efi')
540
efi_image = os.path.join(self.target, 'boot', 'grub', 'efi.img')
541
if os.path.exists(efi_file):
543
if not os.path.exists(efi_image):
106
failure_msg = _('Could not write the disk image (%(source)s) to the device'
107
' (%(device)s).') % {'source': self.source,
108
'device': self.device}
547
112
bus = dbus.SystemBus()
548
113
obj = bus.get_object('com.ubuntu.USBCreator',
549
114
'/com/ubuntu/USBCreator')
550
obj.InstallEFI(self.target, efi_image,
551
dbus_interface='com.ubuntu.USBCreator',
552
timeout=MAX_DBUS_TIMEOUT)
115
obj.Image(self.source, self.device, self.allow_system_internal,
116
dbus_interface='com.ubuntu.USBCreator',
117
timeout=MAX_DBUS_TIMEOUT)
553
118
except dbus.DBusException:
554
self._failure(message)
558
# Some of the code in this function was copied from Ubiquity's
561
self.progress_message(_('Removing files...'))
563
# TODO evand 2009-07-23: This should throw up some sort of warning
564
# before removing the files. Add files to self.files, directories to
565
# self.directories, and then process each after the warning. If we can
566
# detect that it's Ubuntu (.disk/info), have the warning first say
567
# "Would you like to remove Ubuntu VERSION".
569
for f in os.listdir(self.source):
571
f = os.path.join(self.target, f)
572
if os.path.exists(f):
573
if os.path.isfile(f):
574
logging.debug('Removing %s' % f)
576
elif os.path.isdir(f):
577
logging.debug('Removing %s' % f)
582
self.initialize_progress_thread()
584
self.progress_message(_('Copying files...'))
585
for dirpath, dirnames, filenames in os.walk(self.source):
586
sp = dirpath[len(self.source.rstrip(os.path.sep))+1:]
587
for name in dirnames + filenames:
588
relpath = os.path.join(sp, name)
589
sourcepath = os.path.join(self.source, relpath)
590
targetpath = os.path.join(self.target, relpath)
591
logging.debug('Writing %s' % targetpath)
592
st = os.lstat(sourcepath)
593
mode = stat.S_IMODE(st.st_mode)
594
if stat.S_ISLNK(st.st_mode):
595
if os.path.lexists(targetpath):
596
os.unlink(targetpath)
597
linkto = os.readlink(sourcepath)
598
# XXX evand 2009-07-24: VFAT does not have support for
600
logging.warn('Tried to symlink %s -> %s\n' %
601
(linkto, targetpath))
602
elif stat.S_ISDIR(st.st_mode):
603
if not os.path.isdir(targetpath):
604
os.mkdir(targetpath, mode)
605
elif stat.S_ISCHR(st.st_mode):
606
os.mknod(targetpath, stat.S_IFCHR | mode, st.st_rdev)
607
elif stat.S_ISBLK(st.st_mode):
608
os.mknod(targetpath, stat.S_IFBLK | mode, st.st_rdev)
609
elif stat.S_ISFIFO(st.st_mode):
610
os.mknod(targetpath, stat.S_IFIFO | mode)
611
elif stat.S_ISSOCK(st.st_mode):
612
os.mknod(targetpath, stat.S_IFSOCK | mode)
613
elif stat.S_ISREG(st.st_mode):
614
if os.path.exists(targetpath):
615
os.unlink(targetpath)
616
self.copy_file(sourcepath, targetpath)
620
grub = os.path.join(self.target, 'boot', 'grub', 'i386-pc')
621
if os.path.isdir(grub):
622
self.install_bootloader(grub)
624
self.install_bootloader()
625
self.mangle_syslinux()
627
self.create_persistence()
630
def copy_file(self, sourcepath, targetpath):
634
# TODO evand 2009-07-24: Allow the user to disable this with a command
639
sourcefh = open(sourcepath, 'rb')
640
targetfh = open(targetpath, 'wb')
645
buf = sourcefh.read(16 * 1024)
651
# TODO evand 2009-07-23: Catch exceptions around the
652
# user removing the flash drive mid-write. Give the
653
# user the option of selecting the re-inserted disk
654
# from a drop down list and continuing.
655
# TODO evand 2009-07-23: Fail more gracefully.
656
self._failure(_('Could not read from %s') % self.source)
658
sourcehash.update(buf)
663
# TODO evand 2009-07-25: First check the MD5SUMS.txt file for
664
# the hash. If it exists, and matches the source hash,
665
# continue on. If it exists and does not match the source hash,
666
# or it does not exist, calculate a new hash and compare again.
668
targetfh = open(targetpath, 'rb')
670
buf = targetfh.read(16 * 1024)
673
targethash.update(buf)
674
if targethash.digest() != sourcehash.digest():
679
logging.error('Checksums do not match.')
680
if callable(self.retry):
681
response = self.retry(_('Checksums do not match. Retry?'))
685
self._failure(_('Checksums do not match.'))
119
self._failure(failure_msg)