/*************************************************************************** * Copyright (c) 2024 Microsoft Corporation * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at * https://opensource.org/licenses/MIT. * * SPDX-License-Identifier: MIT **************************************************************************/ /**************************************************************************/ /**************************************************************************/ /** */ /** FileX Component */ /** */ /** Directory */ /** */ /**************************************************************************/ /**************************************************************************/ #define FX_SOURCE_CODE /* Include necessary system files. */ #include "fx_api.h" #include "fx_system.h" #include "fx_directory.h" #include "fx_utility.h" #ifndef FX_NO_LOCAL_PATH FX_LOCAL_PATH_SETUP #endif /**************************************************************************/ /* */ /* FUNCTION RELEASE */ /* */ /* _fx_directory_search PORTABLE C */ /* 6.1.10 */ /* AUTHOR */ /* */ /* William E. Lamie, Microsoft Corporation */ /* */ /* DESCRIPTION */ /* */ /* This function searches the media for the supplied name. The search */ /* routine will find files as well as directory names. */ /* */ /* INPUT */ /* */ /* media_ptr Media control block pointer */ /* name_ptr Name pointer */ /* entry_ptr Pointer to directory entry */ /* record */ /* last_dir_ptr Pointer to destination for */ /* the last directory entry */ /* last_name_ptr Pointer to the last name */ /* token that was searched for */ /* */ /* OUTPUT */ /* */ /* return status */ /* */ /* CALLS */ /* */ /* _fx_directory_name_extract Extract directory name from */ /* input string */ /* _fx_directory_entry_read Read entries from root dir */ /* _fx_utility_FAT_entry_read Read FAT entries to calculate */ /* the sub-directory size */ /* */ /* CALLED BY */ /* */ /* FileX System Functions */ /* */ /* RELEASE HISTORY */ /* */ /* DATE NAME DESCRIPTION */ /* */ /* 05-19-2020 William E. Lamie Initial Version 6.0 */ /* 09-30-2020 William E. Lamie Modified comment(s), and */ /* added conditional to */ /* disable media search cache, */ /* resulting in version 6.1 */ /* 06-02-2021 Bhupendra Naphade Modified comment(s), and */ /* added check for */ /* volume label, */ /* resulting in version 6.1.7 */ /* 01-31-2022 William E. Lamie Modified comment(s), and */ /* fixed path compare, */ /* resulting in version 6.1.10 */ /* */ /**************************************************************************/ UINT _fx_directory_search(FX_MEDIA *media_ptr, CHAR *name_ptr, FX_DIR_ENTRY *entry_ptr, FX_DIR_ENTRY *last_dir_ptr, CHAR **last_name_ptr) { ULONG i, n; UINT found; UINT status; #ifndef FX_MEDIA_DISABLE_SEARCH_CACHE UINT v, j; #endif /* FX_MEDIA_DISABLE_SEARCH_CACHE */ ULONG cluster, next_cluster = 0; ULONG64 directory_size; CHAR *dir_name_ptr; CHAR *work_ptr; CHAR *source_name_ptr; CHAR *destination_name_ptr; FX_DIR_ENTRY search_dir; FX_DIR_ENTRY *search_dir_ptr; CHAR *name, alpha, name_alpha; #ifndef FX_MEDIA_DISABLE_SEARCH_CACHE UINT index; CHAR *path_ptr = FX_NULL; CHAR *original_name = name_ptr; #endif #ifndef FX_MEDIA_STATISTICS_DISABLE /* Increment the number of directory search requests. */ media_ptr -> fx_media_directory_searches++; #endif /* Setup pointer to media name buffer. */ name = media_ptr -> fx_media_name_buffer; /* Setup the last directory, if required. */ if (last_dir_ptr) { /* Set the first character of the directory entry to NULL to indicate root or no directory. */ last_dir_ptr -> fx_dir_entry_name[0] = 0; } /* Determine if the file name has a full directory path. */ if ((*name_ptr == '\\') || (*name_ptr == '/')) { /* Directory name has full path, set the search pointer to NULL. */ search_dir_ptr = FX_NULL; } else { /* Set the initial search directory to the current working directory - if there is one. */ /* First check for a local path pointer stored in the thread control block. This is only available in ThreadX Version 4 and above. */ #ifndef FX_NO_LOCAL_PATH if (_tx_thread_current_ptr -> tx_thread_filex_ptr) { /* Determine if the local directory is not the root directory. */ if (((FX_PATH *)_tx_thread_current_ptr -> tx_thread_filex_ptr) -> fx_path_directory.fx_dir_entry_name[0]) { /* Start at the current working directory of the media. */ search_dir = ((FX_PATH *)_tx_thread_current_ptr -> tx_thread_filex_ptr) -> fx_path_directory; #ifndef FX_MEDIA_DISABLE_SEARCH_CACHE /* Setup pointer to the path. */ path_ptr = ((FX_PATH *)_tx_thread_current_ptr -> tx_thread_filex_ptr) -> fx_path_string; #endif /* Set the internal pointer to the search directory as well. */ search_dir_ptr = &search_dir; } else { /* We are searching in the root directory. */ search_dir_ptr = FX_NULL; } } else #endif if (media_ptr -> fx_media_default_path.fx_path_directory.fx_dir_entry_name[0]) { /* Start at the current working directory of the media. */ search_dir = media_ptr -> fx_media_default_path.fx_path_directory; #ifndef FX_MEDIA_DISABLE_SEARCH_CACHE /* Setup pointer to the path. */ path_ptr = media_ptr -> fx_media_default_path.fx_path_string; #endif /* Set the internal pointer to the search directory as well. */ search_dir_ptr = &search_dir; } else { /* The current default directory is the root so just set the search directory pointer to NULL. */ search_dir_ptr = FX_NULL; } } #ifndef FX_MEDIA_DISABLE_SEARCH_CACHE /* Determine if there is a previously found directory entry. */ if (media_ptr -> fx_media_last_found_name[0]) { UINT match; CHAR *temp_ptr, beta; /* Yes, there is a previously found directory in our cache. */ /* Initialize the index. */ v = 0; /* Determine if there is a full path. */ if ((*name_ptr == '\\') || (*name_ptr == '/')) { /* Yes, the full path is in the name buffer. Simply compare with what is in the last search buffer. */ while ((v < (FX_MAX_LAST_NAME_LEN - 1)) && (name_ptr[v])) { /* Pickup the respective name characters. */ alpha = name_ptr[v]; beta = media_ptr -> fx_media_last_found_name[v]; /* Ensure directory markers are the same. */ if (alpha == '\\') { alpha = '/'; } if (beta == '\\') { beta = '/'; } /* Is the name the same? */ if (alpha != beta) { /* Break out of loop! */ break; } /* Move to next character. */ v++; } /* Determine if we have a match. */ if (name_ptr[v] != media_ptr -> fx_media_last_found_name[v]) { match = FX_FALSE; } else { match = FX_TRUE; } } else { /* Default to found. */ match = FX_TRUE; /* Determine if there is a default path to compare with. */ if (path_ptr) { /* Yes, compare the current path with what is contained in the last found buffer. Note that the last found name must have at least one path separator as well as room for at least one character for a name. */ while ((v < (FX_MAX_LAST_NAME_LEN - 1)) && (path_ptr[v])) { /* Pickup the respective name characters. */ alpha = media_ptr -> fx_media_last_found_name[v]; beta = path_ptr[v]; /* Ensure directory markers are the same. */ if (alpha == '\\') { alpha = '/'; } if (beta == '\\') { beta = '/'; } /* Is the name the same? */ if (alpha != beta) { /* Break out of loop! */ break; } /* Move to next character. */ v++; } /* Determine if we don't have a match... The relative path must be exhausted. */ if (path_ptr[v]) { match = FX_FALSE; } } /* Determine if we still have a match. */ if (match) { /* Now examine the rest of the last name and the newly supplied input name. */ /* Determine if a valid directory separator is present. */ if ((media_ptr -> fx_media_last_found_name[v] != '\\') && (media_ptr -> fx_media_last_found_name[v] != '/')) { /* Set match to false - invalid directory path separator. */ match = FX_FALSE; } else { /* Position past the next directory separator in the last name string. */ v++; } /* Yes, the full path is in the name buffer. Simply compare with what is in the last search buffer. */ j = 0; while ((v < (FX_MAX_LAST_NAME_LEN - 1)) && (name_ptr[j]) && (match)) { /* Pickup the respective name characters. */ alpha = name_ptr[j]; beta = media_ptr -> fx_media_last_found_name[v]; /* Ensure directory markers are the same. */ if (alpha == '\\') { alpha = '/'; } if (beta == '\\') { beta = '/'; } /* Is the name the same? */ if (alpha != beta) { /* Break out of loop! */ break; } /* Move to next character. */ v++; j++; } /* Avoid accessing fx_media_last_found_name out of bounds. */ if (v >= 256) { match = FX_FALSE; } else if ((match) && (name_ptr[j] != media_ptr -> fx_media_last_found_name[v])) { /* We don't have a match. */ match = FX_FALSE; } } } /* Now determine if we actually found a match. */ if (match) { /* Save the directory entry name pointer. */ temp_ptr = entry_ptr -> fx_dir_entry_name; /* Copy the saved directory entry. */ *entry_ptr = media_ptr -> fx_media_last_found_entry; /* Restore the directory entry name pointer. */ entry_ptr -> fx_dir_entry_name = temp_ptr; /* Copy the directory name into the destination directory name. */ for (index = 0; index < FX_MAX_LONG_NAME_LEN; index++) { /* Copy character into the destination. */ temp_ptr[index] = media_ptr -> fx_media_last_found_file_name[index]; /* See if we have copied the NULL termination character. */ if (temp_ptr[index] == (CHAR)FX_NULL) { /* Determine if we should break here or at the top of the loop. */ if (index < (FX_MAX_LONG_NAME_LEN - 1)) { /* Yes, break out of the loop early. */ break; } } } /* Determine if there is a search directory to copy. */ if ((last_dir_ptr) && (media_ptr -> fx_media_last_found_directory_valid)) { /* Yes, there was a search directory... and one is requested in this request as well. Simply copy it into the destination. */ /* First, save the name pointer from the list directory pointer. */ destination_name_ptr = last_dir_ptr -> fx_dir_entry_name; /* Copy the entire directory entry structure. */ *last_dir_ptr = media_ptr -> fx_media_last_found_directory; /* Restore the original name buffer pointer. */ last_dir_ptr -> fx_dir_entry_name = destination_name_ptr; /* Pickup pointer to name to copy. */ source_name_ptr = media_ptr -> fx_media_last_found_directory.fx_dir_entry_name; /* Loop to copy the name into the last directory name buffer. */ for (n = 0; n < FX_MAX_LONG_NAME_LEN; n++) { /* Copy a character. */ destination_name_ptr[n] = source_name_ptr[n]; /* See if we have copied the NULL termination character. */ if (source_name_ptr[n] == (CHAR)FX_NULL) { /* Determine if we should break here or at the top of the loop. */ if (n < (FX_MAX_LONG_NAME_LEN - 1)) { /* Yes, break out of the loop early. */ break; } } } } /* Return the last name pointer, if required. */ if (last_name_ptr) { /* Just set the last name to initial name string. */ *last_name_ptr = temp_ptr; } #ifndef FX_MEDIA_STATISTICS_DISABLE /* Increment the number of directory search cache hits. */ media_ptr -> fx_media_directory_search_cache_hits++; #endif /* Return success. */ return(FX_SUCCESS); } } /* Not a sequential search, invalidate the saved information. */ media_ptr -> fx_media_last_found_name[0] = FX_NULL; #ifndef FX_MEDIA_STATISTICS_DISABLE /* If trace is enabled, insert this event into the trace buffer. */ FX_TRACE_IN_LINE_INSERT(FX_TRACE_INTERNAL_DIR_CACHE_MISS, media_ptr, media_ptr -> fx_media_directory_searches - media_ptr -> fx_media_directory_search_cache_hits, 0, 0, FX_TRACE_INTERNAL_EVENTS, 0, 0) #else /* If trace is enabled, insert this event into the trace buffer. */ FX_TRACE_IN_LINE_INSERT(FX_TRACE_INTERNAL_DIR_CACHE_MISS, media_ptr, 0, 0, 0, FX_TRACE_INTERNAL_EVENTS, 0, 0) #endif #endif /* Loop to traverse the directory paths to find the specified file. */ do { /* Remember the last name pointer, if required. */ if (last_name_ptr) { /* Just set the last name to initial name string. */ *last_name_ptr = name_ptr; } /* Extract file name. */ name_ptr = _fx_directory_name_extract(name_ptr, name); /* Calculate the directory size. */ if (search_dir_ptr) { /* Ensure that the search directory's last search cluster is cleared. */ search_dir_ptr -> fx_dir_entry_last_search_cluster = 0; /* Calculate the directory size by counting the allocated clusters for it. */ i = 0; cluster = search_dir_ptr -> fx_dir_entry_cluster; while (cluster < media_ptr -> fx_media_fat_reserved) { /* Increment the cluster count. */ i++; /* Read the next FAT entry. */ status = _fx_utility_FAT_entry_read(media_ptr, cluster, &next_cluster); /* Check the return status. */ if (status != FX_SUCCESS) { /* Return the bad status. */ return(status); } /* Check for error situation. */ if ((cluster < FX_FAT_ENTRY_START) || (cluster == next_cluster) || (i > media_ptr -> fx_media_total_clusters)) { /* Return the bad status. */ return(FX_FAT_READ_ERROR); } cluster = next_cluster; } /* Now we can calculate the directory size. */ directory_size = (((ULONG64) media_ptr -> fx_media_bytes_per_sector) * ((ULONG64) media_ptr -> fx_media_sectors_per_cluster) * i) / (ULONG64) FX_DIR_ENTRY_SIZE; /* Also save this in the directory entry so we don't have to calculate it later. */ search_dir_ptr -> fx_dir_entry_file_size = directory_size; /* If required, copy the last search directory entry into the destination. */ if (last_dir_ptr) { /* Copy the last search directory into the destination. */ /* First, save the name pointer from the list directory pointer. */ destination_name_ptr = last_dir_ptr -> fx_dir_entry_name; /* Copy the entire directory entry structure. */ *last_dir_ptr = *search_dir_ptr; /* Restore the original name buffer pointer. */ last_dir_ptr -> fx_dir_entry_name = destination_name_ptr; /* Pickup pointer to name to copy. */ source_name_ptr = search_dir_ptr -> fx_dir_entry_name; /* Loop to copy the name into the last directory name buffer. */ for (n = 0; n < FX_MAX_LONG_NAME_LEN; n++) { /* Copy a character. */ destination_name_ptr[n] = source_name_ptr[n]; /* See if we have copied the NULL termination character. */ if (source_name_ptr[n] == (CHAR) FX_NULL) { /* Determine if we should break here or at the top of the loop. */ if (n < (FX_MAX_LONG_NAME_LEN - 1)) { /* Yes, break out of the loop early. */ break; } } } } } else { /* Directory size is the number of entries in the root directory. */ directory_size = (ULONG)media_ptr -> fx_media_root_directory_entries; } /* Loop through entries in the directory. Yes, this is a linear search! */ i = 0; found = FX_FALSE; do { /* Read an entry from the directory. */ status = _fx_directory_entry_read(media_ptr, search_dir_ptr, &i, entry_ptr); i++; /* Check for error status. */ if (status != FX_SUCCESS) { return(status); } /* Determine if this is the last directory entry. */ if ((UCHAR)entry_ptr -> fx_dir_entry_name[0] == (UCHAR)FX_DIR_ENTRY_DONE) { break; } /* Determine if the entry is a volume label entry */ if ((entry_ptr -> fx_dir_entry_attributes & FX_VOLUME)) { continue; } /* Determine if this is an empty entry. */ if (((UCHAR)entry_ptr -> fx_dir_entry_name[0] == (UCHAR)FX_DIR_ENTRY_FREE) && (entry_ptr -> fx_dir_entry_short_name[0] == 0)) { continue; } /* Compare the input name and extension with the directory entry. */ work_ptr = &name[0]; dir_name_ptr = &(entry_ptr -> fx_dir_entry_name[0]); /* Loop to compare names. */ do { /* Pickup character of directory name. */ alpha = *dir_name_ptr; /* Pickup character of name. */ name_alpha = *work_ptr; /* Determine if its case needs to be changed. */ if ((alpha >= 'a') && (alpha <= 'z')) { /* Yes, make upper case. */ alpha = (CHAR)((INT)alpha - 0x20); } /* Determine if its case needs to be changed. */ if ((name_alpha >= 'a') && (name_alpha <= 'z')) { /* Yes, make upper case. */ name_alpha = (CHAR)((INT)name_alpha - 0x20); } /* Compare name with directory name. */ if (alpha != name_alpha) { /* The names don't match, get out of the loop. */ break; } /* Otherwise, increment the name pointers. */ work_ptr++; dir_name_ptr++; } while (*dir_name_ptr); /* Determine if the requested name has been found. If so, return success to the caller. */ if ((*dir_name_ptr == 0) && (*work_ptr == *dir_name_ptr)) { /* Yes, the name was located. All pertinent directory information is in the directory entry field. */ found = FX_TRUE; } /* Determine if there is a short name to check. */ else if (entry_ptr -> fx_dir_entry_short_name[0] != 0) { /* Yes, check for the short part of the name. */ /* Compare the input name and extension with the directory entry. */ work_ptr = &name[0]; dir_name_ptr = &(entry_ptr -> fx_dir_entry_short_name[0]); /* Loop to compare names. */ do { /* Pickup character of directory name. */ alpha = *dir_name_ptr; /* Pickup character of name. */ name_alpha = *work_ptr; /* Determine if its case needs to be changed. */ if ((name_alpha >= 'a') && (name_alpha <= 'z')) { /* Yes, make upper case. */ name_alpha = (CHAR)((INT)name_alpha - 0x20); } /* Compare name with directory name. */ if (alpha != name_alpha) { /* The names don't match, get out of the loop. */ break; } /* Otherwise, move the name pointers and increment the count. */ work_ptr++; dir_name_ptr++; } while (*dir_name_ptr); /* Determine if the names match. */ if ((*dir_name_ptr == 0) && (*work_ptr == *dir_name_ptr)) { /* Yes, the name was located. All pertinent directory information is in the directory entry field. */ found = FX_TRUE; } } } while ((i < directory_size) && (!found)); /* Now determine if we have a match. */ if (!found) { /* Return a "not found" status to the caller. */ return(FX_NOT_FOUND); } /* Determine if the found entry is indeed a sub-directory. */ if (entry_ptr -> fx_dir_entry_attributes & FX_DIRECTORY) { /* Move the directory search pointer to this entry. */ search_dir = *entry_ptr; search_dir_ptr = &search_dir; /* Ensure that the search directory's last search cluster is cleared. */ search_dir_ptr -> fx_dir_entry_last_search_cluster = 0; /* Now determine if the new search directory is the root directory. */ if (!search_dir_ptr -> fx_dir_entry_cluster) { /* This is a backward link to the root directory. Make sure this is indicated in the search directory information. */ search_dir_ptr -> fx_dir_entry_name[0] = 0; /* Determine if we need to remember this in the last directory searched return area. */ if (last_dir_ptr) { /* Yes, return this value to the caller. */ /* First, save the name pointer from the list directory pointer. */ destination_name_ptr = last_dir_ptr -> fx_dir_entry_name; /* Copy the entire directory entry structure. */ *last_dir_ptr = *search_dir_ptr; /* Restore the original name buffer pointer. */ last_dir_ptr -> fx_dir_entry_name = destination_name_ptr; /* Pickup pointer to name to copy. */ source_name_ptr = search_dir_ptr -> fx_dir_entry_name; /* Loop to copy the name into the last directory name buffer. */ for (n = 0; n < FX_MAX_LONG_NAME_LEN; n++) { /* Copy a character. */ destination_name_ptr[n] = source_name_ptr[n]; } } /* Set the search directory pointer to NULL to indicate we are at the root directory. */ search_dir_ptr = FX_NULL; } } else { /* This is not a directory, we better return not found since we can't continue the search. */ if (name_ptr) { /* Return not-found status to caller. */ return(FX_NOT_FOUND); } } } while (name_ptr); /* If you reach this point, the directory is found absolutely, since !found will return directly in the loop above. */ #ifndef FX_MEDIA_DISABLE_SEARCH_CACHE /* At this point, cache the found information. If a subsequent search for the same name is done, it will return immediately. */ /* Set the index of the saved name string. */ v= 0; /* First, build the full path and name. */ if ((*original_name != '\\') && (*original_name != '/') && (path_ptr)) { /* Copy the path into the destination. */ while ((v< (FX_MAX_LAST_NAME_LEN - 1)) && (path_ptr[v])) { /* Copy one character. */ media_ptr -> fx_media_last_found_name[v] = path_ptr[v]; /* Move to next character. */ v++; } } /* Now see if there is no directory path symbol in the name itself. */ if ((*original_name != '\\') && (*original_name != '/')) { /* If there is room, place a directory separator character. */ if (v < (FX_MAX_LAST_NAME_LEN - 1)) { media_ptr -> fx_media_last_found_name[v++] = '/'; } } /* Now append the name to the path. */ j = 0; while ((v < FX_MAX_LAST_NAME_LEN) && (original_name[j])) { /* Copy one character. */ media_ptr -> fx_media_last_found_name[v] = original_name[j]; /* Move to next character. */ v++; j++; } /* Null terminate the last name string. */ if (v< FX_MAX_LAST_NAME_LEN) { /* Null terminate. */ media_ptr -> fx_media_last_found_name[v] = FX_NULL; } else { /* The string is too big, NULL the string so it won't be used in searching. */ media_ptr -> fx_media_last_found_name[0] = FX_NULL; } /* Determine if there is a search pointer. */ if (search_dir_ptr) { /* Yes, there is a search directory pointer so save it! */ media_ptr -> fx_media_last_found_directory = *search_dir_ptr; /* Indicate the search directory is valid. */ media_ptr -> fx_media_last_found_directory_valid = FX_TRUE; } else { /* Indicate the search directory is not valid. */ media_ptr -> fx_media_last_found_directory_valid = FX_FALSE; } /* Copy the directory entry. */ media_ptr -> fx_media_last_found_entry = *entry_ptr; /* Setup the directory entry for the last found internal file name. */ media_ptr -> fx_media_last_found_entry.fx_dir_entry_name = media_ptr -> fx_media_last_found_file_name; /* Copy the actual directory name into the cached directory name. */ for (index = 0; index < FX_MAX_LONG_NAME_LEN; index++) { /* Copy character into the cached directory name. */ media_ptr -> fx_media_last_found_file_name[index] = entry_ptr -> fx_dir_entry_name[index]; /* See if we have copied the NULL termination character. */ if (entry_ptr -> fx_dir_entry_name[index] == (CHAR)FX_NULL) { /* Check to see if we use the break to get out of the loop. */ if (index < (FX_MAX_LONG_NAME_LEN - 1)) { /* Yes, not at the end of the string, break. */ break; } } } #endif return(FX_SUCCESS); }