diff --git a/common/src/fx_directory_rename.c b/common/src/fx_directory_rename.c index 48b6784..aeee9bd 100644 --- a/common/src/fx_directory_rename.c +++ b/common/src/fx_directory_rename.c @@ -51,6 +51,8 @@ /* If found, the rename request is valid and the directory will be */ /* changed to the new name. Otherwise, if the directory is not found, */ /* the appropriate error code is returned to the caller. */ +/* If the directory is moved under a different parent, the ".." */ +/* entry inside the directory is updated to name the new parent. */ /* */ /* INPUT */ /* */ @@ -64,6 +66,7 @@ /* */ /* CALLS */ /* */ +/* _fx_directory_entry_read Read entries from directory */ /* _fx_directory_entry_write Write the new directory entry */ /* _fx_directory_free_search Search for a free directory */ /* entry */ @@ -90,6 +93,7 @@ FX_DIR_ENTRY new_dir_entry; FX_DIR_ENTRY search_directory; CHAR *new_name_ptr; ULONG i; +ULONG parent_cluster; CHAR *work_ptr; CHAR alpha, beta; #ifdef FX_RENAME_PATH_INHERIT @@ -451,20 +455,97 @@ UINT j; /* Now wipe out the old directory entry. */ status = _fx_directory_entry_write(media_ptr, &old_dir_entry); + /* Determine if the write was successful. */ + if (status != FX_SUCCESS) + { + #ifdef FX_ENABLE_FAULT_TOLERANT - /* Check for a bad status. */ + FX_FAULT_TOLERANT_TRANSACTION_FAIL(media_ptr); +#endif /* FX_ENABLE_FAULT_TOLERANT */ + + /* Release media protection. */ + FX_UNPROTECT + + /* Return the error code. */ + return(status); + } + + /* The renamed directory itself contains a ".." entry, which stores + the starting cluster of its parent directory. If the rename moved + the directory under a different parent, that stored cluster is + stale now: ".." inside the renamed directory would still lead to + the old parent and, once the old parent's cluster is reused, to an + unrelated place. Update it in place. */ + + /* Determine the starting cluster of the new parent directory. + An empty name in the search directory marks the root + directory - the name is the only root indication, the other + fields keep earlier values in that case. A ".." entry stores + its parent's starting cluster; when the parent is the root + directory, it stores cluster 0. */ + if (search_directory.fx_dir_entry_name[0]) + { + + /* New parent is a sub-directory. */ + parent_cluster = search_directory.fx_dir_entry_cluster; + } + else + { + + /* New parent is the root directory. */ + parent_cluster = 0; + } + + /* old_dir_entry is no longer needed and still holds the starting + cluster of the renamed directory - reuse it to address that + directory as the one to read from. Ensure the cluster chain is + walked from the start. */ + old_dir_entry.fx_dir_entry_last_search_cluster = 0; + + /* Read the second directory entry of the renamed directory - its + ".." entry, created together with the directory. new_dir_entry + is no longer needed either and serves as the destination. */ + i = 1; + status = _fx_directory_entry_read(media_ptr, &old_dir_entry, &i, &new_dir_entry); + + /* Update the entry only if it is the ".." entry the FAT format + places at this position - on a non-conformant media the rename + result is left as it is - and only if the stored cluster does + not match the new parent already. A rename inside one parent + ends here without an additional write. */ + if ((status == FX_SUCCESS) && + (new_dir_entry.fx_dir_entry_name[0] == '.') && + (new_dir_entry.fx_dir_entry_name[1] == '.') && + (new_dir_entry.fx_dir_entry_name[2] == 0) && + (new_dir_entry.fx_dir_entry_cluster != parent_cluster)) + { + + /* Update the stored parent cluster. Everything else of the + entry - its position included - stays untouched: the first + two entries of a directory are a format invariant other + services rely on. */ + new_dir_entry.fx_dir_entry_cluster = parent_cluster; + + /* Write the updated ".." entry back. */ + status = _fx_directory_entry_write(media_ptr, &new_dir_entry); + } + + /* Determine if the ".." update was successful. */ if (status != FX_SUCCESS) { +#ifdef FX_ENABLE_FAULT_TOLERANT FX_FAULT_TOLERANT_TRANSACTION_FAIL(media_ptr); +#endif /* FX_ENABLE_FAULT_TOLERANT */ /* Release media protection. */ FX_UNPROTECT - /* Return the bad status. */ + /* Return the error code. */ return(status); } +#ifdef FX_ENABLE_FAULT_TOLERANT /* End transaction. */ status = _fx_fault_tolerant_transaction_end(media_ptr); #endif /* FX_ENABLE_FAULT_TOLERANT */ diff --git a/test/regression_test/filex_directory_rename_test.c b/test/regression_test/filex_directory_rename_test.c index 36fd181..005379c 100644 --- a/test/regression_test/filex_directory_rename_test.c +++ b/test/regression_test/filex_directory_rename_test.c @@ -15,6 +15,7 @@ #include "tx_api.h" #endif #include "fx_api.h" +#include "fx_directory.h" #include "fx_ram_driver_test.h" #include "fx_fault_tolerant.h" #include "fx_utility.h" @@ -35,6 +36,7 @@ static TX_THREAD ftest_0; #endif static FX_MEDIA ram_disk; +static FX_FILE probe_file; static CHAR max_name[FX_MAX_LONG_NAME_LEN + 2]; static CHAR max_newname[FX_MAX_LONG_NAME_LEN + 1]; @@ -55,6 +57,9 @@ static UCHAR fault_tolerant_buffer[FAULT_TOLERANT_SIZE]; void filex_directory_rename_application_define(void *first_unused_memory); static void ftest_0_entry(ULONG thread_input); +static UINT dot_dot_probe(CHAR *marker_path); +static UINT directory_cluster_read(CHAR *directory_path, ULONG *cluster_ptr); +static UINT dot_dot_cluster_read(CHAR *directory_path, ULONG *cluster_ptr); VOID _fx_ram_driver(FX_MEDIA *media_ptr); void test_control_return(UINT status); @@ -102,6 +107,90 @@ void filex_directory_rename_application_define(void *first_unused_memory) +/* Open and close the file at marker_path - a path that contains a ".." + component - to report where ".." currently leads. */ +static UINT dot_dot_probe(CHAR *marker_path) +{ + +UINT status; + + status = fx_file_open(&ram_disk, &probe_file, marker_path, FX_OPEN_FOR_READ); + if (status == FX_SUCCESS) + { + fx_file_close(&probe_file); + } + + return(status); +} + + +/* Return the starting cluster of the directory at directory_path. */ +static UINT directory_cluster_read(CHAR *directory_path, ULONG *cluster_ptr) +{ + +UINT status; +FX_DIR_ENTRY dir_entry; + + /* The directory's own entry carries its starting cluster. */ + dir_entry.fx_dir_entry_name = ram_disk.fx_media_name_buffer + FX_MAX_LONG_NAME_LEN; + dir_entry.fx_dir_entry_short_name[0] = 0; + status = _fx_directory_search(&ram_disk, directory_path, &dir_entry, FX_NULL, FX_NULL); + + /* Return the cluster only on success. */ + if (status == FX_SUCCESS) + { + *cluster_ptr = dir_entry.fx_dir_entry_cluster; + } + + return(status); +} + + +/* Read the ".." entry - entry 1 - of the directory at directory_path and + return the parent starting cluster stored in it. Verifies that the + entry actually reads back as "..". */ +static UINT dot_dot_cluster_read(CHAR *directory_path, ULONG *cluster_ptr) +{ + +UINT status; +ULONG entry_index; +FX_DIR_ENTRY dir_entry; +FX_DIR_ENTRY dot_dot_entry; + + /* Find the directory itself first. */ + dir_entry.fx_dir_entry_name = ram_disk.fx_media_name_buffer + FX_MAX_LONG_NAME_LEN; + dir_entry.fx_dir_entry_short_name[0] = 0; + status = _fx_directory_search(&ram_disk, directory_path, &dir_entry, FX_NULL, FX_NULL); + if (status != FX_SUCCESS) + { + return(status); + } + + /* Read its second directory entry - the ".." entry. */ + dir_entry.fx_dir_entry_last_search_cluster = 0; + dot_dot_entry.fx_dir_entry_name = ram_disk.fx_media_name_buffer + FX_MAX_LONG_NAME_LEN * 2; + dot_dot_entry.fx_dir_entry_short_name[0] = 0; + entry_index = 1; + status = _fx_directory_entry_read(&ram_disk, &dir_entry, &entry_index, &dot_dot_entry); + if (status != FX_SUCCESS) + { + return(status); + } + + /* Anything other than ".." at this position is a failure. */ + if ((dot_dot_entry.fx_dir_entry_name[0] != '.') || + (dot_dot_entry.fx_dir_entry_name[1] != '.') || + (dot_dot_entry.fx_dir_entry_name[2] != 0)) + { + return(FX_INVALID_STATE); + } + + *cluster_ptr = dot_dot_entry.fx_dir_entry_cluster; + + return(FX_SUCCESS); +} + + /* Define the test threads. */ static void ftest_0_entry(ULONG thread_input) @@ -109,6 +198,11 @@ static void ftest_0_entry(ULONG thread_input) UINT status; UINT i; + ULONG parent_cluster; + ULONG dot_dot_cluster; +#ifndef FX_MEDIA_STATISTICS_DISABLE + ULONG entry_writes_before; +#endif FX_PARAMETER_NOT_USED(thread_input); @@ -296,6 +390,137 @@ static void ftest_0_entry(ULONG thread_input) status = fx_media_close(&ram_disk); return_if_fail( status == FX_SUCCESS); + /* Format the media again: the checks below build their own fixture + and must not disturb - or depend on - the directory layout the + error-injection steps above are calibrated against. */ + status = fx_media_format(&ram_disk, + _fx_ram_driver, // Driver entry + ram_disk_memory, // RAM disk memory pointer + cache_buffer, // Media buffer pointer + CACHE_SIZE, // Media buffer size + "MY_RAM_DISK", // Volume Name + 1, // Number of FATs + 128, // Directory Entries + 0, // Hidden sectors + +#ifdef FX_ENABLE_FAULT_TOLERANT + 4096 * 8, // Total sectors + 256, // Sector size + 8, // Sectors per cluster +#else + 4096, // Total sectors + 128, // Sector size + 1, // Sectors per cluster +#endif + + 1, // Heads + 1); // Sectors per track + status += fx_media_open(&ram_disk, "RAM DISK", _fx_ram_driver, ram_disk_memory, cache_buffer, CACHE_SIZE); + return_if_fail( status == FX_SUCCESS); + +#ifdef FX_ENABLE_FAULT_TOLERANT + status = fx_fault_tolerant_enable(&ram_disk, fault_tolerant_buffer, FAULT_TOLERANT_SIZE); + return_if_fail( status == FX_SUCCESS); +#endif + + /* Moving a directory to a different parent has to update the ".." + entry inside the moved directory: it stores the starting cluster + of the parent directory - cluster 0 when the parent is the root + directory. Exercise every parent transition and verify both the + path resolution through ".." and the on-media entry itself. */ + status = fx_directory_create(&ram_disk, "/DOTA"); + status += fx_directory_create(&ram_disk, "/DOTB"); + status += fx_file_create(&ram_disk, "/MARKR.TXT"); + status += fx_file_create(&ram_disk, "/DOTA/MARKA.TXT"); + status += fx_file_create(&ram_disk, "/DOTB/MARKB.TXT"); + status += fx_directory_create(&ram_disk, "/DOTA/SUB"); + return_if_fail( status == FX_SUCCESS); + + /* The probe itself has to work before anything is moved: ".." of a + new directory leads to the parent it was created in. */ + status = dot_dot_probe("/DOTA/SUB/../MARKA.TXT"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_probe("/DOTA/SUB/../MARKB.TXT"); + return_if_fail( status == FX_NOT_FOUND); + + /* Move the directory to a different parent directory. Exactly one + directory entry write - the ".." update - is added to the two + parent entry writes. */ +#ifndef FX_MEDIA_STATISTICS_DISABLE + entry_writes_before = ram_disk.fx_media_directory_entry_writes; +#endif + status = fx_directory_rename(&ram_disk, "/DOTA/SUB", "/DOTB/SUB"); + return_if_fail( status == FX_SUCCESS); +#ifndef FX_MEDIA_STATISTICS_DISABLE + return_if_fail( (ram_disk.fx_media_directory_entry_writes - entry_writes_before) == 3); +#endif + + /* ".." now leads to the new parent and only to the new parent. */ + status = dot_dot_probe("/DOTB/SUB/../MARKB.TXT"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_probe("/DOTB/SUB/../MARKA.TXT"); + return_if_fail( status == FX_NOT_FOUND); + + /* The on-media ".." entry stores the new parent's starting cluster. */ + status = directory_cluster_read("/DOTB", &parent_cluster); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_cluster_read("/DOTB/SUB", &dot_dot_cluster); + return_if_fail( status == FX_SUCCESS); + return_if_fail( (dot_dot_cluster == parent_cluster) && (parent_cluster != 0)); + + /* Move the directory into the root directory: ".." stores cluster 0 + there, per the FAT convention directory create itself writes. */ + status = fx_directory_rename(&ram_disk, "/DOTB/SUB", "/SUB"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_probe("/SUB/../MARKR.TXT"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_probe("/SUB/../MARKB.TXT"); + return_if_fail( status == FX_NOT_FOUND); + status = dot_dot_cluster_read("/SUB", &dot_dot_cluster); + return_if_fail( (status == FX_SUCCESS) && (dot_dot_cluster == 0)); + + /* Move the directory back out of the root directory: 0 becomes the + new parent's cluster again. */ + status = fx_directory_rename(&ram_disk, "/SUB", "/DOTA/SUB"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_probe("/DOTA/SUB/../MARKA.TXT"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_probe("/DOTA/SUB/../MARKR.TXT"); + return_if_fail( status == FX_NOT_FOUND); + status = directory_cluster_read("/DOTA", &parent_cluster); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_cluster_read("/DOTA/SUB", &dot_dot_cluster); + return_if_fail( (status == FX_SUCCESS) && (dot_dot_cluster == parent_cluster) && (parent_cluster != 0)); + + /* A rename inside one parent does not touch ".." - and adds no third + directory entry write. */ + status = fx_directory_create(&ram_disk, "/DOTC"); + return_if_fail( status == FX_SUCCESS); +#ifndef FX_MEDIA_STATISTICS_DISABLE + entry_writes_before = ram_disk.fx_media_directory_entry_writes; +#endif + status = fx_directory_rename(&ram_disk, "/DOTC", "/DOTD"); + return_if_fail( status == FX_SUCCESS); +#ifndef FX_MEDIA_STATISTICS_DISABLE + return_if_fail( (ram_disk.fx_media_directory_entry_writes - entry_writes_before) == 2); +#endif + status = dot_dot_probe("/DOTD/../MARKR.TXT"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_cluster_read("/DOTD", &dot_dot_cluster); + return_if_fail( (status == FX_SUCCESS) && (dot_dot_cluster == 0)); + + /* Same inside a sub-directory: ".." keeps the parent's cluster. */ + status = fx_directory_rename(&ram_disk, "/DOTA/SUB", "/DOTA/SUB2"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_probe("/DOTA/SUB2/../MARKA.TXT"); + return_if_fail( status == FX_SUCCESS); + status = dot_dot_cluster_read("/DOTA/SUB2", &dot_dot_cluster); + return_if_fail( (status == FX_SUCCESS) && (dot_dot_cluster == parent_cluster)); + + /* Close the media. */ + status = fx_media_close(&ram_disk); + return_if_fail( status == FX_SUCCESS); + printf("SUCCESS!\n"); test_control_return(0); }