~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Transport for the local filesystem.
18
18
 
20
20
"""
21
21
 
22
22
import os
23
 
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
 
23
from stat import ST_MODE, S_ISDIR, S_IMODE
24
24
import sys
25
25
 
26
26
from bzrlib.lazy_import import lazy_import
33
33
    osutils,
34
34
    urlutils,
35
35
    symbol_versioning,
36
 
    transport,
37
36
    )
38
 
from bzrlib.trace import mutter
39
37
from bzrlib.transport import LateReadError
40
38
""")
41
39
 
42
 
from bzrlib.transport import Transport, Server
43
 
 
44
 
 
45
 
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
46
 
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
47
 
 
48
 
 
49
 
class LocalTransport(Transport):
 
40
from bzrlib import transport
 
41
 
 
42
 
 
43
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT
 
44
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY | osutils.O_NOINHERIT
 
45
 
 
46
 
 
47
class LocalTransport(transport.Transport):
50
48
    """This is the transport agent for local filesystem access."""
51
49
 
52
50
    def __init__(self, base):
71
69
            self._local_base = ''
72
70
            super(LocalTransport, self).__init__(base)
73
71
            return
74
 
            
 
72
 
75
73
        super(LocalTransport, self).__init__(base)
76
74
        self._local_base = urlutils.local_path_from_url(base)
 
75
        if self._local_base[-1] != '/':
 
76
            self._local_base = self._local_base + '/'
77
77
 
78
78
    def clone(self, offset=None):
79
79
        """Return a new LocalTransport with root at self.base + offset
80
 
        Because the local filesystem does not require a connection, 
 
80
        Because the local filesystem does not require a connection,
81
81
        we can just return a new object.
82
82
        """
83
83
        if offset is None:
99
99
         - relative_reference is url escaped.
100
100
        """
101
101
        if relative_reference in ('.', ''):
102
 
            return self._local_base
 
102
            # _local_base normally has a trailing slash; strip it so that stat
 
103
            # on a transport pointing to a symlink reads the link not the
 
104
            # referent but be careful of / and c:\
 
105
            return osutils.split(self._local_base)[0]
103
106
        return self._local_base + urlutils.unescape(relative_reference)
104
107
 
105
108
    def abspath(self, relpath):
160
163
            transport._file_streams[canonical_url].flush()
161
164
        try:
162
165
            path = self._abspath(relpath)
163
 
            return open(path, 'rb')
 
166
            return osutils.open_file(path, 'rb')
164
167
        except (IOError, OSError),e:
165
168
            if e.errno == errno.EISDIR:
166
169
                return LateReadError(relpath)
171
174
 
172
175
        :param relpath: Location to put the contents, relative to base.
173
176
        :param f:       File-like object.
174
 
        :param mode: The mode for the newly created file, 
 
177
        :param mode: The mode for the newly created file,
175
178
                     None means just use the default
176
179
        """
177
180
 
204
207
        except (IOError, OSError),e:
205
208
            self._translate_error(e, path)
206
209
        try:
207
 
            fp.write(bytes)
 
210
            if bytes:
 
211
                fp.write(bytes)
208
212
            fp.commit()
209
213
        finally:
210
214
            fp.close()
285
289
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
286
290
                             create_parent_dir=False, dir_mode=None):
287
291
        def writer(fd):
288
 
            os.write(fd, bytes)
 
292
            if bytes:
 
293
                os.write(fd, bytes)
289
294
        self._put_non_atomic_helper(relpath, writer, mode=mode,
290
295
                                    create_parent_dir=create_parent_dir,
291
296
                                    dir_mode=dir_mode)
324
329
 
325
330
    def open_write_stream(self, relpath, mode=None):
326
331
        """See Transport.open_write_stream."""
327
 
        # initialise the file
328
 
        self.put_bytes_non_atomic(relpath, "", mode=mode)
329
332
        abspath = self._abspath(relpath)
330
 
        handle = open(abspath, 'wb')
 
333
        try:
 
334
            handle = osutils.open_file(abspath, 'wb')
 
335
        except (IOError, OSError),e:
 
336
            self._translate_error(e, abspath)
 
337
        handle.truncate()
331
338
        if mode is not None:
332
339
            self._check_mode_and_size(abspath, handle.fileno(), mode)
333
340
        transport._file_streams[self.abspath(relpath)] = handle
370
377
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
371
378
        try:
372
379
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
373
 
            os.write(fd, bytes)
 
380
            if bytes:
 
381
                os.write(fd, bytes)
374
382
        finally:
375
383
            os.close(fd)
376
384
        return result
396
404
 
397
405
    def rename(self, rel_from, rel_to):
398
406
        path_from = self._abspath(rel_from)
 
407
        path_to = self._abspath(rel_to)
399
408
        try:
400
 
            # *don't* call bzrlib.osutils.rename, because we want to 
401
 
            # detect errors on rename
402
 
            os.rename(path_from, self._abspath(rel_to))
 
409
            # *don't* call bzrlib.osutils.rename, because we want to
 
410
            # detect conflicting names on rename, and osutils.rename tries to
 
411
            # mask cross-platform differences there
 
412
            os.rename(path_from, path_to)
403
413
        except (IOError, OSError),e:
404
414
            # TODO: What about path_to?
405
415
            self._translate_error(e, path_from)
478
488
        path = relpath
479
489
        try:
480
490
            path = self._abspath(relpath)
481
 
            return os.stat(path)
 
491
            return os.lstat(path)
482
492
        except (IOError, OSError),e:
483
493
            self._translate_error(e, path)
484
494
 
512
522
        except (IOError, OSError),e:
513
523
            self._translate_error(e, path)
514
524
 
 
525
    if osutils.host_os_dereferences_symlinks():
 
526
        def readlink(self, relpath):
 
527
            """See Transport.readlink."""
 
528
            return osutils.readlink(self._abspath(relpath))
 
529
 
 
530
    if osutils.hardlinks_good():
 
531
        def hardlink(self, source, link_name):
 
532
            """See Transport.link."""
 
533
            try:
 
534
                os.link(self._abspath(source), self._abspath(link_name))
 
535
            except (IOError, OSError), e:
 
536
                self._translate_error(e, source)
 
537
 
 
538
    if osutils.has_symlinks():
 
539
        def symlink(self, source, link_name):
 
540
            """See Transport.symlink."""
 
541
            abs_link_dirpath = urlutils.dirname(self.abspath(link_name))
 
542
            source_rel = urlutils.file_relpath(
 
543
                urlutils.strip_trailing_slash(abs_link_dirpath),
 
544
                urlutils.strip_trailing_slash(self.abspath(source))
 
545
            )
 
546
 
 
547
            try:
 
548
                os.symlink(source_rel, self._abspath(link_name))
 
549
            except (IOError, OSError), e:
 
550
                self._translate_error(e, source_rel)
 
551
 
515
552
    def _can_roundtrip_unix_modebits(self):
516
553
        if sys.platform == 'win32':
517
554
            # anyone else?
536
573
 
537
574
    def clone(self, offset=None):
538
575
        """Return a new LocalTransport with root at self.base + offset
539
 
        Because the local filesystem does not require a connection, 
 
576
        Because the local filesystem does not require a connection,
540
577
        we can just return a new object.
541
578
        """
542
579
        if offset is None:
551
588
            return EmulatedWin32LocalTransport(abspath)
552
589
 
553
590
 
554
 
class LocalURLServer(Server):
555
 
    """A pretend server for local transports, using file:// urls.
556
 
    
557
 
    Of course no actual server is required to access the local filesystem, so
558
 
    this just exists to tell the test code how to get to it.
559
 
    """
560
 
 
561
 
    def setUp(self):
562
 
        """Setup the server to service requests.
563
 
        
564
 
        :param decorated_transport: ignored by this implementation.
565
 
        """
566
 
 
567
 
    def get_url(self):
568
 
        """See Transport.Server.get_url."""
569
 
        return urlutils.local_path_to_url('')
570
 
 
571
 
 
572
591
def get_test_permutations():
573
592
    """Return the permutations to be used in testing."""
574
 
    return [
575
 
            (LocalTransport, LocalURLServer),
576
 
            ]
 
593
    from bzrlib.tests import test_server
 
594
    return [(LocalTransport, test_server.LocalURLServer),]