From 7376d186bc65b6cd094112ca4b9a96ed2b0b847f Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 1 Oct 2015 22:40:10 +0100 Subject: [PATCH 01/10] Windows fileutil improvements Signed-off-by: Daira Hopwood --- src/allmydata/util/fileutil.py | 141 +++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 152cae0bb8..8a044dd0b0 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -3,6 +3,8 @@ """ import sys, exceptions, os, stat, tempfile, time, binascii +from collections import namedtuple +from errno import ENOENT if sys.platform == "win32": from ctypes import WINFUNCTYPE, WinError, windll, POINTER, byref, c_ulonglong, \ @@ -514,3 +516,142 @@ def get_available_space(whichdir, reserved_space): except EnvironmentError: log.msg("OS call to get disk statistics failed") return 0 + + +if sys.platform == "win32": + from ctypes.wintypes import BOOL, HANDLE, DWORD, LPCWSTR, LPVOID, WinError, get_last_error + + # + CreateFileW = WINFUNCTYPE(HANDLE, LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE) \ + (("CreateFileW", windll.kernel32)) + + GENERIC_WRITE = 0x40000000 + FILE_SHARE_READ = 0x00000001 + FILE_SHARE_WRITE = 0x00000002 + OPEN_EXISTING = 3 + INVALID_HANDLE_VALUE = 0xFFFFFFFF + + # + FlushFileBuffers = WINFUNCTYPE(BOOL, HANDLE)(("FlushFileBuffers", windll.kernel32)) + + # + CloseHandle = WINFUNCTYPE(BOOL, HANDLE)(("CloseHandle", windll.kernel32)) + + # + def flush_volume(path): + drive = os.path.splitdrive(os.path.realpath(path))[0] + + hVolume = CreateFileW(u"\\\\.\\" + drive, + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + 0, + None + ) + if hVolume == INVALID_HANDLE_VALUE: + raise WinError() + + if FlushFileBuffers(hVolume) == 0: + raise WinError() + + CloseHandle(hVolume) +else: + def flush_volume(path): + # use sync()? + pass + + +class ConflictError(Exception): + pass + +class UnableToUnlinkReplacementError(Exception): + pass + +def reraise(wrapper): + _, exc, tb = sys.exc_info() + wrapper_exc = wrapper("%s: %s" % (exc.__class__.__name__, exc)) + raise wrapper_exc.__class__, wrapper_exc, tb + +if sys.platform == "win32": + from ctypes import WINFUNCTYPE, windll, WinError, get_last_error + from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPVOID + + # + ReplaceFileW = WINFUNCTYPE( + BOOL, + LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID, + use_last_error=True + )(("ReplaceFileW", windll.kernel32)) + + REPLACEFILE_IGNORE_MERGE_ERRORS = 0x00000002 + + def rename_no_overwrite(source_path, dest_path): + os.rename(source_path, dest_path) + + def replace_file(replaced_path, replacement_path, backup_path): + precondition_abspath(replaced_path) + precondition_abspath(replacement_path) + precondition_abspath(backup_path) + + r = ReplaceFileW(replaced_path, replacement_path, backup_path, + REPLACEFILE_IGNORE_MERGE_ERRORS, None, None) + if r == 0: + # The UnableToUnlinkReplacementError case does not happen on Windows; + # all errors should be treated as signalling a conflict. + err = get_last_error() + raise ConflictError("WinError: %s" % (WinError(err))) +else: + def rename_no_overwrite(source_path, dest_path): + # link will fail with EEXIST if there is already something at dest_path. + os.link(source_path, dest_path) + try: + os.unlink(source_path) + except EnvironmentError: + reraise(UnableToUnlinkReplacementError) + + def replace_file(replaced_path, replacement_path, backup_path): + precondition_abspath(replaced_path) + precondition_abspath(replacement_path) + precondition_abspath(backup_path) + + if not os.path.exists(replacement_path): + raise ConflictError("Replacement file not found: %r" % (replacement_path,)) + + try: + os.rename(replaced_path, backup_path) + except OSError as e: + if e.errno != ENOENT: + raise + try: + rename_no_overwrite(replacement_path, replaced_path) + except EnvironmentError: + reraise(ConflictError) + +PathInfo = namedtuple('PathInfo', 'isdir isfile islink exists size mtime ctime') + +def get_pathinfo(path_u, now=None): + try: + statinfo = os.lstat(path_u) + mode = statinfo.st_mode + return PathInfo(isdir =stat.S_ISDIR(mode), + isfile=stat.S_ISREG(mode), + islink=stat.S_ISLNK(mode), + exists=True, + size =statinfo.st_size, + mtime =statinfo.st_mtime, + ctime =statinfo.st_ctime, + ) + except OSError as e: + if e.errno == ENOENT: + if now is None: + now = time.time() + return PathInfo(isdir =False, + isfile=False, + islink=False, + exists=False, + size =None, + mtime =now, + ctime =now, + ) + raise From f49b1338997b6307754a8a9c2b049423d2aca3b7 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 8 Oct 2015 16:01:46 +0100 Subject: [PATCH 02/10] Re-order some imports in fileutil Signed-off-by: Daira Hopwood --- src/allmydata/util/fileutil.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 8a044dd0b0..8a5000d39f 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -9,7 +9,7 @@ if sys.platform == "win32": from ctypes import WINFUNCTYPE, WinError, windll, POINTER, byref, c_ulonglong, \ create_unicode_buffer, get_last_error - from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPWSTR + from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPWSTR, LPVOID, HANDLE from twisted.python import log @@ -519,8 +519,6 @@ def get_available_space(whichdir, reserved_space): if sys.platform == "win32": - from ctypes.wintypes import BOOL, HANDLE, DWORD, LPCWSTR, LPVOID, WinError, get_last_error - # CreateFileW = WINFUNCTYPE(HANDLE, LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE) \ (("CreateFileW", windll.kernel32)) @@ -574,9 +572,6 @@ def reraise(wrapper): raise wrapper_exc.__class__, wrapper_exc, tb if sys.platform == "win32": - from ctypes import WINFUNCTYPE, windll, WinError, get_last_error - from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPVOID - # ReplaceFileW = WINFUNCTYPE( BOOL, From b7d7b0a4b13e05885c467deb73b09a7ca17692a2 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 23 Oct 2015 21:58:39 +0100 Subject: [PATCH 03/10] Add long_path=False option to abspath_expanduser_unicode. Signed-off-by: Daira Hopwood --- src/allmydata/test/test_util.py | 17 +++++++++++++++++ src/allmydata/util/fileutil.py | 5 +++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index 55ff66b1ab..634a2d1714 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -497,12 +497,14 @@ def test_abspath_expanduser_unicode(self): saved_cwd = os.path.normpath(os.getcwdu()) abspath_cwd = fileutil.abspath_expanduser_unicode(u".") + abspath_cwd_notlong = fileutil.abspath_expanduser_unicode(u".", long_path=False) self.failUnless(isinstance(saved_cwd, unicode), saved_cwd) self.failUnless(isinstance(abspath_cwd, unicode), abspath_cwd) if sys.platform == "win32": self.failUnlessReallyEqual(abspath_cwd, fileutil.to_windows_long_path(saved_cwd)) else: self.failUnlessReallyEqual(abspath_cwd, saved_cwd) + self.failUnlessReallyEqual(abspath_cwd_notlong, saved_cwd) self.failUnlessReallyEqual(fileutil.to_windows_long_path(u"\\\\?\\foo"), u"\\\\?\\foo") self.failUnlessReallyEqual(fileutil.to_windows_long_path(u"\\\\.\\foo"), u"\\\\.\\foo") @@ -531,7 +533,19 @@ def test_abspath_expanduser_unicode(self): self.failUnlessReallyEqual(baz[4], bar[4]) # same drive + baz_notlong = fileutil.abspath_expanduser_unicode(u"\\baz", long_path=False) + self.failIf(baz_notlong.startswith(u"\\\\?\\"), baz_notlong) + self.failUnlessReallyEqual(baz_notlong[1 :], u":\\baz") + + bar_notlong = fileutil.abspath_expanduser_unicode(u"\\bar", base=baz, long_path=False) + self.failIf(bar_notlong.startswith(u"\\\\?\\"), bar_notlong) + self.failUnlessReallyEqual(bar_notlong[1 :], u":\\bar") + # not u":\\baz\\bar", because \bar is absolute on the current drive. + + self.failUnlessReallyEqual(baz_notlong[0], bar_notlong[0]) # same drive + self.failIfIn(u"~", fileutil.abspath_expanduser_unicode(u"~")) + self.failIfIn(u"~", fileutil.abspath_expanduser_unicode(u"~", long_path=False)) cwds = ['cwd'] try: @@ -547,6 +561,9 @@ def test_abspath_expanduser_unicode(self): for upath in (u'', u'fuu', u'f\xf9\xf9', u'/fuu', u'U:\\', u'~'): uabspath = fileutil.abspath_expanduser_unicode(upath) self.failUnless(isinstance(uabspath, unicode), uabspath) + + uabspath_notlong = fileutil.abspath_expanduser_unicode(upath, long_path=False) + self.failUnless(isinstance(uabspath_notlong, unicode), uabspath_notlong) finally: os.chdir(saved_cwd) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 8a5000d39f..4d1c8dbab9 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -281,13 +281,14 @@ def precondition_abspath(path): except ImportError: pass -def abspath_expanduser_unicode(path, base=None): +def abspath_expanduser_unicode(path, base=None, long_path=True): """ Return the absolute version of a path. If 'base' is given and 'path' is relative, the path will be expanded relative to 'base'. 'path' must be a Unicode string. 'base', if given, must be a Unicode string corresponding to an absolute path as returned by a previous call to abspath_expanduser_unicode. + On Windows, the result will be a long path unless long_path is given as False. """ if not isinstance(path, unicode): raise AssertionError("paths must be Unicode strings") @@ -318,7 +319,7 @@ def abspath_expanduser_unicode(path, base=None): # there is always at least one Unicode path component. path = os.path.normpath(path) - if sys.platform == "win32": + if sys.platform == "win32" and long_path: path = to_windows_long_path(path) return path From a1710dd6e6515069aa0a034bdcb8c4e94ef71717 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sat, 24 Oct 2015 01:14:18 +0100 Subject: [PATCH 04/10] Fix fileutil tests. Signed-off-by: Daira Hopwood --- src/allmydata/test/test_util.py | 56 ++++++++++++++++++++++++++++++++- src/allmydata/util/fileutil.py | 2 +- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index 634a2d1714..ff68500811 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -537,7 +537,7 @@ def test_abspath_expanduser_unicode(self): self.failIf(baz_notlong.startswith(u"\\\\?\\"), baz_notlong) self.failUnlessReallyEqual(baz_notlong[1 :], u":\\baz") - bar_notlong = fileutil.abspath_expanduser_unicode(u"\\bar", base=baz, long_path=False) + bar_notlong = fileutil.abspath_expanduser_unicode(u"\\bar", base=baz_notlong, long_path=False) self.failIf(bar_notlong.startswith(u"\\\\?\\"), bar_notlong) self.failUnlessReallyEqual(bar_notlong[1 :], u":\\bar") # not u":\\baz\\bar", because \bar is absolute on the current drive. @@ -621,6 +621,60 @@ def test_disk_stats_avail_nonnegative(self): disk = fileutil.get_disk_stats('.', 2**128) self.failUnlessEqual(disk['avail'], 0) + def test_get_pathinfo(self): + basedir = "util/FileUtil/test_get_pathinfo" + fileutil.make_dirs(basedir) + + # create a directory + self.mkdir(basedir, "a") + dirinfo = fileutil.get_pathinfo(basedir) + self.failUnlessTrue(dirinfo.isdir) + self.failUnlessTrue(dirinfo.exists) + self.failUnlessFalse(dirinfo.isfile) + self.failUnlessFalse(dirinfo.islink) + + # create a file + f = os.path.join(basedir, "1.txt") + fileutil.write(f, "a"*10) + fileinfo = fileutil.get_pathinfo(f) + self.failUnlessTrue(fileinfo.isfile) + self.failUnlessTrue(fileinfo.exists) + self.failUnlessFalse(fileinfo.isdir) + self.failUnlessFalse(fileinfo.islink) + self.failUnlessEqual(fileinfo.size, 10) + + # path at which nothing exists + dnename = os.path.join(basedir, "doesnotexist") + now = time.time() + dneinfo = fileutil.get_pathinfo(dnename, now=now) + self.failUnlessFalse(dneinfo.exists) + self.failUnlessFalse(dneinfo.isfile) + self.failUnlessFalse(dneinfo.isdir) + self.failUnlessFalse(dneinfo.islink) + self.failUnlessEqual(dneinfo.size, None) + self.failUnlessEqual(dneinfo.mtime, now) + self.failUnlessEqual(dneinfo.ctime, now) + + def test_get_pathinfo_symlink(self): + if not hasattr(os, 'symlink'): + raise unittest.SkipTest("can't create symlinks on this platform") + + basedir = "util/FileUtil/test_get_pathinfo" + fileutil.make_dirs(basedir) + + f = os.path.join(basedir, "1.txt") + fileutil.write(f, "a"*10) + + # create a symlink pointing to 1.txt + slname = os.path.join(basedir, "linkto1.txt") + os.symlink(f, slname) + symlinkinfo = fileutil.get_pathinfo(slname) + self.failUnlessTrue(symlinkinfo.islink) + self.failUnlessTrue(symlinkinfo.exists) + self.failUnlessFalse(symlinkinfo.isfile) + self.failUnlessFalse(symlinkinfo.isdir) + + class PollMixinTests(unittest.TestCase): def setUp(self): self.pm = pollmixin.PollMixin() diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 4d1c8dbab9..2315202610 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -292,7 +292,7 @@ def abspath_expanduser_unicode(path, base=None, long_path=True): """ if not isinstance(path, unicode): raise AssertionError("paths must be Unicode strings") - if base is not None: + if base is not None and long_path: precondition_abspath(base) path = expanduser(path) From c8c380a462a56c12286e1fa54bb23c48ad039790 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sat, 24 Oct 2015 01:14:56 +0100 Subject: [PATCH 05/10] replace_file should allow the replaced file not to exist on Windows. Signed-off-by: Daira Hopwood --- src/allmydata/util/fileutil.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 2315202610..29e6d1b194 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -582,6 +582,9 @@ def reraise(wrapper): REPLACEFILE_IGNORE_MERGE_ERRORS = 0x00000002 + # + ERROR_FILE_NOT_FOUND = 2 + def rename_no_overwrite(source_path, dest_path): os.rename(source_path, dest_path) @@ -596,7 +599,13 @@ def replace_file(replaced_path, replacement_path, backup_path): # The UnableToUnlinkReplacementError case does not happen on Windows; # all errors should be treated as signalling a conflict. err = get_last_error() - raise ConflictError("WinError: %s" % (WinError(err))) + if err != ERROR_FILE_NOT_FOUND: + raise ConflictError("WinError: %s" % (WinError(err),)) + + try: + rename_no_overwrite(replacement_path, replaced_path) + except EnvironmentError: + reraise(ConflictError) else: def rename_no_overwrite(source_path, dest_path): # link will fail with EEXIST if there is already something at dest_path. From fc507f2ef56fbc53d3e06f63d9e6f549992b1c3e Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 27 Oct 2015 14:47:11 +0000 Subject: [PATCH 06/10] Improve all of the Windows-specific error reporting. Also make the Windows function declarations more readable and consistent. Signed-off-by: Daira Hopwood --- src/allmydata/util/fileutil.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 29e6d1b194..1133c261c7 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -521,8 +521,10 @@ def get_available_space(whichdir, reserved_space): if sys.platform == "win32": # - CreateFileW = WINFUNCTYPE(HANDLE, LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE) \ - (("CreateFileW", windll.kernel32)) + CreateFileW = WINFUNCTYPE( + HANDLE, LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE, + use_last_error=True + )(("CreateFileW", windll.kernel32)) GENERIC_WRITE = 0x40000000 FILE_SHARE_READ = 0x00000001 @@ -531,10 +533,16 @@ def get_available_space(whichdir, reserved_space): INVALID_HANDLE_VALUE = 0xFFFFFFFF # - FlushFileBuffers = WINFUNCTYPE(BOOL, HANDLE)(("FlushFileBuffers", windll.kernel32)) + FlushFileBuffers = WINFUNCTYPE( + BOOL, HANDLE, + use_last_error=True + )(("FlushFileBuffers", windll.kernel32)) # - CloseHandle = WINFUNCTYPE(BOOL, HANDLE)(("CloseHandle", windll.kernel32)) + CloseHandle = WINFUNCTYPE( + BOOL, HANDLE, + use_last_error=True + )(("CloseHandle", windll.kernel32)) # def flush_volume(path): @@ -549,10 +557,10 @@ def flush_volume(path): None ) if hVolume == INVALID_HANDLE_VALUE: - raise WinError() + raise WinError(get_last_error()) if FlushFileBuffers(hVolume) == 0: - raise WinError() + raise WinError(get_last_error()) CloseHandle(hVolume) else: @@ -575,10 +583,9 @@ def reraise(wrapper): if sys.platform == "win32": # ReplaceFileW = WINFUNCTYPE( - BOOL, - LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID, + BOOL, LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID, use_last_error=True - )(("ReplaceFileW", windll.kernel32)) + )(("ReplaceFileW", windll.kernel32)) REPLACEFILE_IGNORE_MERGE_ERRORS = 0x00000002 From 2c660e0d82666e761885c615eb319ba89679d22b Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 27 Oct 2015 21:39:05 +0000 Subject: [PATCH 07/10] Strip any long path marker in the input to flush_volume. Signed-off-by: Daira Hopwood --- src/allmydata/util/fileutil.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 1133c261c7..0366daabf6 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -546,8 +546,12 @@ def get_available_space(whichdir, reserved_space): # def flush_volume(path): - drive = os.path.splitdrive(os.path.realpath(path))[0] + abspath = os.path.realpath(path) + if abspath.startswith("\\\\?\\"): + abspath = abspath[4 :] + drive = os.path.splitdrive(abspath)[0] + print "flushing %r" % (drive,) hVolume = CreateFileW(u"\\\\.\\" + drive, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, From 2f75c388ee36e871b4f7590024c1669388ab311e Mon Sep 17 00:00:00 2001 From: David Stainton Date: Thu, 21 Jan 2016 19:34:24 +0100 Subject: [PATCH 08/10] Add make_dirs_with_absolute_mode to fileutils --- src/allmydata/test/test_util.py | 15 +++++++++++++++ src/allmydata/util/fileutil.py | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index ff68500811..f9c251353a 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -567,6 +567,21 @@ def test_abspath_expanduser_unicode(self): finally: os.chdir(saved_cwd) + def test_make_dirs_with_absolute_mode(self): + workdir = u"test_make_dirs_with_absolute_mode" + fileutil.make_dirs(workdir) + fileutil.make_dirs_with_absolute_mode(workdir, os.path.join(workdir,"a/b/c/d"), 0766) + new_mode = os.stat(os.path.join(workdir,"a/b/c/d")).st_mode & 0777 + self.failUnlessEqual(new_mode, 0766) + new_mode = os.stat(os.path.join(workdir,"a/b/c")).st_mode & 0777 + self.failUnlessEqual(new_mode, 0766) + new_mode = os.stat(os.path.join(workdir,"a/b")).st_mode & 0777 + self.failUnlessEqual(new_mode, 0766) + new_mode = os.stat(os.path.join(workdir,"a")).st_mode & 0777 + self.failUnlessEqual(new_mode, 0766) + new_mode = os.stat(workdir).st_mode & 0777 + self.failIfEqual(new_mode, 0766) + def test_create_long_path(self): workdir = u"test_create_long_path" fileutil.make_dirs(workdir) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 0366daabf6..70075bba37 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -142,6 +142,31 @@ def truncate(self, newsize): old end-of-file are unspecified. The file position after this operation is unspecified.""" self.file.truncate(newsize) +def make_dirs_with_absolute_mode(parent, dirname, mode): + """ + Make directory `dirname` and chmod it to `mode` afterwards. + We chmod all parent directories of `dirname` until we reach + `parent`. + """ + precondition_abspath(parent) + precondition_abspath(dirname) + if not is_ancestor_path(parent, dirname): + raise AssertionError("dirname must be a descendant of parent") + + make_dirs(dirname) + while dirname != parent: + os.chmod(dirname, mode) + # FIXME: doesn't seem to work on Windows for long paths + old_dirname, dirname = dirname, os.path.dirname(dirname) + _assert(len(dirname) < len(old_dirname), dirname=dirname, old_dirname=old_dirname) + +def is_ancestor_path(parent, dirname): + while dirname != parent: + # FIXME: doesn't seem to work on Windows for long paths + old_dirname, dirname = dirname, os.path.dirname(dirname) + if len(dirname) >= len(old_dirname): + return False + return True def make_dirs(dirname, mode=0777): """ From c39e736118617d1ceed0d9bb773e3a309989e185 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Mon, 15 Feb 2016 15:29:29 +0000 Subject: [PATCH 09/10] Fix test to use arguments with absolute unicode paths --- src/allmydata/test/test_util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index f9c251353a..c542963165 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -568,9 +568,10 @@ def test_abspath_expanduser_unicode(self): os.chdir(saved_cwd) def test_make_dirs_with_absolute_mode(self): - workdir = u"test_make_dirs_with_absolute_mode" + workdir = fileutil.abspath_expanduser_unicode(u"test_make_dirs_with_absolute_mode") fileutil.make_dirs(workdir) - fileutil.make_dirs_with_absolute_mode(workdir, os.path.join(workdir,"a/b/c/d"), 0766) + abspath = fileutil.abspath_expanduser_unicode(u"a/b/c/d", base=workdir) + fileutil.make_dirs_with_absolute_mode(workdir, abspath, 0766) new_mode = os.stat(os.path.join(workdir,"a/b/c/d")).st_mode & 0777 self.failUnlessEqual(new_mode, 0766) new_mode = os.stat(os.path.join(workdir,"a/b/c")).st_mode & 0777 From c4e969fceb38e735461d2afc97a3ed3e8862f687 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Mon, 15 Feb 2016 15:29:06 +0000 Subject: [PATCH 10/10] Fix missing import --- src/allmydata/util/fileutil.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 70075bba37..f24f32d286 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -15,6 +15,8 @@ from pycryptopp.cipher.aes import AES +from allmydata.util.assertutil import _assert + def rename(src, dst, tries=4, basedelay=0.1): """ Here is a superkludge to workaround the fact that occasionally on