From 19232def41e0cffd9daef3408bf223a92d65673e Mon Sep 17 00:00:00 2001 From: David Rebbe Date: Mon, 29 Jun 2026 13:55:18 +0000 Subject: [PATCH] C2: Add icsneoc2_device_force_disk_config_update --- api/icsneoc2/icsneoc2.cpp | 16 +++++++ examples/c2/diskformat/src/main.c | 79 +++++++++++++++++++++++++++++++ include/icsneo/disk/diskdetails.h | 3 +- include/icsneo/icsneoc2.h | 19 ++++++++ include/icsneo/icsneoc2types.h | 2 +- test/unit/icsneoc2.cpp | 11 +++++ 6 files changed, 128 insertions(+), 2 deletions(-) diff --git a/api/icsneoc2/icsneoc2.cpp b/api/icsneoc2/icsneoc2.cpp index dcf2f52..91915fd 100644 --- a/api/icsneoc2/icsneoc2.cpp +++ b/api/icsneoc2/icsneoc2.cpp @@ -71,6 +71,7 @@ icsneoc2_error_t icsneoc2_error_code_get(icsneoc2_error_t error_code, char* valu "Close failed", // icsneoc2_error_close_failed "Reconnect failed", // icsneoc2_error_reconnect_failed "Invalid data", // icsneoc2_error_invalid_data + "Force disk config update failed", // icsneoc2_error_force_disk_config_update_failed }; static_assert(std::size(error_strings) == icsneoc2_error_maxsize, "error_strings is out of sync with _icsneoc2_error_t enum - update both together"); @@ -1097,6 +1098,21 @@ icsneoc2_error_t icsneoc2_device_format_disk(const icsneoc2_device_t* device, ic return icsneoc2_error_success; } +icsneoc2_error_t icsneoc2_device_force_disk_config_update(const icsneoc2_device_t* device, icsneoc2_disk_details_t* disk_details) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!disk_details || !disk_details->details) { + return icsneoc2_error_invalid_parameters; + } + + if(!device->device->forceDiskConfigUpdate(*disk_details->details)) { + return icsneoc2_error_force_disk_config_update_failed; + } + return icsneoc2_error_success; +} + static icsneoc2_error_t get_supported_networks(const icsneoc2_device_t* device, const std::vector& nets, icsneoc2_netid_t* networks, size_t* count) { auto res = icsneoc2_device_is_valid(device); if(res != icsneoc2_error_success) { diff --git a/examples/c2/diskformat/src/main.c b/examples/c2/diskformat/src/main.c index 9358c64..23a2c53 100644 --- a/examples/c2/diskformat/src/main.c +++ b/examples/c2/diskformat/src/main.c @@ -109,6 +109,85 @@ int main() { } } + /* Choose operation */ + printf("\n\tChoose operation:\n"); + printf("\t [f] Format the disk(s)\n"); + printf("\t [u] Update the disk configuration without formatting\n"); + printf("\t [q] Quit\n"); + printf("\tSelection [f/u/q]: "); + char choice[8] = {0}; + if(scanf("%7s", choice) != 1) { + choice[0] = 'q'; + } + + if(choice[0] == 'u' || choice[0] == 'U') { + /* Force a disk config update (e.g. changing the disk layout) without formatting. + * Unlike a format, this preserves the existing data on the disk(s). */ + printf("\n\tChoose disk layout:\n"); + printf("\t [s] Spanned\n"); + printf("\t [r] RAID0\n"); + printf("\tSelection [s/r] (current: %s): ", layout == icsneoc2_disk_layout_raid0 ? "RAID0" : "Spanned"); + char layout_choice[8] = {0}; + if(scanf("%7s", layout_choice) != 1) { + layout_choice[0] = '\0'; + } + if(layout_choice[0] == 'r' || layout_choice[0] == 'R') { + icsneoc2_disk_details_layout_set(details, icsneoc2_disk_layout_raid0); + } else if(layout_choice[0] == 's' || layout_choice[0] == 'S') { + icsneoc2_disk_details_layout_set(details, icsneoc2_disk_layout_spanned); + } else { + printf("\tKeeping current layout.\n"); + } + + /* Enable/disable individual disks in the configuration. A disk's enabled + * state is carried by the FORMATTED flag; only present disks can be enabled. */ + for(size_t i = 0; i < detail_count; i++) { + icsneoc2_disk_format_flags_t flags = 0; + icsneoc2_disk_details_flags_get(details, i, &flags); + if(!(flags & ICSNEOC2_DISK_FORMAT_FLAGS_PRESENT)) { + continue; /* Can't enable a disk that isn't present */ + } + printf("\tEnable disk [%zu]? [y/N] (currently %s): ", i, + (flags & ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED) ? "enabled" : "disabled"); + char disk_choice[8] = {0}; + if(scanf("%7s", disk_choice) != 1) { + disk_choice[0] = '\0'; + } + if(disk_choice[0] == 'y' || disk_choice[0] == 'Y') { + flags |= ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED; + } else { + flags &= ~(icsneoc2_disk_format_flags_t)ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED; + } + icsneoc2_disk_details_flags_set(details, i, flags); + } + + icsneoc2_disk_layout_t new_layout = 0; + icsneoc2_disk_details_layout_get(details, &new_layout); + printf("\n\tForcing disk config update on %s to %s layout (no data will be erased)...\n", + description, new_layout == icsneoc2_disk_layout_raid0 ? "RAID0" : "Spanned"); + res = icsneoc2_device_force_disk_config_update(device, details); + if(res != icsneoc2_error_success) { + print_error_code("\tForce disk config update failed", res); + icsneoc2_disk_details_free(details); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + return -1; + } + printf("\tDisk config update complete!\n"); + icsneoc2_disk_details_free(details); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + return 0; + } + + if(choice[0] != 'f' && choice[0] != 'F') { + printf("\tAborted.\n"); + icsneoc2_disk_details_free(details); + icsneoc2_device_close(device); + icsneoc2_device_free(device); + return 0; + } + /* Build format config: mark present disks for formatting */ bool any_present = false; for(size_t i = 0; i < detail_count; i++) { diff --git a/include/icsneo/disk/diskdetails.h b/include/icsneo/disk/diskdetails.h index 3e65010..dd9319a 100644 --- a/include/icsneo/disk/diskdetails.h +++ b/include/icsneo/disk/diskdetails.h @@ -18,7 +18,8 @@ enum class DiskLayout : uint8_t { struct DiskInfo { bool present; // Disk is connected bool initialized; // Disk is initialized - bool formatted; // Disk is formatted + // getDiskDetails: disk is formatted | formatDisk: disk to format | forceDiskConfigUpdate: disk enabled in layout (also reported as formatted) + bool formatted; uint64_t sectors; uint32_t bytesPerSector; diff --git a/include/icsneo/icsneoc2.h b/include/icsneo/icsneoc2.h index 6799ff1..4f5f9dd 100644 --- a/include/icsneo/icsneoc2.h +++ b/include/icsneo/icsneoc2.h @@ -47,6 +47,7 @@ typedef enum _icsneoc2_error_t { icsneoc2_error_close_failed, // Failed to close device icsneoc2_error_reconnect_failed, // Failed to reconnect to device icsneoc2_error_invalid_data, // Failed to get/set data due to invalid data pointer or size + icsneoc2_error_force_disk_config_update_failed, // Failed to force a disk config update // NOTE: Any new values added here should be updated in icsneoc2_error_code_get icsneoc2_error_maxsize } _icsneoc2_error_t; @@ -846,6 +847,24 @@ icsneoc2_error_t icsneoc2_disk_details_full_format_set(const icsneoc2_disk_detai */ icsneoc2_error_t icsneoc2_device_format_disk(const icsneoc2_device_t* device, icsneoc2_disk_details_t* disk_details, icsneoc2_disk_format_progress_fn progress_callback, void* user_data); +/** + * Force a disk layout/configuration change on a device without formatting. + * + * Unlike icsneoc2_device_format_disk(), this applies the configuration described by the + * disk details handle (such as the disk layout) and enables the change without erasing data. + * + * @param[in] device The device whose disk configuration should be updated. + * @param[in] disk_details A disk details handle describing the desired configuration. + * Use icsneoc2_device_disk_details_get() to obtain a handle, then modify it + * (e.g. set the layout with icsneoc2_disk_details_layout_set()). + * In this context the per-disk ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED flag + * selects whether that disk is enabled in the layout (no formatting occurs). + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, + * icsneoc2_error_force_disk_config_update_failed otherwise. + */ +icsneoc2_error_t icsneoc2_device_force_disk_config_update(const icsneoc2_device_t* device, icsneoc2_disk_details_t* disk_details); + /** * Get the list of networks this device supports for receiving. * diff --git a/include/icsneo/icsneoc2types.h b/include/icsneo/icsneoc2types.h index 146ec68..2bc11a2 100644 --- a/include/icsneo/icsneoc2types.h +++ b/include/icsneo/icsneoc2types.h @@ -413,7 +413,7 @@ typedef uint8_t icsneoc2_disk_layout_t; #define ICSNEOC2_DISK_FORMAT_FLAGS_PRESENT 0x1 // Indicates that the disk is present #define ICSNEOC2_DISK_FORMAT_FLAGS_INITIALIZED 0x2 // Indicates that the disk is initialized -#define ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED 0x4 // Indicates that the disk is already formatted +#define ICSNEOC2_DISK_FORMAT_FLAGS_FORMATTED 0x4 // disk_details_get: disk is formatted | format_disk: disk to format | force_disk_config_update: disk enabled in layout (also reported as formatted) typedef uint32_t icsneoc2_disk_format_flags_t; diff --git a/test/unit/icsneoc2.cpp b/test/unit/icsneoc2.cpp index 5f559b3..3efbf04 100644 --- a/test/unit/icsneoc2.cpp +++ b/test/unit/icsneoc2.cpp @@ -338,6 +338,7 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_supports_disk_formatting(NULL, &placeholderBool)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_disk_details_get(NULL, NULL)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_format_disk(NULL, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_device_force_disk_config_update(NULL, NULL)); // Disk details accessors with NULL details ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_disk_details_count_get(NULL, &placeholderSizeT)); @@ -1391,6 +1392,16 @@ TEST(icsneoc2, test_icsneoc2_format_disk_error_code) ASSERT_STREQ(error_str, "Disk format failed"); } +TEST(icsneoc2, test_icsneoc2_force_disk_config_update_error_code) +{ + // Verify the new error code exists and has a valid string + char error_str[64] = {0}; + size_t error_str_len = sizeof(error_str); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_error_code_get(icsneoc2_error_force_disk_config_update_failed, error_str, &error_str_len)); + ASSERT_GT(error_str_len, 0u); + ASSERT_STREQ(error_str, "Force disk config update failed"); +} + TEST(icsneoc2, test_icsneoc2_memory_type_enums) { // Memory type enum values should match Disk::MemoryType