1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
3
3
# This program is free software; you can redistribute it and/or modify
 
4
4
# it under the terms of the GNU General Public License as published by
 
 
243
244
        # have a similar bug allowing someone to think they got the lock
 
244
245
        # when it's already held.
 
246
 
        # See <https://bugs.edge.launchpad.net/bzr/+bug/498378> for one case.
 
 
247
        # See <https://bugs.launchpad.net/bzr/+bug/498378> for one case.
 
248
249
        # Strictly the check is unnecessary and a waste of time for most
 
249
250
        # people, but probably worth trapping if something is wrong.
 
 
346
347
        This is a UI centric function: it uses the bzrlib.ui.ui_factory to
 
347
348
        prompt for input if a lock is detected and there is any doubt about
 
348
349
        it possibly being still active.
 
 
351
        :returns: LockResult for the broken lock.
 
350
353
        self._check_not_locked()
 
351
 
        holder_info = self.peek()
 
 
355
            holder_info = self.peek()
 
 
356
        except LockCorrupt, e:
 
 
357
            # The lock info is corrupt.
 
 
358
            if bzrlib.ui.ui_factory.get_boolean("Break (corrupt %r)" % (self,)):
 
 
359
                self.force_break_corrupt(e.file_data)
 
352
361
        if holder_info is not None:
 
353
362
            lock_info = '\n'.join(self._format_lock_info(holder_info))
 
354
 
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
 
355
 
                self.force_break(holder_info)
 
 
363
            if bzrlib.ui.ui_factory.confirm_action(
 
 
364
                "Break %(lock_info)s", 'bzrlib.lockdir.break', 
 
 
365
                dict(lock_info=lock_info)):
 
 
366
                result = self.force_break(holder_info)
 
 
367
                bzrlib.ui.ui_factory.show_message(
 
 
368
                    "Broke lock %s" % result.lock_url)
 
357
370
    def force_break(self, dead_holder_info):
 
358
371
        """Release a lock held by another process.
 
 
369
382
        After the lock is broken it will not be held by any process.
 
370
383
        It is possible that another process may sneak in and take the
 
371
384
        lock before the breaking process acquires it.
 
 
386
        :returns: LockResult for the broken lock.
 
373
388
        if not isinstance(dead_holder_info, dict):
 
374
389
            raise ValueError("dead_holder_info: %r" % dead_holder_info)
 
 
394
409
                                 current_info.get('nonce'))
 
395
410
        for hook in self.hooks['lock_broken']:
 
 
414
    def force_break_corrupt(self, corrupt_info_lines):
 
 
415
        """Release a lock that has been corrupted.
 
 
417
        This is very similar to force_break, it except it doesn't assume that
 
 
418
        self.peek() can work.
 
 
420
        :param corrupt_info_lines: the lines of the corrupted info file, used
 
 
421
            to check that the lock hasn't changed between reading the (corrupt)
 
 
422
            info file and calling force_break_corrupt.
 
 
424
        # XXX: this copes with unparseable info files, but what about missing
 
 
425
        # info files?  Or missing lock dirs?
 
 
426
        self._check_not_locked()
 
 
427
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
 
 
428
        self.transport.rename(self._held_dir, tmpname)
 
 
429
        # check that we actually broke the right lock, not someone else;
 
 
430
        # there's a small race window between checking it and doing the
 
 
432
        broken_info_path = tmpname + self.__INFO_NAME
 
 
433
        broken_content = self.transport.get_bytes(broken_info_path)
 
 
434
        broken_lines = osutils.split_lines(broken_content)
 
 
435
        if broken_lines != corrupt_info_lines:
 
 
436
            raise LockBreakMismatch(self, broken_lines, corrupt_info_lines)
 
 
437
        self.transport.delete(broken_info_path)
 
 
438
        self.transport.rmdir(tmpname)
 
 
439
        result = lock.LockResult(self.transport.abspath(self.path))
 
 
440
        for hook in self.hooks['lock_broken']:
 
398
443
    def _check_not_locked(self):
 
399
444
        """If the lock is held by this instance, raise an error."""
 
 
447
492
        # XXX: is creating this here inefficient?
 
448
493
        config = bzrlib.config.GlobalConfig()
 
450
 
            user = config.user_email()
 
451
 
        except errors.NoEmailInUsername:
 
452
495
            user = config.username()
 
 
496
        except errors.NoWhoami:
 
 
497
            user = osutils.getuser_unicode()
 
453
498
        s = rio.Stanza(hostname=get_host_name(),
 
454
499
                   pid=str(os.getpid()),
 
455
500
                   start_time=str(int(time.time())),
 
 
459
504
        return s.to_string()
 
461
506
    def _parse_info(self, info_bytes):
 
462
 
        stanza = rio.read_stanza(osutils.split_lines(info_bytes))
 
 
507
        lines = osutils.split_lines(info_bytes)
 
 
509
            stanza = rio.read_stanza(lines)
 
 
510
        except ValueError, e:
 
 
511
            mutter('Corrupt lock info file: %r', lines)
 
 
512
            raise LockCorrupt("could not parse lock info file: " + str(e),
 
463
514
        if stanza is None:
 
464
515
            # see bug 185013; we fairly often end up with the info file being
 
465
516
            # empty after an interruption; we could log a message here but
 
 
486
537
            hook(hook_result)
 
 
540
    def lock_url_for_display(self):
 
 
541
        """Give a nicely-printable representation of the URL of this lock."""
 
 
542
        # As local lock urls are correct we display them.
 
 
543
        # We avoid displaying remote lock urls.
 
 
544
        lock_url = self.transport.abspath(self.path)
 
 
545
        if lock_url.startswith('file://'):
 
 
546
            lock_url = lock_url.split('.bzr/')[0]
 
489
551
    def wait_lock(self, timeout=None, poll=None, max_attempts=None):
 
490
552
        """Wait a certain period for a lock.
 
 
539
602
                if deadline_str is None:
 
540
603
                    deadline_str = time.strftime('%H:%M:%S',
 
541
604
                                                 time.localtime(deadline))
 
542
 
                lock_url = self.transport.abspath(self.path)
 
543
 
                # See <https://bugs.edge.launchpad.net/bzr/+bug/250451>
 
544
 
                # the URL here is sometimes not one that is useful to the
 
545
 
                # user, perhaps being wrapped in a lp-%d or chroot decorator,
 
546
 
                # especially if this error is issued from the server.
 
547
 
                self._report_function('%s %s\n'
 
549
 
                    '%s\n' # locked ... ago
 
550
 
                    'Will continue to try until %s, unless '
 
551
 
                    'you press Ctrl-C.\n'
 
552
 
                    'See "bzr help break-lock" for more.',
 
 
605
                user, hostname, pid, time_ago = formatted_info
 
 
606
                msg = ('%s lock %s '        # lock_url
 
 
610
                    '[process #%s], '       # pid
 
 
611
                    'acquired %s.')         # time ago
 
 
612
                msg_args = [start, lock_url, user, hostname, pid, time_ago]
 
 
614
                    msg += ('\nWill continue to try until %s, unless '
 
 
616
                    msg_args.append(deadline_str)
 
 
617
                msg += '\nSee "bzr help break-lock" for more.'
 
 
618
                self._report_function(msg, *msg_args)
 
560
619
            if (max_attempts is not None) and (attempt_count >= max_attempts):
 
561
620
                self._trace("exceeded %d attempts")
 
562
621
                raise LockContention(self)
 
 
564
623
                self._trace("waiting %ss", poll)
 
 
626
                # As timeout is always 0 for remote locks
 
 
627
                # this block is applicable only for local
 
567
629
                self._trace("timeout after waiting %ss", timeout)
 
568
 
                raise LockContention(self)
 
 
630
                raise LockContention('(local)', lock_url)
 
570
632
    def leave_in_place(self):
 
571
633
        self._locked_via_token = True
 
 
617
679
    def _format_lock_info(self, info):
 
618
680
        """Turn the contents of peek() into something for the user"""
 
619
 
        lock_url = self.transport.abspath(self.path)
 
620
681
        start_time = info.get('start_time')
 
621
682
        if start_time is None:
 
622
683
            time_ago = '(unknown)'
 
624
685
            time_ago = format_delta(time.time() - int(info['start_time']))
 
 
686
        user = info.get('user', '<unknown>')
 
 
687
        hostname = info.get('hostname', '<unknown>')
 
 
688
        pid = info.get('pid', '<unknown>')
 
626
 
            'lock %s' % (lock_url,),
 
627
 
            'held by %s on host %s [process #%s]' %
 
628
 
                tuple([info.get(x, '<unknown>') for x in ['user', 'hostname', 'pid']]),
 
629
 
            'locked %s' % (time_ago,),
 
632
696
    def validate_token(self, token):