From e58850eb05b237e79f53b779d801d7f880fa6cc7 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Dec 19 2016 17:28:10 +0000 Subject: [PATCH 1/5] first stab at rmtree for long filenames --- diff --git a/koji/util.py b/koji/util.py index 563703d..e16a6a0 100644 --- a/koji/util.py +++ b/koji/util.py @@ -319,6 +319,56 @@ def rmtree(path): os.rmdir(dirpath) +def rmtree2(path): + """Reimplement to avoid forming long paths""" + st = os.lstat(path) + if not stat.S_ISDIR(st.st_mode): + raise koji.GenericError, "Not a directory: %s" % path + dev = st.st_dev + cwd = os.getcwd() + try: + os.chdir(path) + _rmtree2(dev) + finally: + os.chdir(cwd) + os.rmdir(path) + + +def _rmtree2(dev): + dirstack = [] + while True: + dirs = _stripcwd(dev) + # if no dirs, walk back up until we find some + while not dirs and dirstack: + os.chdir('..') + dirs = dirstack.pop() + empty_dir = dirs.pop() + os.rmdir(empty_dir) + if not dirs: + # we are done + break + # otherwise go deeper + subdir = dirs[-1] + # note: we do not pop here because we need to remember to remove subdir later + dirstack.append(dirs) + os.chdir(subdir) + + +def _stripcwd(dev): + """Unlink all files in cwd and return list of subdirs""" + dirs = [] + for fn in os.listdir('.'): + st = os.lstat(fn) + if st.st_dev != dev: + # don't cross fs boundary + continue + if stat.S_ISDIR(st.st_mode): + dirs.append(fn) + else: + os.unlink(fn) + return dirs + + def safer_move(src, dst): """Rename if possible, copy+rm otherwise From 949f23dd7b11021e761b8d0bf7230c78504fef58 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Dec 19 2016 17:28:10 +0000 Subject: [PATCH 2/5] add a note --- diff --git a/koji/util.py b/koji/util.py index e16a6a0..788a19c 100644 --- a/koji/util.py +++ b/koji/util.py @@ -320,7 +320,9 @@ def rmtree(path): def rmtree2(path): - """Reimplement to avoid forming long paths""" + """Delete a directory tree without crossing fs boundaries""" + # implemented to avoid forming long paths + # see: https://pagure.io/koji/issue/201 st = os.lstat(path) if not stat.S_ISDIR(st.st_mode): raise koji.GenericError, "Not a directory: %s" % path From 8f42fda97de52afc9ea0c683ff9e853a2a8a930a Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Dec 19 2016 17:28:10 +0000 Subject: [PATCH 3/5] keep going on errors --- diff --git a/koji/util.py b/koji/util.py index 788a19c..95f63bf 100644 --- a/koji/util.py +++ b/koji/util.py @@ -345,7 +345,11 @@ def _rmtree2(dev): os.chdir('..') dirs = dirstack.pop() empty_dir = dirs.pop() - os.rmdir(empty_dir) + try: + os.rmdir(empty_dir) + except OSError: + # we'll still fail at the top level + pass if not dirs: # we are done break @@ -367,7 +371,11 @@ def _stripcwd(dev): if stat.S_ISDIR(st.st_mode): dirs.append(fn) else: - os.unlink(fn) + try: + os.unlink(fn) + except OSError: + # we'll still fail at the top level + pass return dirs From d4590637a1cc08edf33c2e453e28b72ed6abcbf4 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Dec 19 2016 17:28:10 +0000 Subject: [PATCH 4/5] replace old rmtree with the new one --- diff --git a/koji/util.py b/koji/util.py index 95f63bf..3091002 100644 --- a/koji/util.py +++ b/koji/util.py @@ -282,45 +282,6 @@ def lazysetattr(object, name, func, args, kwargs=None, cache=False): def rmtree(path): """Delete a directory tree without crossing fs boundaries""" - st = os.lstat(path) - if not stat.S_ISDIR(st.st_mode): - raise koji.GenericError, "Not a directory: %s" % path - dev = st.st_dev - dirlist = [] - for dirpath, dirnames, filenames in os.walk(path): - dirlist.append(dirpath) - newdirs = [] - dirsyms = [] - for fn in dirnames: - path = os.path.join(dirpath, fn) - st = os.lstat(path) - if st.st_dev != dev: - # don't cross fs boundary - continue - if stat.S_ISLNK(st.st_mode): - #os.walk includes symlinks to dirs here - dirsyms.append(fn) - continue - newdirs.append(fn) - #only walk our filtered dirs - dirnames[:] = newdirs - for fn in filenames + dirsyms: - path = os.path.join(dirpath, fn) - st = os.lstat(path) - if st.st_dev != dev: - #shouldn't happen, but just to be safe... - continue - os.unlink(path) - dirlist.reverse() - for dirpath in dirlist: - if os.listdir(dirpath): - # dir not empty. could happen if a mount was present - continue - os.rmdir(dirpath) - - -def rmtree2(path): - """Delete a directory tree without crossing fs boundaries""" # implemented to avoid forming long paths # see: https://pagure.io/koji/issue/201 st = os.lstat(path) @@ -330,13 +291,13 @@ def rmtree2(path): cwd = os.getcwd() try: os.chdir(path) - _rmtree2(dev) + _rmtree(dev) finally: os.chdir(cwd) os.rmdir(path) -def _rmtree2(dev): +def _rmtree(dev): dirstack = [] while True: dirs = _stripcwd(dev) From ab99443414918f19417d5ee5d8616ec3838f3623 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Dec 19 2016 17:31:01 +0000 Subject: [PATCH 5/5] Update exception syntax in rmtree() --- diff --git a/koji/util.py b/koji/util.py index 3091002..50d203d 100644 --- a/koji/util.py +++ b/koji/util.py @@ -286,7 +286,7 @@ def rmtree(path): # see: https://pagure.io/koji/issue/201 st = os.lstat(path) if not stat.S_ISDIR(st.st_mode): - raise koji.GenericError, "Not a directory: %s" % path + raise koji.GenericError("Not a directory: %s" % path) dev = st.st_dev cwd = os.getcwd() try: