Skip to content

Conversation

@blackboxsw
Copy link
Collaborator

Proposed Commit Message

Add test coverage for the fix that prevents cloud-init clean --logs
from attempting to remove non-file paths (e.g., character devices,
directories) that may appear in the log file configuration.

The test verifies that when /dev/null is configured as a log path,
it is safely skipped while actual log files are still removed.

Additional Context

Test Steps

 tox -e py3 -- tests/unittests/cmd/test_clean.py::TestClean::test_remove_artifacts_skips_non_file_logs

Merge type

  • Squash merge using "Proposed Commit Message"
  • Rebase and merge unique commits. Requires commit messages per-commit each referencing the pull request number (#<PR_NUM>)

@blackboxsw blackboxsw force-pushed the fix-traceback-on-clean branch from 535d56e to 20a69e4 Compare November 11, 2025 17:59
Fix OSError raised when cloud-init clean --logs encounters
character-device paths in log configuration (e.g., directories, character
devices like /dev/null, block devices FIFOs, or sockets).

While uncommon, log configurations could contain non-file paths that would
cause clean operations to fail.

Add os.path.isfile() guard in del_file() with debug logs when paths are
skipped in cloudinit/util.py. Add unittest coverage for regular files,
symlinks, dirs and character devices.
@blackboxsw blackboxsw force-pushed the fix-traceback-on-clean branch from 20a69e4 to 1dabc2c Compare November 11, 2025 18:24
Copy link
Member

@holmanb holmanb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @blackboxsw. I left a couple of comments. Also, can you share the error you saw?



def del_file(path):
if not os.path.isfile(path):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used in various places. This would have previously deleted a directory or symlink, but now will not. Is that what we want in every case that it is used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, while it wouldn't have removed a directory, it previously would have removed a symlink.

  File ".../src/cloud-init/cloudinit/util.py", line 2082, in del_file
    os.unlink(path)
IsADirectoryError: [Errno 21] Is a directory: 'bogus'

Aligned with your comment though, I've made the following changes:

  1. I'll add special lstat checks to allow symlink removal, file and named pipes, but avoid device files
  2. Let's limit this changeset to user-configurable paths (via changes only in cmd/clean.py instead of util.del_file as those supplemental guard-rails are not necessary on every del_file call, but on paths we think are user-configurable and more prone to error inputs.

if remove_logs:
for log_file in get_config_logfiles(init.cfg):
del_file(log_file)
if os.path.isfile(log_file):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change behavior for anyone that is using a named pipe as a destination for their logs - the pipe will no longer be cleaned up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pulled this out of util.del_file so we only impact cloud-init clean behavior for potentially customized log-config.

@holmanb holmanb added the incomplete Action required by submitter label Nov 20, 2025
@blackboxsw
Copy link
Collaborator Author

Thanks for this @blackboxsw. I left a couple of comments. Also, can you share the error you saw?

@holmanb here is the error which could be caused be custom /etc/cloud/cloud.cfg.d/05_logging.cfg changing log files to character devices or symlinks.

root@dd:~# cloud-init clean --logs
Traceback (most recent call last):
  File "/usr/bin/cloud-init", line 9, in <module>
    sys.exit(main.main())
             ~~~~~~~~~^^
  File "/usr/lib/python3/dist-packages/cloudinit/cmd/main.py", line 1308, in main
    return sub_main(args, parser)
  File "/usr/lib/python3/dist-packages/cloudinit/cmd/main.py", line 1438, in sub_main
    retval = functor(name, args)
  File "/usr/lib/python3/dist-packages/cloudinit/cmd/clean.py", line 189, in handle_clean_args
    exit_code = remove_artifacts(
        init, args.remove_logs, args.remove_seed, args.remove_config
    )
  File "/usr/lib/python3/dist-packages/cloudinit/cmd/clean.py", line 129, in remove_artifacts
    del_file(log_file)
    ~~~~~~~~^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/cloudinit/util.py", line 2072, in del_file
    os.unlink(path)
    ~~~~~~~~~^^^^^^
OSError: [Errno 16] Device or resource busy: '/dev/console'

@blackboxsw blackboxsw removed the incomplete Action required by submitter label Nov 24, 2025
@blackboxsw blackboxsw requested a review from holmanb November 24, 2025 18:12
…nd devs

We don't want to change behavior of all callsites of util.del_file.
Limit changset to just cloudinit/cmd/clean log file removal where
some custom log configuration may set log files to /dev/null, /dev/console,
symlinks or named pipes. Avoid removal of such file types.

Reverts commit 1dabc2c.
@blackboxsw blackboxsw force-pushed the fix-traceback-on-clean branch from 033b72a to 0ed3280 Compare November 24, 2025 18:13
Copy link
Member

@holmanb holmanb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @blackboxsw. I left some feedback inline. Also, the proposed commit message is confusing. Block devices, character devices, directories, symlinks, and pipes are all files.

More succinctly, the goal here is to avoid tracebacks and aborted operations when a reasonable configuration is provided. Perhaps we can just say something like that in the commit message.

Comment on lines +55 to +56
if not os.path.exists(log_file):
return False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant: del_file is already safe against trying to remove non-existent files.

Comment on lines +57 to +63
if is_link(log_file):
log_util.multi_log(
f"Skipping removal of symlink log file: {log_file}\n",
log=LOG,
log_level=logging.INFO,
)
return False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unlikely. While device files such as /dev/null are a widely known kernel-provided option for ignoring output, a symlink is far less common and would almost certainly have been inserted at runtime or image build time.

The same is true for other kinds of files: named pipes, for example, which don't get this special treatment in this code. The choice to ignore symlinks but not named pipes seems arbitrary - why should a user expect them to be treated differently? It doesn't seem likely and treating some cases differently than others doesn't seem necessary.

In short, this seems "magical", and doesn't fit the real need addressed in this PR: prevent an unwanted traceback seen in a reasonable logging configuration.

file_stat = os.stat(log_file)
if stat.S_ISBLK(file_stat.st_mode) or stat.S_ISCHR(file_stat.st_mode):
log_util.multi_log(
f"Skipping removal of device file: {log_file}\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is writing to the console really desirable here? It seems like unnecessary noise.

@github-actions github-actions bot added the stale-pr Pull request is stale; will be auto-closed soon label Dec 9, 2025
@canonical canonical deleted a comment from github-actions bot Dec 9, 2025
@github-actions github-actions bot removed the stale-pr Pull request is stale; will be auto-closed soon label Dec 10, 2025
@github-actions github-actions bot added the stale-pr Pull request is stale; will be auto-closed soon label Dec 24, 2025
@canonical canonical deleted a comment from github-actions bot Dec 24, 2025
@blackboxsw blackboxsw removed the stale-pr Pull request is stale; will be auto-closed soon label Dec 24, 2025
@holmanb holmanb self-assigned this Jan 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants