#include <sys/stat.h>
#include <unistd.h>
+#include "sd-daemon.h"
#include "sd-id128.h"
#include "sd-json.h"
#include "sd-varlink.h"
STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep);
STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, set_freep);
+typedef enum ProgressPhase {
+ PROGRESS_LOADING_DEFINITIONS,
+ PROGRESS_LOADING_TABLE,
+ PROGRESS_OPENING_COPY_BLOCK_SOURCES,
+ PROGRESS_ACQUIRING_PARTITION_LABELS,
+ PROGRESS_MINIMIZING,
+ PROGRESS_PLACING,
+ PROGRESS_WIPING_DISK,
+ PROGRESS_WIPING_PARTITION,
+ PROGRESS_COPYING_PARTITION,
+ PROGRESS_FORMATTING_PARTITION,
+ PROGRESS_ADJUSTING_PARTITION,
+ PROGRESS_WRITING_TABLE,
+ PROGRESS_REREADING_TABLE,
+ _PROGRESS_PHASE_MAX,
+ _PROGRESS_PHASE_INVALID = -EINVAL,
+} ProgressPhase;
+
typedef struct FreeArea FreeArea;
typedef enum EncryptMode {
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(subvolume_hash_ops, char, path_hash_func, path_compare, Subvolume, subvolume_free);
+typedef struct Context Context;
+
typedef struct Partition {
+ Context *context;
+
char *definition_path;
char **drop_in_files;
uint64_t allocated;
};
-typedef struct Context {
+struct Context {
char **definitions;
LIST_HEAD(Partition, partitions);
X509 *certificate;
EVP_PKEY *private_key;
-} Context;
+
+ sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */
+};
static const char *empty_mode_table[_EMPTY_MODE_MAX] = {
[EMPTY_UNSET] = "unset",
[MINIMIZE_GUESS] = "guess",
};
+static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = {
+ [PROGRESS_LOADING_DEFINITIONS] = "loading-definitions",
+ [PROGRESS_LOADING_TABLE] = "loading-table",
+ [PROGRESS_OPENING_COPY_BLOCK_SOURCES] = "opening-copy-block-sources",
+ [PROGRESS_ACQUIRING_PARTITION_LABELS] = "acquiring-partition-labels",
+ [PROGRESS_MINIMIZING] = "minimizing",
+ [PROGRESS_PLACING] = "placing",
+ [PROGRESS_WIPING_DISK] = "wiping-disk",
+ [PROGRESS_WIPING_PARTITION] = "wiping-partition",
+ [PROGRESS_COPYING_PARTITION] = "copying-partition",
+ [PROGRESS_FORMATTING_PARTITION] = "formatting-partition",
+ [PROGRESS_ADJUSTING_PARTITION] = "adjusting-partition",
+ [PROGRESS_WRITING_TABLE] = "writing-table",
+ [PROGRESS_REREADING_TABLE] = "rereading-table",
+};
+
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(empty_mode, EmptyMode);
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode);
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode);
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(minimize_mode, MinimizeMode, MINIMIZE_BEST);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase);
static uint64_t round_down_size(uint64_t v, uint64_t p) {
return (v / p) * p;
return 0;
}
-static Partition *partition_new(void) {
+static Partition *partition_new(Context *c) {
Partition *p;
p = new(Partition, 1);
return NULL;
*p = (Partition) {
+ .context = c,
.weight = 1000,
.padding_weight = 0,
.current_size = UINT64_MAX,
X509_free(context->certificate);
EVP_PKEY_free(context->private_key);
+ context->link = sd_varlink_unref(context->link);
+
return mfree(context);
}
return flags;
}
+static int context_notify(
+ Context *c,
+ ProgressPhase phase,
+ const char *object,
+ unsigned percent) {
+
+ int r;
+
+ assert(c);
+ assert(phase >= 0);
+ assert(phase < _PROGRESS_PHASE_MAX);
+
+ /* Send progress information, via sd_notify() and via varlink (if client asked for it by setting "more" flag) */
+
+ _cleanup_free_ char *n = NULL;
+ if (asprintf(&n,
+ "STATUS=Phase %1$s\n"
+ "X_SYSTEMD_PHASE=%1$s",
+ progress_phase_to_string(phase)) < 0)
+ return log_oom_debug();
+
+ if (percent != UINT_MAX)
+ if (strextendf(&n, "\nX_SYSTEMD_PHASE_PROGRESS=%u", percent) < 0)
+ return log_oom_debug();
+
+ r = sd_notify(/* unset_environment= */ false, n);
+ if (r < 0)
+ log_debug_errno(r, "Failed to send sd_notify() progress notification, ignoring: %m");
+
+ if (c->link) {
+ r = sd_varlink_notifybo(
+ c->link,
+ SD_JSON_BUILD_PAIR("phase", JSON_BUILD_STRING_UNDERSCORIFY(progress_phase_to_string(phase))),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("progress", percent, UINT_MAX));
+ if (r < 0)
+ log_debug_errno(r, "Failed to send varlink notify progress notification, ignoring: %m");
+ }
+
+ return 0;
+}
+
static int partition_read_definition(
Context *c,
Partition *p,
if (partition_type_exclude(&type))
continue;
- np = partition_new();
+ np = partition_new(context);
if (!np)
return log_oom();
assert(context);
+ (void) context_notify(context, PROGRESS_LOADING_DEFINITIONS, /* object= */ NULL, UINT_MAX);
+
dirs = (const char* const*) (context->definitions ?: CONF_PATHS_STRV("repart.d"));
r = conf_files_list_strv(
STRV_FOREACH(f, files) {
_cleanup_(partition_freep) Partition *p = NULL;
- p = partition_new();
+ p = partition_new(context);
if (!p)
return log_oom();
assert(context->end == UINT64_MAX);
assert(context->total == UINT64_MAX);
+ context_notify(context, PROGRESS_LOADING_TABLE, /* object= */ NULL, UINT_MAX);
+
c = fdisk_new_context();
if (!c)
return log_oom();
if (!found) {
_cleanup_(partition_freep) Partition *np = NULL;
- np = partition_new();
+ np = partition_new(context);
if (!np)
return log_oom();
if (partition_type_defer(&p->type))
continue;
+ (void) context_notify(context, PROGRESS_WIPING_PARTITION, p->definition_path, UINT_MAX);
+
r = context_wipe_partition(context, p);
if (r < 0)
return r;
p->last_percent = percent;
+ (void) context_notify(p->context, PROGRESS_COPYING_PARTITION, p->definition_path, percent);
+
return 0;
}
if (p->copy_blocks_fd < 0)
continue;
+ (void) context_notify(context, PROGRESS_COPYING_PARTITION, p->definition_path, UINT_MAX);
+
assert(p->new_size != UINT64_MAX);
size_t extra = p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0;
if (p->copy_blocks_fd >= 0)
continue;
+ (void) context_notify(context, PROGRESS_FORMATTING_PARTITION, p->definition_path, UINT_MAX);
+
assert(p->offset != UINT64_MAX);
assert(p->new_size != UINT64_MAX);
assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0));
continue;
}
+ (void) context_notify(context, PROGRESS_ACQUIRING_PARTITION_LABELS, p->definition_path, UINT_MAX);
+
if (!sd_id128_is_null(p->current_uuid))
p->new_uuid = uuid = p->current_uuid; /* Never change initialized UUIDs */
else if (p->new_uuid_is_set)
if (partition_type_defer(&p->type))
continue;
+ (void) context_notify(context, PROGRESS_ADJUSTING_PARTITION, p->definition_path, UINT_MAX);
+
assert(p->new_size != UINT64_MAX);
assert(p->offset != UINT64_MAX);
assert(p->partno != UINT64_MAX);
log_info("Applying changes to %s.", context->node);
if (context->from_scratch && context->empty != EMPTY_CREATE) {
+
+ (void) context_notify(context, PROGRESS_WIPING_DISK, /* object= */ NULL, UINT_MAX);
+
/* Erase everything if we operate from scratch, except if the image was just created anyway, and thus is definitely empty. */
r = context_wipe_range(context, 0, context->total);
if (r < 0)
log_info("Writing new partition table.");
+ (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX);
+
r = fdisk_write_disklabel(context->fdisk_context);
if (r < 0)
return log_error_errno(r, "Failed to write partition table: %m");
return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m");
else if (capable > 0) {
log_info("Informing kernel about changed partitions...");
+ (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX);
+
r = reread_partition_table_fd(fdisk_get_devfd(context->fdisk_context), /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to reread partition table: %m");
assert(context);
+ if (!context->partitions)
+ return 0;
+
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_close_ int source_fd = -EBADF;
_cleanup_free_ char *opened = NULL;
} else
continue;
+ (void) context_notify(context, PROGRESS_OPENING_COPY_BLOCK_SOURCES, p->definition_path, UINT_MAX);
+
if (S_ISDIR(st.st_mode)) {
_cleanup_free_ char *bdev = NULL;
dev_t devt;
if (!partition_needs_populate(p))
continue;
+ (void) context_notify(context, PROGRESS_MINIMIZING, p->definition_path, UINT_MAX);
+
assert(!p->copy_blocks_path);
(void) partition_hint(p, context->node, &hint);
assert(context);
+ (void) context_notify(context, PROGRESS_PLACING, /* object= */ NULL, UINT_MAX);
+
/* First try to fit new partitions in, dropping by priority until it fits */
for (;;) {
uint64_t largest_free_area;
if (!context)
return log_oom();
+ if (FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
+ context->link = sd_varlink_ref(link);
+
r = context_read_seed(context, arg_root);
if (r < 0)
return r;
#include "varlink-io.systemd.Repart.h"
+static SD_VARLINK_DEFINE_ENUM_TYPE(
+ ProgressPhase,
+ SD_VARLINK_DEFINE_ENUM_VALUE(loading_definitions),
+ SD_VARLINK_DEFINE_ENUM_VALUE(loading_table),
+ SD_VARLINK_DEFINE_ENUM_VALUE(opening_copy_block_sources),
+ SD_VARLINK_DEFINE_ENUM_VALUE(acquiring_partition_labels),
+ SD_VARLINK_DEFINE_ENUM_VALUE(minimizing),
+ SD_VARLINK_DEFINE_ENUM_VALUE(placing),
+ SD_VARLINK_DEFINE_ENUM_VALUE(wiping_disk),
+ SD_VARLINK_DEFINE_ENUM_VALUE(wiping_partition),
+ SD_VARLINK_DEFINE_ENUM_VALUE(copying_partition),
+ SD_VARLINK_DEFINE_ENUM_VALUE(formatting_partition),
+ SD_VARLINK_DEFINE_ENUM_VALUE(adjusting_partition),
+ SD_VARLINK_DEFINE_ENUM_VALUE(writing_table),
+ SD_VARLINK_DEFINE_ENUM_VALUE(rereading_table));
+
static SD_VARLINK_DEFINE_ENUM_TYPE(
EmptyMode,
SD_VARLINK_FIELD_COMMENT("Refuse to operate on disks without an existing partition table"),
SD_VARLINK_FIELD_COMMENT("In dry-run mode returns the minimal disk size required."),
SD_VARLINK_DEFINE_OUTPUT(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("In dry-run mode returns the size of the selected block device."),
- SD_VARLINK_DEFINE_OUTPUT(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
+ SD_VARLINK_DEFINE_OUTPUT(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a phase identifier is sent in progress updates."),
+ SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(phase, ProgressPhase, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, an object identifier string is sent in progress updates."),
+ SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a progress percentrage (specific to the work done for the specified phase+object is sent in progress updates."),
+ SD_VARLINK_DEFINE_OUTPUT(progress, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
ListCandidateDevices,
SD_VARLINK_SYMBOL_COMMENT("Behaviors for disks that are completely empty (i.e. don't have a partition table yet)"),
&vl_type_EmptyMode,
+ SD_VARLINK_SYMBOL_COMMENT("Progress phase identifiers. Note that we might add more phases here, and thus identifiers. Frontends can choose to display the phase to the user in some human readable form, or not do that, but if they do it and they receive a notification for a so far unknown phase, they should just ignore it."),
+ &vl_type_ProgressPhase,
SD_VARLINK_SYMBOL_COMMENT("Invoke the actual repartitioning operation, either in dry-run mode or for real. If invoked with 'more' enabled will report progress, otherwise will just report completion."),
&vl_method_Run,