Changeset c715e0d in trunk


Ignore:
Timestamp:
2016-05-04T15:04:51Z (9 years ago)
Author:
Brian Warner <warner@…>
Branches:
master
Children:
d1d9884
Parents:
9402b40d (diff), fa418a7 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge pull request #265 from meejah/fileutil-improvements

Location:
src/allmydata
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • TabularUnified src/allmydata/test/test_util.py

    r9402b40d rc715e0d  
    498498        saved_cwd = os.path.normpath(os.getcwdu())
    499499        abspath_cwd = fileutil.abspath_expanduser_unicode(u".")
     500        abspath_cwd_notlong = fileutil.abspath_expanduser_unicode(u".", long_path=False)
    500501        self.failUnless(isinstance(saved_cwd, unicode), saved_cwd)
    501502        self.failUnless(isinstance(abspath_cwd, unicode), abspath_cwd)
     
    504505        else:
    505506            self.failUnlessReallyEqual(abspath_cwd, saved_cwd)
     507        self.failUnlessReallyEqual(abspath_cwd_notlong, saved_cwd)
    506508
    507509        self.failUnlessReallyEqual(fileutil.to_windows_long_path(u"\\\\?\\foo"), u"\\\\?\\foo")
     
    532534            self.failUnlessReallyEqual(baz[4], bar[4])  # same drive
    533535
     536            baz_notlong = fileutil.abspath_expanduser_unicode(u"\\baz", long_path=False)
     537            self.failIf(baz_notlong.startswith(u"\\\\?\\"), baz_notlong)
     538            self.failUnlessReallyEqual(baz_notlong[1 :], u":\\baz")
     539
     540            bar_notlong = fileutil.abspath_expanduser_unicode(u"\\bar", base=baz_notlong, long_path=False)
     541            self.failIf(bar_notlong.startswith(u"\\\\?\\"), bar_notlong)
     542            self.failUnlessReallyEqual(bar_notlong[1 :], u":\\bar")
     543            # not u":\\baz\\bar", because \bar is absolute on the current drive.
     544
     545            self.failUnlessReallyEqual(baz_notlong[0], bar_notlong[0])  # same drive
     546
    534547        self.failIfIn(u"~", fileutil.abspath_expanduser_unicode(u"~"))
     548        self.failIfIn(u"~", fileutil.abspath_expanduser_unicode(u"~", long_path=False))
    535549
    536550        cwds = ['cwd']
     
    548562                    uabspath = fileutil.abspath_expanduser_unicode(upath)
    549563                    self.failUnless(isinstance(uabspath, unicode), uabspath)
     564
     565                    uabspath_notlong = fileutil.abspath_expanduser_unicode(upath, long_path=False)
     566                    self.failUnless(isinstance(uabspath_notlong, unicode), uabspath_notlong)
    550567            finally:
    551568                os.chdir(saved_cwd)
     569
     570    def test_make_dirs_with_absolute_mode(self):
     571        if sys.platform == 'win32':
     572            raise unittest.SkipTest("Permissions don't work the same on windows.")
     573
     574        workdir = fileutil.abspath_expanduser_unicode(u"test_make_dirs_with_absolute_mode")
     575        fileutil.make_dirs(workdir)
     576        abspath = fileutil.abspath_expanduser_unicode(u"a/b/c/d", base=workdir)
     577        fileutil.make_dirs_with_absolute_mode(workdir, abspath, 0766)
     578        new_mode = os.stat(os.path.join(workdir, "a", "b", "c", "d")).st_mode & 0777
     579        self.failUnlessEqual(new_mode, 0766)
     580        new_mode = os.stat(os.path.join(workdir, "a", "b", "c")).st_mode & 0777
     581        self.failUnlessEqual(new_mode, 0766)
     582        new_mode = os.stat(os.path.join(workdir, "a", "b")).st_mode & 0777
     583        self.failUnlessEqual(new_mode, 0766)
     584        new_mode = os.stat(os.path.join(workdir,"a")).st_mode & 0777
     585        self.failUnlessEqual(new_mode, 0766)
     586        new_mode = os.stat(workdir).st_mode & 0777
     587        self.failIfEqual(new_mode, 0766)
    552588
    553589    def test_create_long_path(self):
     
    604640        disk = fileutil.get_disk_stats('.', 2**128)
    605641        self.failUnlessEqual(disk['avail'], 0)
     642
     643    def test_get_pathinfo(self):
     644        basedir = "util/FileUtil/test_get_pathinfo"
     645        fileutil.make_dirs(basedir)
     646
     647        # create a directory
     648        self.mkdir(basedir, "a")
     649        dirinfo = fileutil.get_pathinfo(basedir)
     650        self.failUnlessTrue(dirinfo.isdir)
     651        self.failUnlessTrue(dirinfo.exists)
     652        self.failUnlessFalse(dirinfo.isfile)
     653        self.failUnlessFalse(dirinfo.islink)
     654
     655        # create a file
     656        f = os.path.join(basedir, "1.txt")
     657        fileutil.write(f, "a"*10)
     658        fileinfo = fileutil.get_pathinfo(f)
     659        self.failUnlessTrue(fileinfo.isfile)
     660        self.failUnlessTrue(fileinfo.exists)
     661        self.failUnlessFalse(fileinfo.isdir)
     662        self.failUnlessFalse(fileinfo.islink)
     663        self.failUnlessEqual(fileinfo.size, 10)
     664
     665        # path at which nothing exists
     666        dnename = os.path.join(basedir, "doesnotexist")
     667        now = time.time()
     668        dneinfo = fileutil.get_pathinfo(dnename, now=now)
     669        self.failUnlessFalse(dneinfo.exists)
     670        self.failUnlessFalse(dneinfo.isfile)
     671        self.failUnlessFalse(dneinfo.isdir)
     672        self.failUnlessFalse(dneinfo.islink)
     673        self.failUnlessEqual(dneinfo.size, None)
     674        self.failUnlessEqual(dneinfo.mtime, now)
     675        self.failUnlessEqual(dneinfo.ctime, now)
     676
     677    def test_get_pathinfo_symlink(self):
     678        if not hasattr(os, 'symlink'):
     679            raise unittest.SkipTest("can't create symlinks on this platform")
     680
     681        basedir = "util/FileUtil/test_get_pathinfo"
     682        fileutil.make_dirs(basedir)
     683
     684        f = os.path.join(basedir, "1.txt")
     685        fileutil.write(f, "a"*10)
     686
     687        # create a symlink pointing to 1.txt
     688        slname = os.path.join(basedir, "linkto1.txt")
     689        os.symlink(f, slname)
     690        symlinkinfo = fileutil.get_pathinfo(slname)
     691        self.failUnlessTrue(symlinkinfo.islink)
     692        self.failUnlessTrue(symlinkinfo.exists)
     693        self.failUnlessFalse(symlinkinfo.isfile)
     694        self.failUnlessFalse(symlinkinfo.isdir)
     695
    606696
    607697class PollMixinTests(unittest.TestCase):
  • TabularUnified src/allmydata/util/fileutil.py

    r9402b40d rc715e0d  
    44
    55import sys, exceptions, os, stat, tempfile, time, binascii
     6from collections import namedtuple
     7from errno import ENOENT
    68
    79if sys.platform == "win32":
    810    from ctypes import WINFUNCTYPE, WinError, windll, POINTER, byref, c_ulonglong, \
    911        create_unicode_buffer, get_last_error
    10     from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPWSTR
     12    from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPWSTR, LPVOID, HANDLE
    1113
    1214from twisted.python import log
    1315
    1416from pycryptopp.cipher.aes import AES
     17
     18from allmydata.util.assertutil import _assert
    1519
    1620
     
    141145        self.file.truncate(newsize)
    142146
     147def make_dirs_with_absolute_mode(parent, dirname, mode):
     148    """
     149    Make directory `dirname` and chmod it to `mode` afterwards.
     150    We chmod all parent directories of `dirname` until we reach
     151    `parent`.
     152    """
     153    precondition_abspath(parent)
     154    precondition_abspath(dirname)
     155    if not is_ancestor_path(parent, dirname):
     156        raise AssertionError("dirname must be a descendant of parent")
     157
     158    make_dirs(dirname)
     159    while dirname != parent:
     160        os.chmod(dirname, mode)
     161        # FIXME: doesn't seem to work on Windows for long paths
     162        old_dirname, dirname = dirname, os.path.dirname(dirname)
     163        _assert(len(dirname) < len(old_dirname), dirname=dirname, old_dirname=old_dirname)
     164
     165def is_ancestor_path(parent, dirname):
     166    while dirname != parent:
     167        # FIXME: doesn't seem to work on Windows for long paths
     168        old_dirname, dirname = dirname, os.path.dirname(dirname)
     169        if len(dirname) >= len(old_dirname):
     170            return False
     171    return True
    143172
    144173def make_dirs(dirname, mode=0777):
     
    280309    pass
    281310
    282 def abspath_expanduser_unicode(path, base=None):
     311def abspath_expanduser_unicode(path, base=None, long_path=True):
    283312    """
    284313    Return the absolute version of a path. If 'base' is given and 'path' is relative,
     
    287316    corresponding to an absolute path as returned by a previous call to
    288317    abspath_expanduser_unicode.
     318    On Windows, the result will be a long path unless long_path is given as False.
    289319    """
    290320    if not isinstance(path, unicode):
    291321        raise AssertionError("paths must be Unicode strings")
    292     if base is not None:
     322    if base is not None and long_path:
    293323        precondition_abspath(base)
    294324
     
    317347    path = os.path.normpath(path)
    318348
    319     if sys.platform == "win32":
     349    if sys.platform == "win32" and long_path:
    320350        path = to_windows_long_path(path)
    321351
     
    515545        log.msg("OS call to get disk statistics failed")
    516546        return 0
     547
     548
     549if sys.platform == "win32":
     550    # <http://msdn.microsoft.com/en-us/library/aa363858%28v=vs.85%29.aspx>
     551    CreateFileW = WINFUNCTYPE(
     552        HANDLE,  LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE,
     553        use_last_error=True
     554    )(("CreateFileW", windll.kernel32))
     555
     556    GENERIC_WRITE        = 0x40000000
     557    FILE_SHARE_READ      = 0x00000001
     558    FILE_SHARE_WRITE     = 0x00000002
     559    OPEN_EXISTING        = 3
     560    INVALID_HANDLE_VALUE = 0xFFFFFFFF
     561
     562    # <http://msdn.microsoft.com/en-us/library/aa364439%28v=vs.85%29.aspx>
     563    FlushFileBuffers = WINFUNCTYPE(
     564        BOOL,  HANDLE,
     565        use_last_error=True
     566    )(("FlushFileBuffers", windll.kernel32))
     567
     568    # <http://msdn.microsoft.com/en-us/library/ms724211%28v=vs.85%29.aspx>
     569    CloseHandle = WINFUNCTYPE(
     570        BOOL,  HANDLE,
     571        use_last_error=True
     572    )(("CloseHandle", windll.kernel32))
     573
     574    # <http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/4465cafb-f4ed-434f-89d8-c85ced6ffaa8/>
     575    def flush_volume(path):
     576        abspath = os.path.realpath(path)
     577        if abspath.startswith("\\\\?\\"):
     578            abspath = abspath[4 :]
     579        drive = os.path.splitdrive(abspath)[0]
     580
     581        print "flushing %r" % (drive,)
     582        hVolume = CreateFileW(u"\\\\.\\" + drive,
     583                              GENERIC_WRITE,
     584                              FILE_SHARE_READ | FILE_SHARE_WRITE,
     585                              None,
     586                              OPEN_EXISTING,
     587                              0,
     588                              None
     589                             )
     590        if hVolume == INVALID_HANDLE_VALUE:
     591            raise WinError(get_last_error())
     592
     593        if FlushFileBuffers(hVolume) == 0:
     594            raise WinError(get_last_error())
     595
     596        CloseHandle(hVolume)
     597else:
     598    def flush_volume(path):
     599        # use sync()?
     600        pass
     601
     602
     603class ConflictError(Exception):
     604    pass
     605
     606class UnableToUnlinkReplacementError(Exception):
     607    pass
     608
     609def reraise(wrapper):
     610    _, exc, tb = sys.exc_info()
     611    wrapper_exc = wrapper("%s: %s" % (exc.__class__.__name__, exc))
     612    raise wrapper_exc.__class__, wrapper_exc, tb
     613
     614if sys.platform == "win32":
     615    # <https://msdn.microsoft.com/en-us/library/windows/desktop/aa365512%28v=vs.85%29.aspx>
     616    ReplaceFileW = WINFUNCTYPE(
     617        BOOL,  LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID,
     618        use_last_error=True
     619    )(("ReplaceFileW", windll.kernel32))
     620
     621    REPLACEFILE_IGNORE_MERGE_ERRORS = 0x00000002
     622
     623    # <https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx>
     624    ERROR_FILE_NOT_FOUND = 2
     625
     626    def rename_no_overwrite(source_path, dest_path):
     627        os.rename(source_path, dest_path)
     628
     629    def replace_file(replaced_path, replacement_path, backup_path):
     630        precondition_abspath(replaced_path)
     631        precondition_abspath(replacement_path)
     632        precondition_abspath(backup_path)
     633
     634        r = ReplaceFileW(replaced_path, replacement_path, backup_path,
     635                         REPLACEFILE_IGNORE_MERGE_ERRORS, None, None)
     636        if r == 0:
     637            # The UnableToUnlinkReplacementError case does not happen on Windows;
     638            # all errors should be treated as signalling a conflict.
     639            err = get_last_error()
     640            if err != ERROR_FILE_NOT_FOUND:
     641                raise ConflictError("WinError: %s" % (WinError(err),))
     642
     643            try:
     644                rename_no_overwrite(replacement_path, replaced_path)
     645            except EnvironmentError:
     646                reraise(ConflictError)
     647else:
     648    def rename_no_overwrite(source_path, dest_path):
     649        # link will fail with EEXIST if there is already something at dest_path.
     650        os.link(source_path, dest_path)
     651        try:
     652            os.unlink(source_path)
     653        except EnvironmentError:
     654            reraise(UnableToUnlinkReplacementError)
     655
     656    def replace_file(replaced_path, replacement_path, backup_path):
     657        precondition_abspath(replaced_path)
     658        precondition_abspath(replacement_path)
     659        precondition_abspath(backup_path)
     660
     661        if not os.path.exists(replacement_path):
     662            raise ConflictError("Replacement file not found: %r" % (replacement_path,))
     663
     664        try:
     665            os.rename(replaced_path, backup_path)
     666        except OSError as e:
     667            if e.errno != ENOENT:
     668                raise
     669        try:
     670            rename_no_overwrite(replacement_path, replaced_path)
     671        except EnvironmentError:
     672            reraise(ConflictError)
     673
     674PathInfo = namedtuple('PathInfo', 'isdir isfile islink exists size mtime ctime')
     675
     676def get_pathinfo(path_u, now=None):
     677    try:
     678        statinfo = os.lstat(path_u)
     679        mode = statinfo.st_mode
     680        return PathInfo(isdir =stat.S_ISDIR(mode),
     681                        isfile=stat.S_ISREG(mode),
     682                        islink=stat.S_ISLNK(mode),
     683                        exists=True,
     684                        size  =statinfo.st_size,
     685                        mtime =statinfo.st_mtime,
     686                        ctime =statinfo.st_ctime,
     687                       )
     688    except OSError as e:
     689        if e.errno == ENOENT:
     690            if now is None:
     691                now = time.time()
     692            return PathInfo(isdir =False,
     693                            isfile=False,
     694                            islink=False,
     695                            exists=False,
     696                            size  =None,
     697                            mtime =now,
     698                            ctime =now,
     699                           )
     700        raise
Note: See TracChangeset for help on using the changeset viewer.