~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/sftp.py

merge permissions branch, also fixup tests so they are lined up with bzr.dev to help prevent conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
127
127
    def send(self, data):
128
128
        return os.write(self.proc.stdin.fileno(), data)
129
129
 
 
130
    def recv_ready(self):
 
131
        # TODO: jam 20051215 this function is necessary to support the
 
132
        # pipelined() function. In reality, it probably should use
 
133
        # poll() or select() to actually return if there is data
 
134
        # available, otherwise we probably don't get any benefit
 
135
        return True
 
136
 
130
137
    def recv(self, count):
131
138
        return os.read(self.proc.stdout.fileno(), count)
132
139
 
192
199
        self.lock_path = path + '.write-lock'
193
200
        self.transport = transport
194
201
        try:
195
 
            self.lock_file = transport._sftp_open_exclusive(self.lock_path)
 
202
            abspath = transport._abspath(self.lock_path)
 
203
            self.lock_file = transport._sftp_open_exclusive(abspath)
196
204
        except FileExists:
197
205
            raise LockError('File %r already locked' % (self.path,))
198
206
 
343
351
            f.prefetch()
344
352
        return f
345
353
 
346
 
    def put(self, relpath, f):
 
354
    def put(self, relpath, f, mode=None):
347
355
        """
348
356
        Copy the file-like or string object into the location.
349
357
 
350
358
        :param relpath: Location to put the contents, relative to base.
351
359
        :param f:       File-like or string object.
 
360
        :param mode: The final mode for the file
352
361
        """
353
362
        final_path = self._abspath(relpath)
354
 
        tmp_relpath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
 
363
        self._put(final_path, f, mode=mode)
 
364
 
 
365
    def _put(self, abspath, f, mode=None):
 
366
        """Helper function so both put() and copy_abspaths can reuse the code"""
 
367
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
355
368
                        os.getpid(), random.randint(0,0x7FFFFFFF))
356
 
        tmp_abspath = self._abspath(tmp_relpath)
357
 
        fout = self._sftp_open_exclusive(tmp_relpath)
358
 
 
 
369
        fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
359
370
        closed = False
360
371
        try:
361
372
            try:
 
373
                fout.set_pipelined(True)
362
374
                self._pump(f, fout)
363
375
            except (IOError, paramiko.SSHException), e:
364
376
                self._translate_io_exception(e, tmp_abspath)
 
377
            if mode is not None:
 
378
                self._sftp.chmod(tmp_abspath, mode)
365
379
            fout.close()
366
380
            closed = True
367
 
            self._rename(tmp_abspath, final_path)
 
381
            self._rename(tmp_abspath, abspath)
368
382
        except Exception, e:
369
383
            # If we fail, try to clean up the temporary file
370
384
            # before we throw the exception
371
385
            # but don't let another exception mess things up
 
386
            # Write out the traceback, because otherwise
 
387
            # the catch and throw destroys it
 
388
            import traceback
 
389
            mutter(traceback.format_exc())
372
390
            try:
373
391
                if not closed:
374
392
                    fout.close()
389
407
            else:
390
408
                yield relpath
391
409
 
392
 
    def mkdir(self, relpath):
 
410
    def mkdir(self, relpath, mode=None):
393
411
        """Create a directory at the given path."""
394
412
        try:
395
413
            path = self._abspath(relpath)
 
414
            # In the paramiko documentation, it says that passing a mode flag 
 
415
            # will filtered against the server umask.
 
416
            # StubSFTPServer does not do this, which would be nice, because it is
 
417
            # what we really want :)
 
418
            # However, real servers do use umask, so we really should do it that way
396
419
            self._sftp.mkdir(path)
 
420
            if mode is not None:
 
421
                self._sftp.chmod(path, mode=mode)
397
422
        except (paramiko.SSHException, IOError), e:
398
 
            self._translate_io_exception(e, relpath, ': unable to mkdir',
 
423
            self._translate_io_exception(e, path, ': unable to mkdir',
399
424
                failure_exc=FileExists)
400
425
 
401
426
    def _translate_io_exception(self, e, path, more_info='', failure_exc=NoSuchFile):
422
447
            # strange but true, for the paramiko server.
423
448
            if (e.args == ('Failure',)):
424
449
                raise failure_exc(path, str(e) + more_info)
 
450
            mutter('Raising exception with args %s', e.args)
 
451
        if hasattr(e, 'errno'):
 
452
            mutter('Raising exception with errno %s', e.errno)
425
453
        raise e
426
454
 
427
455
    def append(self, relpath, f):
442
470
        path_to = self._abspath(rel_to)
443
471
        self._copy_abspaths(path_from, path_to)
444
472
 
445
 
    def _copy_abspaths(self, path_from, path_to):
 
473
    def _copy_abspaths(self, path_from, path_to, mode=None):
446
474
        """Copy files given an absolute path
447
475
 
448
476
        :param path_from: Path on remote server to read
457
485
        try:
458
486
            fin = self._sftp.file(path_from, 'rb')
459
487
            try:
460
 
                fout = self._sftp.file(path_to, 'wb')
461
 
                try:
462
 
                    fout.set_pipelined(True)
463
 
                    self._pump(fin, fout)
464
 
                finally:
465
 
                    fout.close()
 
488
                self._put(path_to, fin, mode=mode)
466
489
            finally:
467
490
                fin.close()
468
491
        except (IOError, paramiko.SSHException), e:
469
492
            self._translate_io_exception(e, path_from, ': unable copy to: %r' % path_to)
470
493
 
471
 
    def copy_to(self, relpaths, other, pb=None):
 
494
    def copy_to(self, relpaths, other, mode=None, pb=None):
472
495
        """Copy a set of entries from self into another Transport.
473
496
 
474
497
        :param relpaths: A list/generator of entries to be copied.
483
506
                path_from = self._abspath(relpath)
484
507
                path_to = other._abspath(relpath)
485
508
                self._update_pb(pb, 'copy-to', count, total)
486
 
                self._copy_abspaths(path_from, path_to)
 
509
                self._copy_abspaths(path_from, path_to, mode=mode)
487
510
                count += 1
488
511
            return count
489
512
        else:
490
 
            return super(SFTPTransport, self).copy_to(relpaths, other, pb=pb)
491
 
 
492
 
        # The dummy implementation just does a simple get + put
493
 
        def copy_entry(path):
494
 
            other.put(path, self.get(path))
495
 
 
496
 
        return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
 
513
            return super(SFTPTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
497
514
 
498
515
    def _rename(self, abs_from, abs_to):
499
516
        """Do a fancy rename on the remote server.
768
785
            pass
769
786
        return False
770
787
 
771
 
    def _sftp_open_exclusive(self, relpath):
 
788
    def _sftp_open_exclusive(self, abspath, mode=None):
772
789
        """Open a remote path exclusively.
773
790
 
774
791
        SFTP supports O_EXCL (SFTP_FLAG_EXCL), which fails if
779
796
        WARNING: This breaks the SFTPClient abstraction, so it
780
797
        could easily break against an updated version of paramiko.
781
798
 
782
 
        :param relpath: The relative path, where the file should be opened
 
799
        :param abspath: The remote absolute path where the file should be opened
 
800
        :param mode: The mode permissions bits for the new file
783
801
        """
784
 
        path = self._sftp._adjust_cwd(self._abspath(relpath))
 
802
        path = self._sftp._adjust_cwd(abspath)
785
803
        attr = SFTPAttributes()
786
 
        mode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE 
 
804
        if mode is not None:
 
805
            attr.st_mode = mode
 
806
        omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE 
787
807
                | SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
788
808
        try:
789
 
            t, msg = self._sftp._request(CMD_OPEN, path, mode, attr)
 
809
            t, msg = self._sftp._request(CMD_OPEN, path, omode, attr)
790
810
            if t != CMD_HANDLE:
791
811
                raise TransportError('Expected an SFTP handle')
792
812
            handle = msg.get_string()
793
813
            return SFTPFile(self._sftp, handle, 'wb', -1)
794
814
        except (paramiko.SSHException, IOError), e:
795
 
            self._translate_io_exception(e, relpath, ': unable to open',
 
815
            self._translate_io_exception(e, abspath, ': unable to open',
796
816
                failure_exc=FileExists)
797
817