-
Notifications
You must be signed in to change notification settings - Fork 1k
fix: cloud-init clean --logs should not remove non-files #6568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
535d56e to
20a69e4
Compare
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.
20a69e4 to
1dabc2c
Compare
holmanb
left a comment
There was a problem hiding this 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?
cloudinit/util.py
Outdated
|
|
||
|
|
||
| def del_file(path): | ||
| if not os.path.isfile(path): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
- I'll add special lstat checks to allow symlink removal, file and named pipes, but avoid device files
- 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.
cloudinit/cmd/clean.py
Outdated
| if remove_logs: | ||
| for log_file in get_config_logfiles(init.cfg): | ||
| del_file(log_file) | ||
| if os.path.isfile(log_file): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 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. |
…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.
033b72a to
0ed3280
Compare
holmanb
left a comment
There was a problem hiding this 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.
| if not os.path.exists(log_file): | ||
| return False |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.
Proposed Commit Message
Additional Context
Test Steps
Merge type