fs-util: teach unlinkat_deallocate() a simple scheme for overwriting for erasing
With that it becomes useful for deleting password files and such.
This commit is contained in:
parent
1d06deba0f
commit
053e0626db
|
@ -23,6 +23,7 @@
|
|||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
#include "random-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
|
@ -1303,11 +1304,13 @@ void unlink_tempfilep(char (*p)[]) {
|
|||
(void) unlink_noerrno(*p);
|
||||
}
|
||||
|
||||
int unlinkat_deallocate(int fd, const char *name, int flags) {
|
||||
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
|
||||
_cleanup_close_ int truncate_fd = -1;
|
||||
struct stat st;
|
||||
off_t l, bs;
|
||||
|
||||
assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
|
||||
|
||||
/* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
|
||||
* link to it. This is useful to ensure that other processes that might have the file open for reading won't be
|
||||
* able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up
|
||||
|
@ -1324,7 +1327,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
|
|||
* Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the
|
||||
* primary job – to delete the file – is accomplished. */
|
||||
|
||||
if ((flags & AT_REMOVEDIR) == 0) {
|
||||
if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
|
||||
truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
|
||||
if (truncate_fd < 0) {
|
||||
|
||||
|
@ -1340,7 +1343,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
|
|||
}
|
||||
}
|
||||
|
||||
if (unlinkat(fd, name, flags) < 0)
|
||||
if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
|
||||
return -errno;
|
||||
|
||||
if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */
|
||||
|
@ -1351,7 +1354,45 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!S_ISREG(st.st_mode) || st.st_blocks == 0 || st.st_nlink > 0)
|
||||
if (!S_ISREG(st.st_mode))
|
||||
return 0;
|
||||
|
||||
if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
|
||||
uint64_t left = st.st_size;
|
||||
char buffer[64 * 1024];
|
||||
|
||||
/* If erasing is requested, let's overwrite the file with random data once before deleting
|
||||
* it. This isn't going to give you shred(1) semantics, but hopefully should be good enough
|
||||
* for stuff backed by tmpfs at least.
|
||||
*
|
||||
* Note that we only erase like this if the link count of the file is zero. If it is higer it
|
||||
* is still linked by someone else and we'll leave it to them to remove it securely
|
||||
* eventually! */
|
||||
|
||||
random_bytes(buffer, sizeof(buffer));
|
||||
|
||||
while (left > 0) {
|
||||
ssize_t n;
|
||||
|
||||
n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
|
||||
if (n < 0) {
|
||||
log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(left >= (size_t) n);
|
||||
left -= n;
|
||||
}
|
||||
|
||||
/* Let's refresh metadata */
|
||||
if (fstat(truncate_fd, &st) < 0) {
|
||||
log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't dallocate if there's nothing to deallocate or if the file is linked elsewhere */
|
||||
if (st.st_blocks == 0 || st.st_nlink > 0)
|
||||
return 0;
|
||||
|
||||
/* If this is a regular file, it actually took up space on disk and there are no other links it's time to
|
||||
|
|
|
@ -113,7 +113,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
|
|||
int access_fd(int fd, int mode);
|
||||
|
||||
void unlink_tempfilep(char (*p)[]);
|
||||
int unlinkat_deallocate(int fd, const char *name, int flags);
|
||||
|
||||
typedef enum UnlinkDeallocateFlags {
|
||||
UNLINK_REMOVEDIR = 1 << 0,
|
||||
UNLINK_ERASE = 1 << 1,
|
||||
} UnlinkDeallocateFlags;
|
||||
|
||||
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
|
||||
|
||||
int fsync_directory_of_file(int fd);
|
||||
int fsync_full(int fd);
|
||||
|
|
|
@ -670,7 +670,7 @@ static void test_unlinkat_deallocate(void) {
|
|||
assert_se(st.st_blocks > 0);
|
||||
assert_se(st.st_nlink == 1);
|
||||
|
||||
assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0);
|
||||
assert_se(unlinkat_deallocate(AT_FDCWD, p, UNLINK_ERASE) >= 0);
|
||||
|
||||
assert_se(fstat(fd, &st) >= 0);
|
||||
assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6
|
||||
|
|
Loading…
Reference in New Issue