Skip to content

Commit

Permalink
Stop crash when certain files are encountered
Browse files Browse the repository at this point in the history
When special files such as named pipes and files which the user didn't
have read permission for were encountered, crashes would occur because
exceptions raised by the subprocesses executing 'chattr' and 'lsattr'
were not being handled well.

The commit stops those crashes.
  • Loading branch information
countermeasure committed Aug 23, 2020
1 parent c31df97 commit f2a3cfe
Show file tree
Hide file tree
Showing 15 changed files with 643 additions and 124 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Unreleased
Fixed
~~~~~
- Clear progress bar before printing a path when listing files.
- Stopped crash caused by special files and files with certain permissions.


v0.3.0
Expand Down
3 changes: 3 additions & 0 deletions entomb/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ def _get_path_type(path):
path_type = constants.FILE
elif os.path.isdir(path):
path_type = constants.DIRECTORY
else:
# For all others, use a generic term.
path_type = "path"

return path_type

Expand Down
8 changes: 6 additions & 2 deletions entomb/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
class ProcessingError(Exception):
"""Raise when a subprocess call exits with a non-zero status."""
class GetAttributeError(Exception):
"""Raise when the immutable attribute cannot be accessed."""


class SetAttributeError(Exception):
"""Raise when the immutable attribute cannot be set."""
15 changes: 11 additions & 4 deletions entomb/listing.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import datetime
import os

from entomb import utilities
from entomb import (
exceptions,
utilities,
)


@utilities.hide_cursor()
Expand Down Expand Up @@ -95,6 +98,9 @@ def _print_the_path(path, immutable):
is immutable, or if the immutable parameter is False and the file is
mutable.
The path should not be printed if the immutability attribute can't be
accessed.
Parameters
----------
path : str
Expand All @@ -111,16 +117,17 @@ def _print_the_path(path, immutable):
------
AssertionError
If the path is a directory, is a link or does not exist.
ProcessingError
If the path's immutable attribute cannot be accessed.
"""
# Parameter check.
assert not os.path.isdir(path)
assert not os.path.islink(path)
assert os.path.exists(path)
try:
is_immutable = utilities.file_is_immutable(path)
except exceptions.GetAttributeError:
return False

is_immutable = utilities.file_is_immutable(path)
print_immutable_file_path = is_immutable and immutable
print_mutable_file_path = not is_immutable and not immutable

Expand Down
61 changes: 41 additions & 20 deletions entomb/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def process_objects(path, immutable, include_git, dry_run):

# Set up.
attribute_changed_count = 0
attribute_settable_count = 0
errors = []
file_count = 0
link_count = 0
Expand Down Expand Up @@ -65,21 +66,21 @@ def process_objects(path, immutable, include_git, dry_run):
link_count += 1

else:
# Work out if the file's attribute needs to change.
is_immutable = utilities.file_is_immutable(file_path)
change_attribute = immutable != is_immutable

# Change the file's attribute if necessary.
if change_attribute and not dry_run:
try:
_process_object(file_path, immutable)
except exceptions.ProcessingError as exception:
errors.append(exception)
try:
attribute_was_changed = _process_object(
file_path,
immutable,
dry_run,
)
attribute_settable_count += 1
if attribute_was_changed:
attribute_changed_count += 1
except exceptions.SetAttributeError as error:
errors.append(error)

# Count the file.
file_count += 1
if change_attribute:
attribute_changed_count += 1

# Update the progress bar.
utilities.print_progress_bar(
Expand All @@ -100,7 +101,10 @@ def process_objects(path, immutable, include_git, dry_run):
# Print a summary.
utilities.print_header("Summary")
if file_count > 0:
print("All {} files are now {}".format(file_count, operation))
print(
"All {} files for which immutability can be set are now {}"
.format(attribute_settable_count, operation),
)
print("All {} links were ignored".format(link_count))
else:
print("No files were found")
Expand Down Expand Up @@ -144,7 +148,7 @@ def _print_errors(errors):
print()


def _process_object(path, immutable):
def _process_object(path, immutable, dry_run):
"""Set or unset the immutable attribute for a file.
Parameters
Expand All @@ -153,16 +157,20 @@ def _process_object(path, immutable):
The absolute path of a file.
immutable: bool
Set immutable attribute if True, unset immutable attribute if False.
dry_run : bool
Whether to do a dry run which makes no changes.
Returns
-------
None
bool
Whether the immutable attribute was changed, or if this was a dry run,
should have been changed.
Raises
------
AssertionError
If the path is a directory, is a link or does not exist.
ProcessingError
SetAttributeError
If the path's immutable attribute cannot be set.
"""
Expand All @@ -171,9 +179,22 @@ def _process_object(path, immutable):
assert not os.path.islink(path)
assert os.path.exists(path)

attribute = "+i" if immutable else "-i"
try:
is_immutable = utilities.file_is_immutable(path)
except exceptions.GetAttributeError:
msg = "Immutable attribute not settable for {}".format(path)
raise exceptions.SetAttributeError(msg)

change_attribute = immutable != is_immutable

if change_attribute and not dry_run:
attribute = "+i" if immutable else "-i"
_set_attribute(attribute, path)

_set_attribute(attribute, path)
# The value of change_attribute is a proxy for whether the immutable
# attribute was changed, or if this was a dry run, should have been
# changed.
return change_attribute


def _set_attribute(attribute, path):
Expand All @@ -192,7 +213,7 @@ def _set_attribute(attribute, path):
Raises
------
ProcessingError
SetAttributeError
If the exit status of the chattr command is non-zero.
"""
Expand All @@ -204,5 +225,5 @@ def _set_attribute(attribute, path):
stdout=subprocess.DEVNULL,
)
except subprocess.CalledProcessError:
msg = "The 'chattr' command failed for '{}'".format(path)
raise exceptions.ProcessingError(msg)
msg = "Immutable attribute not settable for {}".format(path)
raise exceptions.SetAttributeError(msg)
68 changes: 47 additions & 21 deletions entomb/reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from entomb import (
constants,
exceptions,
utilities,
)

Expand Down Expand Up @@ -35,16 +36,17 @@ def produce_report(path, include_git):
# Set up.
directory_count = 0
immutable_file_count = 0
inaccessible_file_count = 0
link_count = 0
mutable_file_count = 0

# Print the operation.
print("Produce report")
print()

# If the path is a file or link, print an abbreviated report then return.
if os.path.isfile(path):
_print_file_or_link_report(path)
# If the path is not a directory, print an abbreviated report then return.
if not os.path.isdir(path):
_print_abbreviated_report(path)
return

# Print the progress header and set up the progress bar.
Expand Down Expand Up @@ -73,15 +75,24 @@ def produce_report(path, include_git):

# Count the file.
else:
if utilities.file_is_immutable(file_path):
immutable_file_count += 1
else:
mutable_file_count += 1
try:
if utilities.file_is_immutable(file_path):
immutable_file_count += 1
else:
mutable_file_count += 1
except exceptions.GetAttributeError:
inaccessible_file_count += 1

# Update the progress bar.
total_count = (
immutable_file_count
+ inaccessible_file_count
+ link_count
+ mutable_file_count
)
utilities.print_progress_bar(
start_time,
(immutable_file_count + mutable_file_count + link_count),
total_count,
total_file_paths,
)

Expand All @@ -92,15 +103,13 @@ def produce_report(path, include_git):
directory_count,
link_count,
immutable_file_count,
inaccessible_file_count,
mutable_file_count,
)


def _print_file_or_link_report(path):
"""Print a report for a path which is a file or a link.
This function assumes that the path has already been confirmed to reference
a file or link.
def _print_abbreviated_report(path):
"""Print a report for a path which is not a directory.
Parameters
----------
Expand All @@ -114,28 +123,31 @@ def _print_file_or_link_report(path):
Raises
------
AssertionError
If the path is not a file, is not a link or does not exist.
If the path is a directory or does not exist.
"""
# Parameter check.
assert os.path.isfile(path)
assert not os.path.isdir(path)
assert os.path.exists(path)

utilities.print_header("Report")

if os.path.islink(path):
print("A link has no immutable attribute")
else:
if utilities.file_is_immutable(path):
print("File is immutable")
else:
print("File is mutable")
try:
if utilities.file_is_immutable(path):
print("File is immutable")
else:
print("File is mutable")
except exceptions.GetAttributeError:
print("Immutable attribute could not be accessed")

print()


def _print_full_report(directory_count, link_count, immutable_file_count,
mutable_file_count):
inaccessible_file_count, mutable_file_count):
"""Print a report for a path which is a file or a link.
Parameters
Expand All @@ -146,6 +158,9 @@ def _print_full_report(directory_count, link_count, immutable_file_count,
The number of links counted.
immutable_file_count : int
The number of immutable files counted.
inaccessible_file_count : int
The number of files for which the immutability attribute could not be
accessed.
mutable_file_count : int
The number of mutable files counted.
Expand All @@ -156,7 +171,11 @@ def _print_full_report(directory_count, link_count, immutable_file_count,
"""
# Do calculations.
subdirectory_count = directory_count - 1
total_file_count = immutable_file_count + mutable_file_count
total_file_count = (
immutable_file_count
+ inaccessible_file_count
+ mutable_file_count
)

try:
entombed_proportion = immutable_file_count / total_file_count
Expand All @@ -179,6 +198,13 @@ def _print_full_report(directory_count, link_count, immutable_file_count,
"{:,}".format(mutable_file_count).rjust(constants.TABLE_WIDTH - 14),
)
print(report_separator)
if inaccessible_file_count:
print(
"Inaccessible files",
"{:,}".format(inaccessible_file_count).
rjust(constants.TABLE_WIDTH - 19),
)
print(report_separator)
print(
"All files",
"{:,}".format(total_file_count).rjust(constants.TABLE_WIDTH - 10),
Expand Down
12 changes: 6 additions & 6 deletions entomb/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def file_is_immutable(path):
------
AssertionError
If the path is a directory, is a link or does not exist.
ProcessingError
GetAttributeError
If the path's immutable attribute cannot be accessed.
"""
Expand Down Expand Up @@ -118,8 +118,8 @@ def file_paths(path, include_git):
# when the generator is iterated, not when it is created.
assert os.path.exists(path)

# Yield the path if the path is to a file or link.
if os.path.isfile(path):
# Yield the path if the path is not to a directory.
if not os.path.isdir(path):
yield path

# Walk the path if the path is to a directory.
Expand Down Expand Up @@ -326,7 +326,7 @@ def _get_immutable_flag(path):
Raises
------
ProcessingError
GetAttributeError
If the exit status of the lsattr command is non-zero.
"""
Expand All @@ -339,8 +339,8 @@ def _get_immutable_flag(path):
universal_newlines=True,
)
except subprocess.CalledProcessError:
msg = "'lsattr' failed for '{}'".format(path)
raise exceptions.ProcessingError(msg)
msg = "Immutable attribute could not be accessed for {}".format(path)
raise exceptions.GetAttributeError(msg)

# Extract the immutable attribute from the command output.
attributes = lsattr_result.stdout.split()[0]
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ disable = missing-module-docstring

[pylint.REPORTS]
score = no

[pylint.SIMILARITIES]
min-similarity-lines = 5

0 comments on commit f2a3cfe

Please sign in to comment.