From eb37c52e8cd9b42882b0a4794bd2971e8fe1595b Mon Sep 17 00:00:00 2001 From: Matteo Cypriani Date: Tue, 9 Apr 2013 00:49:25 -0400 Subject: [PATCH] [file_utils] unln: handle exceptions, list errors --- file_utils/README | 5 +++++ file_utils/unln.py | 48 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/file_utils/README b/file_utils/README index c6bb0a2..b7f247d 100644 --- a/file_utils/README +++ b/file_utils/README @@ -25,6 +25,11 @@ equivalent of (cp -p file tmp && mv tmp file). Checks are done in order to work only on regular files which have more than one hard link, so the minimal amount of copies are done. +A summary of the file names on which error were raised is displayed at +the end of the execution. Note that if a file is under a directory which +is not executable (i.e. the program can't cd in), it will be considered +as inexistant and ignored, and its name won't be displayed. + /!\ Warning: the copy function used is shutil.copy2(), which tries to preserve all the metadata it can, but as of Python 3.3 it is not guaranteed that extended attributes will be preserved. See the diff --git a/file_utils/unln.py b/file_utils/unln.py index 43ca640..d404c8b 100755 --- a/file_utils/unln.py +++ b/file_utils/unln.py @@ -29,10 +29,8 @@ # inodes by doing the equivalent of (cp -p file tmp && mv tmp file). # # TODO: -# - Handle exceptions (permission denied on copy, etc.) # - Option verbose # - Option to follow symlinks -# - Summarise skipped regular files with more than one link import sys @@ -90,6 +88,10 @@ usage_string = "Usage:\n\t{} [file2 ...]".format(progname) # Error codes ERR_BAD_USAGE = 1 +# List of filenames that were supposed to be worked on but could not because of +# an error +error_filenames = [] + # Check command-line arguments if len(sys.argv) < 2: @@ -123,17 +125,31 @@ for filename in sys.argv[1:]: # Reserve a temporary file name dirname = os.path.dirname(filename) - handle, tmpfilename = tempfile.mkstemp( - prefix="{}-".format(filename), - suffix="-{}.tmp".format(progname), - dir=dirname) + try: + handle, tmpfilename = tempfile.mkstemp( + prefix="{}-".format(filename), + suffix="-{}.tmp".format(progname), + dir=dirname) + except: + warn("Cannot create temporary file to copy {}:".format(quote(filename)), + sys.exc_info()[1]) + error_filenames.append(filename) + continue os.close(handle) # we just need a temporary file name # Copy the original file to the temporary file verbose("Copying to temporary file", quote(tmpfilename), end="... ") - shutil.copy2(filename, tmpfilename) # cp -p + try: + shutil.copy2(filename, tmpfilename) # cp -p + except: + warn("Cannot copy {} to {}:".format(quote(filename), quote(tmpfilename)), + sys.exc_info()[1]) + error_filenames.append(filename) + # Delete temporary file + os.remove(tmpfilename) + continue - # Try to sync if we are running Python >= 3.3 + # Sync (if we are running Python >= 3.3) if sys.version_info.minor >= 3: verbose("Syncing", end="... ") os.sync() @@ -141,4 +157,18 @@ for filename in sys.argv[1:]: # Rename the temporary file with the original file name verbose("Moving back {} to {}...".format( quote(tmpfilename), quote(filename))) - os.rename(tmpfilename, filename) + try: + os.rename(tmpfilename, filename) + except: + warn("Cannot move {} to {}:".format( + quote(tmpfilename), quote(filename)), + sys.exc_info()[1], "Deleting temporary file", quote(tmpfilename)) + error_filenames.append(filename) + # Delete temporary file + os.remove(tmpfilename) + + +# Display the list of files in error +if len(error_filenames): + warn("The following regular files still have more than one hard link:") + print(*error_filenames, sep="\n", file=sys.stderr)