~usb-creator-hackers/usb-creator/trunk

« back to all changes in this revision

Viewing changes to usbcreator/install.py

  • Committer: Brian Murray
  • Date: 2021-04-02 15:52:52 UTC
  • Revision ID: brian@canonical.com-20210402155252-tk5d4a6lczeld0e0
Tags: 0.3.9
releasing package usb-creator version 0.3.9

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
from usbcreator.misc import (
20
20
    USBCreatorProcessException,
21
21
    callable,
22
 
    fs_size,
23
22
    popen,
24
23
    )
25
 
from usbcreator.remtimest import RemainingTimeEstimator
26
24
from threading import Thread, Event
27
25
import logging
28
26
from hashlib import md5
29
 
 
30
 
if sys.platform != 'win32':
31
 
    from usbcreator.misc import MAX_DBUS_TIMEOUT
32
 
 
33
 
 
34
 
class progress(Thread):
35
 
    def __init__(self, start_free, to_write, device):
36
 
        Thread.__init__(self)
37
 
        self.start_free = start_free
38
 
        self.to_write = to_write
39
 
        self.device = device
40
 
        self._stopevent = Event()
41
 
        # TODO evand 2009-07-24: We should fiddle with the min_age and max_age
42
 
        # parameters so this doesn't constantly remind me of the Windows file
43
 
        # copy dialog: http://xkcd.com/612/
44
 
        self.remtime = RemainingTimeEstimator()
45
 
 
46
 
    def progress(self, per, remaining, speed):
47
 
        pass
48
 
 
49
 
    def run(self):
50
 
        try:
51
 
            while not self._stopevent.isSet():
52
 
                free = fs_size(self.device)[1]
53
 
                written = self.start_free - free
54
 
                v = int((written / float(self.to_write)) * 100)
55
 
                est = self.remtime.estimate(written, self.to_write)
56
 
                if callable(self.progress):
57
 
                    self.progress(v, est[0], est[1])
58
 
                self._stopevent.wait(2)
59
 
        except Exception:
60
 
            logging.exception('Could not update progress:')
61
 
 
62
 
    def join(self, timeout=None):
63
 
        self._stopevent.set()
64
 
        Thread.join(self, timeout)
 
27
from usbcreator.misc import MAX_DBUS_TIMEOUT
 
28
 
65
29
 
66
30
class install(Thread):
67
 
    def __init__(self, source, target, persist, device=None,
68
 
                 allow_system_internal=False,
69
 
                 fastboot_mode=False):
 
31
    def __init__(self, source, target, device=None,
 
32
                 allow_system_internal=False):
70
33
        Thread.__init__(self)
71
34
        self.source = source
72
35
        self.target = target
73
 
        self.persist = persist
74
36
        self.device = device
75
37
        self.allow_system_internal = allow_system_internal
76
 
        self.fastboot_mode = fastboot_mode
77
38
        self._stopevent = Event()
78
 
        self.progress_thread = None
79
39
        logging.debug('install thread source: %s' % source)
80
40
        logging.debug('install thread target: %s' % target)
81
 
        logging.debug('install thread persistence: %d' % persist)
82
41
 
83
42
    # Signals.
84
43
 
86
45
        pass
87
46
    
88
47
    def _success(self):
89
 
        if self.progress_thread and self.progress_thread.is_alive():
90
 
            logging.debug('Shutting down the progress thread.')
91
 
            self.progress_thread.join()
92
48
        if callable(self.success):
93
49
            self.success()
94
50
 
97
53
 
98
54
    def _failure(self, message=None):
99
55
        logging.critical(message)
100
 
        if self.progress_thread and self.progress_thread.is_alive():
101
 
            self.progress_thread.join()
102
56
        if callable(self.failure):
103
57
            self.failure(message)
104
58
        sys.exit(1)
105
59
 
106
 
    def progress(self, complete, remaining, speed):
 
60
    def progress(self, complete):
107
61
        '''Emitted with an integer percentage of progress completed, time
108
62
        remaining, and speed.'''
109
63
        pass
126
80
    def check(self):
127
81
        if self._stopevent.isSet():
128
82
            logging.debug('Asked by the controlling thread to shut down.')
129
 
            if self.progress_thread and self.progress_thread.is_alive():
130
 
                self.progress_thread.join()
131
83
            sys.exit(0)
132
84
    
133
85
    # Exception catching wrapper.
139
91
                if ext not in ['.iso', '.img']:
140
92
                    self._failure(_('The extension "%s" is not supported.') %
141
93
                                    ext)
142
 
                if self.fastboot_mode:
143
 
                    self.bootimg = os.path.splitext(self.source)[0] + '.bootimg'
144
 
                    if not os.path.isfile(self.bootimg):
145
 
                        self._failure(_('Missing matching "%s" for source image %s.') %
146
 
                                    (self.bootimg, self.source))
147
 
                    self.fastboot_install()
148
 
                elif ext == '.iso':
149
 
                    if sys.platform == 'win32':
150
 
                        self.cdimage_install()
151
 
                    else:
152
 
                        self.install()
153
 
                elif ext == '.img':
154
 
                    self.diskimage_install()
 
94
                self.diskimage_install()
155
95
            else:
156
 
                self.install()
 
96
                self.diskimage_install()
157
97
            self._success()
158
98
        except Exception as e:
159
99
            # TODO evand 2009-07-25: Bring up our own apport-like utility.
161
101
            self._failure(_('An uncaught exception was raised:\n%s') % str(e))
162
102
 
163
103
    # Helpers for core routines.
164
 
    
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)
169
 
        else:
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
173
 
        # file.
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()
186
 
        self.check()
187
 
    
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):
193
 
            os.remove(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):
199
 
            os.remove(ldlinux)
200
 
 
201
 
    def os_vers(self):
202
 
        """Return a tuple of the target OS version and our OS version."""
203
 
        import lsb_release
204
 
        try:
205
 
            from debian import debian_support
206
 
        except ImportError:
207
 
            from debian_bundle import debian_support
208
 
 
209
 
        target_os_ver = None
210
 
        our_os_ver = debian_support.Version(
211
 
            lsb_release.get_distro_information()['RELEASE'])
212
 
 
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
218
 
                # (10.04.4 -> 10.04)
219
 
                target_os_ver = debian_support.Version(contents[1])
220
 
 
221
 
        return target_os_ver, our_os_ver
222
 
 
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'
226
 
 
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.
237
 
            opts = '-fma'
238
 
            dev = str(os.path.splitdrive(self.target)[0])
239
 
            try:
240
 
                popen(['syslinux', opts, dev])
241
 
            except (USBCreatorProcessException, IOError):
242
 
                self._failure(message)
243
 
        else:
244
 
            import dbus
245
 
            try:
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,
250
 
                                      grub_location,
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()
257
 
        self.check()
258
 
 
259
 
    def mangle_syslinux(self):
260
 
        logging.debug('mangle_syslinux')
261
 
        self.progress_message(_('Modifying configuration...'))
262
 
        try:
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))
275
 
        self.check()
276
 
        
277
 
        # Mangle the configuration files based on the options we've selected.
278
 
        import glob
279
 
        for filename in glob.iglob(os.path.join(self.target, 'syslinux', '*.cfg')):
280
 
            if os.path.basename(filename) == 'gfxboot.cfg':
281
 
                continue
282
 
            f = None
283
 
            target_os_ver, our_os_ver = self.os_vers()
284
 
            try:
285
 
                f = open(filename, 'r')
286
 
                label = ''
287
 
                to_write = []
288
 
                for line in f.readlines():
289
 
                    line = line.strip('\n\t').split()
290
 
                    if len(line) and len(line[0]):
291
 
                        command = 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'):
310
 
                                line.remove('ui')
311
 
 
312
 
                    to_write.append(' '.join(line) + '\n')
313
 
                f.close()
314
 
                f = open(filename, 'w')
315
 
                f.writelines(to_write)
316
 
            except (KeyboardInterrupt, SystemExit):
317
 
                raise
318
 
            except:
319
 
                # TODO evand 2009-07-28: Fail?  Warn?
320
 
                logging.exception('Unable to add persistence support to %s:' %
321
 
                                  filename)
322
 
            finally:
323
 
                if f:
324
 
                    f.close()
325
 
        self.check()
326
 
 
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)]
339
 
            else:
340
 
                # FIXME evand 2009-07-23: Need a copy of mke2fs.exe.
341
 
                mkfs_cmd = []
342
 
            
343
 
            self.progress_message(_('Creating a persistence file...'))
344
 
            popen(dd_cmd)
345
 
            self.check()
346
 
            self.progress_message(_('Creating an ext2 filesystem in the '
347
 
                                    'persistence file...'))
348
 
            if sys.platform != 'win32':
349
 
                popen(mkfs_cmd)
350
 
            self.check()
351
 
 
352
 
    def sync(self):
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.
363
 
            import dbus
364
 
            try:
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:')
374
 
 
375
 
    # Core routines.
376
 
 
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)
384
 
        
385
 
        cmd = ['dd', 'if=%s' % str(self.source), 'of=%s' % str(self.device),
386
 
               'bs=1M']
387
 
        if sys.platform == 'win32':
388
 
            cmd.append('--size')
389
 
            try:
390
 
                popen(cmd)
391
 
            except USBCreatorProcessException:
392
 
                self._failure(failure_msg)
393
 
        else:
394
 
            import dbus
395
 
            try:
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)
404
 
 
405
 
    def _fastboot_command(self, cmd):
406
 
        fastboot_cmd = ['fastboot', '-s', self.target]
407
 
        fastboot_cmd.extend(cmd)
408
 
        popen(fastboot_cmd)
409
 
 
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)
429
 
 
430
 
    def cdimage_install(self):
431
 
        # Build.
432
 
 
433
 
        cmd = ['7z', 'l', self.source]
434
 
        output = popen(cmd, stderr=None)
435
 
        processing = False
436
 
        listing = []
437
 
        for line in output.splitlines():
438
 
            if line.startswith('----------'):
439
 
                processing = not processing
440
 
                continue
441
 
            if not processing:
442
 
                continue
443
 
            listing.append(line.split())
444
 
        self.check()
445
 
        
446
 
        # Clear.
447
 
 
448
 
        self.progress_message(_('Removing files...'))
449
 
        for line in listing:
450
 
            length = len(line)
451
 
            assert length == 3 or length == 5
452
 
            t = os.path.join(self.target, line[-1])
453
 
            if os.path.exists(t):
454
 
                self.check()
455
 
                if os.path.isfile(t):
456
 
                    logging.debug('Removing %s' % t)
457
 
                    os.unlink(t)
458
 
                elif os.path.isdir(t):
459
 
                    logging.debug('Removing %s' % t)
460
 
                    shutil.rmtree(t)
461
 
        self.check()
462
 
        self.remove_extras()
463
 
        
464
 
        self.initialize_progress_thread()
465
 
 
466
 
        # Copy.
467
 
        
468
 
        cmd = ['7z', 'x', self.source, 'md5sum.txt', '-so']
469
 
        md5sums = {}
470
 
        try:
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
476
 
        except Exception:
477
 
            logging.error('Could not generate the md5sum list from md5sum.txt.')
478
 
 
479
 
        self.progress_message(_('Copying files...'))
480
 
        for line in listing:
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.
487
 
            self.check()
488
 
            length = len(line)
489
 
            if length == 5:
490
 
                path = line[4]
491
 
                logging.debug('Writing %s' % os.path.join(self.target, path))
492
 
                cmd = ['7z', 'x', '-o%s' % self.target, self.source, path]
493
 
                popen(cmd)
494
 
 
495
 
                # Check md5sum.
496
 
 
497
 
                if path in md5sums:
498
 
                    targethash = md5()
499
 
                    targetfh = None
500
 
                    try:
501
 
                        targetfh = open(os.path.join(self.target, path), 'rb')
502
 
                        while 1:
503
 
                            buf = targetfh.read(16 * 1024)
504
 
                            if not buf:
505
 
                                break
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.
510
 
                    finally:
511
 
                        if targetfh:
512
 
                            targetfh.close()
513
 
                else:
514
 
                    logging.warn('md5 hash not available for %s' % path)
515
 
                    # TODO evand 2009-07-27: Recalculate md5 hash.
516
 
            elif length == 3:
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]))
521
 
 
522
 
        self.install_efi()
523
 
 
524
 
        grub = os.path.join(self.target, 'boot', 'grub', 'i386-pc')
525
 
        if os.path.isdir(grub):
526
 
            self.install_bootloader(grub)
527
 
        else:
528
 
            self.install_bootloader()
529
 
            self.mangle_syslinux()
530
 
 
531
 
        self.create_persistence()
532
 
        self.sync()
533
 
 
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):
542
 
            return
543
 
        if not os.path.exists(efi_image):
544
 
            return
 
106
        failure_msg = _('Could not write the disk image (%(source)s) to the device'
 
107
                        ' (%(device)s).') % {'source': self.source,
 
108
                                             'device': self.device}
 
109
        
545
110
        import dbus
546
111
        try:
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)
555
 
 
556
 
 
557
 
    def install(self):
558
 
        # Some of the code in this function was copied from Ubiquity's
559
 
        # scripts/install.py
560
 
 
561
 
        self.progress_message(_('Removing files...'))
562
 
 
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".
568
 
 
569
 
        for f in os.listdir(self.source):
570
 
            self.check()
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)
575
 
                    os.unlink(f)
576
 
                elif os.path.isdir(f):
577
 
                    logging.debug('Removing %s' % f)
578
 
                    shutil.rmtree(f)
579
 
        self.remove_extras()
580
 
        self.check()
581
 
        
582
 
        self.initialize_progress_thread()
583
 
 
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
599
 
                    # symlinks.
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)
617
 
 
618
 
        self.install_efi()
619
 
 
620
 
        grub = os.path.join(self.target, 'boot', 'grub', 'i386-pc')
621
 
        if os.path.isdir(grub):
622
 
            self.install_bootloader(grub)
623
 
        else:
624
 
            self.install_bootloader()
625
 
            self.mangle_syslinux()
626
 
 
627
 
        self.create_persistence()
628
 
        self.sync()
629
 
    
630
 
    def copy_file(self, sourcepath, targetpath):
631
 
        self.check()
632
 
        sourcefh = None
633
 
        targetfh = None
634
 
        # TODO evand 2009-07-24: Allow the user to disable this with a command
635
 
        # line option.
636
 
        md5_check = True
637
 
        try:
638
 
            while 1:
639
 
                sourcefh = open(sourcepath, 'rb')
640
 
                targetfh = open(targetpath, 'wb')
641
 
                if md5_check:
642
 
                    sourcehash = md5()
643
 
                while 1:
644
 
                    self.check()
645
 
                    buf = sourcefh.read(16 * 1024)
646
 
                    if not buf:
647
 
                        break
648
 
                    try:
649
 
                        targetfh.write(buf)
650
 
                    except IOError:
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)
657
 
                    if md5_check:
658
 
                        sourcehash.update(buf)
659
 
 
660
 
                if not md5_check:
661
 
                    break
662
 
                targethash = md5()
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.
667
 
                targetfh.close()
668
 
                targetfh = open(targetpath, 'rb')
669
 
                while 1:
670
 
                    buf = targetfh.read(16 * 1024)
671
 
                    if not buf:
672
 
                        break
673
 
                    targethash.update(buf)
674
 
                if targethash.digest() != sourcehash.digest():
675
 
                    if targetfh:
676
 
                        targetfh.close()
677
 
                    if sourcefh:
678
 
                        sourcefh.close()
679
 
                    logging.error('Checksums do not match.')
680
 
                    if callable(self.retry):
681
 
                        response = self.retry(_('Checksums do not match.  Retry?'))
682
 
                    else:
683
 
                        response = False
684
 
                    if not response:
685
 
                        self._failure(_('Checksums do not match.'))
686
 
                else:
687
 
                    break
688
 
        finally:
689
 
            if targetfh:
690
 
                targetfh.close()
691
 
            if sourcefh:
692
 
                sourcefh.close()
 
119
            self._failure(failure_msg)
 
120