From: Ivan Kruglov Date: Tue, 7 Jan 2025 15:30:17 +0000 (+0100) Subject: machine: image_clean_pool_operation() X-Git-Tag: v258-rc1~1426^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a4019c75c233cfbec96096cef9bdf878a2727d93;p=thirdparty%2Fsystemd.git machine: image_clean_pool_operation() --- diff --git a/src/machine/image.c b/src/machine/image.c new file mode 100644 index 00000000000..56dcdeb44ab --- /dev/null +++ b/src/machine/image.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "discover-image.h" +#include "fd-util.h" +#include "fileio.h" +#include "image.h" +#include "io-util.h" +#include "operation.h" +#include "process-util.h" +#include "string-table.h" +#include "tmpfile-util.h" + +int clean_pool_read_first_entry(FILE *file, int child_error, sd_bus_error *error) { + _cleanup_free_ char *name = NULL; + bool success; + int r; + + assert(file); + + if (fseek(file, 0, SEEK_SET) < 0) + return log_debug_errno(errno, "Failed to seek to the beginning of tmp file: %m"); + + /* The resulting temporary file starts with a boolean value that indicates success or not. */ + errno = 0; + size_t n = fread(&success, 1, sizeof(success), file); + if (n != sizeof(success)) { + log_debug_errno(errno_or_else(EIO), "Received unexpected amount of %zu bytes: %m", n); + return child_error < 0 ? child_error : errno_or_else(EIO); + } + + if (child_error < 0) { + /* The clean-up operation failed. In this case the resulting temporary file should contain a boolean + * set to false followed by the name of the failed image. Let's try to read this and use it for the + * error message. If we can't read it, don't mind, and return the naked error. */ + + if (success) { /* The resulting temporary file could not be updated, ignore it. */ + log_debug("Child process exited with an error. We expected a name of the image which caused the failure in the tmp file, but it failed as well."); + return child_error; + } + + r = read_nul_string(file, LONG_LINE_MAX, &name); + if (r <= 0) { /* Same here... */ + log_debug_errno(r, "Failed to read nul-terminated string from tmp file: %m"); + return child_error; + } + + log_debug_errno(child_error, "Failed to remove image '%s': %m", name); + return sd_bus_error_set_errnof(error, child_error, "Failed to remove image '%s': %m", name); + } + + if (!success) + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Inconsistent state of the temporary file."); + + return 0; +} + +int clean_pool_read_next_entry(FILE *file, char **ret_name, uint64_t *ret_usage) { + /* Return value: + * r < 0: error + * r == 0 last record returned + * r > 0 more recoreds expected */ + + _cleanup_free_ char *name = NULL; + uint64_t usage; + int r; + + assert(file); + + r = read_nul_string(file, LONG_LINE_MAX, &name); + if (r < 0) + return log_debug_errno(r, "Failed to read nul-terminated string: %m"); + if (r == 0) /* reached the end */ + return 0; + + errno = 0; + size_t n = fread(&usage, 1, sizeof(usage), file); + if (n != sizeof(usage)) + return log_debug_errno(errno_or_else(EIO), "Received unexpected amount of %zu bytes: %m", n); + + if (ret_name) + *ret_name = TAKE_PTR(name); + if (ret_usage) + *ret_usage = usage; + + return 1; +} + +int image_clean_pool_operation(Manager *manager, ImageCleanPoolMode mode, Operation **ret_operation) { + _cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR; + _cleanup_close_ int result_fd = -EBADF; + _cleanup_(sigkill_waitp) pid_t child = 0; + int r; + + assert(manager); + assert(ret_operation); + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return log_debug_errno(errno, "Failed to create pipe: %m"); + + /* Create a temporary file we can dump information about deleted images into. We use a temporary file for this + * instead of a pipe or so, since this might grow quit large in theory and we don't want to process this + * continuously */ + result_fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC); + if (result_fd < 0) + return log_debug_errno(result_fd, "Failed to open tmpfile: %m"); + + /* This might be a slow operation, run it asynchronously in a background process */ + r = safe_fork("(sd-clean)", FORK_RESET_SIGNALS, &child); + if (r < 0) + return log_debug_errno(r, "Failed to fork(): %m"); + if (r == 0) { + _cleanup_hashmap_free_ Hashmap *images = NULL; + bool success = true; + Image *image; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + images = hashmap_new(&image_hash_ops); + if (!images) + report_errno_and_exit(errno_pipe_fd[1], ENOMEM); + + r = image_discover(manager->runtime_scope, IMAGE_MACHINE, /* root = */ NULL, images); + if (r < 0) { + log_debug_errno(r, "Failed to discover images: %m"); + report_errno_and_exit(errno_pipe_fd[1], r); + } + + ssize_t n = loop_write(result_fd, &success, sizeof(success)); + if (n < 0) { + log_debug_errno(n, "Failed to write to tmp file: %m"); + report_errno_and_exit(errno_pipe_fd[1], n); + } + + HASHMAP_FOREACH(image, images) { + /* We can't remove vendor images (i.e. those in /usr) */ + if (IMAGE_IS_VENDOR(image)) + continue; + if (IMAGE_IS_HOST(image)) + continue; + if (mode == IMAGE_CLEAN_POOL_REMOVE_HIDDEN && !IMAGE_IS_HIDDEN(image)) + continue; + + r = image_remove(image); + if (r == -EBUSY) { + log_debug("Keeping image '%s' because it's currently used.", image->name); + continue; + } + if (r < 0) { + log_debug_errno(r, "Failed to remove image '%s': %m", image->name); + success = false; + + /* If the operation failed, let's override everything we wrote, and instead write there at which image we failed. */ + if (ftruncate(result_fd, 0) < 0 || + lseek(result_fd, 0, SEEK_SET) < 0 || + write(result_fd, &success, sizeof(success)) < 0 || + write(result_fd, image->name, strlen(image->name)+1) < 0) + log_debug_errno(errno, "Failed to truncate, rewind, or write to tmp file, ignoring: %m"); + + /* Report original error code (not results of rewind/truncate/etc */ + report_errno_and_exit(errno_pipe_fd[1], r); + } + + n = loop_write(result_fd, image->name, strlen(image->name) + 1); + if (n < 0) { + log_debug_errno(n, "Failed to write image name to tmp file: %m"); + report_errno_and_exit(errno_pipe_fd[1], n); + } + + n = loop_write(result_fd, &image->usage_exclusive, sizeof(image->usage_exclusive)); + if (n < 0) { + log_debug_errno(n, "Failed to write image's usage to tmp file: %m"); + report_errno_and_exit(errno_pipe_fd[1], n); + } + } + + result_fd = safe_close(result_fd); + _exit(EXIT_SUCCESS); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + /* The clean-up might take a while, hence install a watch on the child and return */ + r = operation_new(manager, /* machine= */ NULL, child, errno_pipe_fd[0], ret_operation); + if (r < 0) + return r; + + (*ret_operation)->extra_fd = TAKE_FD(result_fd); + TAKE_FD(errno_pipe_fd[0]); + TAKE_PID(child); + return 0; +} + +static const char* const image_clean_pool_mode_table[_IMAGE_CLEAN_POOL_MAX] = { + [IMAGE_CLEAN_POOL_REMOVE_ALL] = "all", + [IMAGE_CLEAN_POOL_REMOVE_HIDDEN] = "hidden" +}; + +DEFINE_STRING_TABLE_LOOKUP(image_clean_pool_mode, ImageCleanPoolMode); diff --git a/src/machine/image.h b/src/machine/image.h new file mode 100644 index 00000000000..522b7459e4b --- /dev/null +++ b/src/machine/image.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "machined.h" +#include "operation.h" + +typedef enum ImageCleanPoolMode { + IMAGE_CLEAN_POOL_REMOVE_ALL, + IMAGE_CLEAN_POOL_REMOVE_HIDDEN, + _IMAGE_CLEAN_POOL_MAX, + _IMAGE_CLEAN_POOL_INVALID = -EINVAL, +} ImageCleanPoolMode; + +ImageCleanPoolMode image_clean_pool_mode_from_string(const char *s) _pure_; +const char* image_clean_pool_mode_to_string(ImageCleanPoolMode mode) _const_; + +int image_clean_pool_operation(Manager *manager, ImageCleanPoolMode mode, Operation **ret_operation); +int clean_pool_read_first_entry(FILE *file, int child_error, sd_bus_error *error); +int clean_pool_read_next_entry(FILE *file, char **ret_name, uint64_t *ret_usage); diff --git a/src/machine/meson.build b/src/machine/meson.build index f3358d4b988..c596f71b5d4 100644 --- a/src/machine/meson.build +++ b/src/machine/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later libmachine_core_sources = files( + 'image.c', 'image-dbus.c', 'image-varlink.c', 'machine-dbus.c',