Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions CoDeLib/FileUtils/src/FileUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,34 @@ int _HandleFtwCallback_Remove(const char *fpath, const struct stat *sb,
// Public function implementations
//=========================

bool IsPathNormalized(const char *const pPath) {
if (pPath == NULL) {
return false;
}

for (size_t i = 0; i < strlen(pPath); ++i) {
Copy link

Copilot AI Jun 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store the result of strlen(pPath) in a local variable before the loop to avoid multiple calls during iteration.

Suggested change
for (size_t i = 0; i < strlen(pPath); ++i) {
size_t pathLength = strlen(pPath);
for (size_t i = 0; i < pathLength; ++i) {

Copilot uses AI. Check for mistakes.
if (pPath[i] == '\\') {
return false;
}
}

return true;
}

char *NormailizePathSeparatorsInPlace(char *pPath) {
if (pPath == NULL) {
return NULL;
}

for (size_t i = 0; i < strlen(pPath); ++i) {
Copy link

Copilot AI Jun 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing the length of pPath in a variable before iterating can improve performance by preventing repeated calls to strlen.

Suggested change
for (size_t i = 0; i < strlen(pPath); ++i) {
size_t pathLength = strlen(pPath);
for (size_t i = 0; i < pathLength; ++i) {

Copilot uses AI. Check for mistakes.
if (pPath[i] == '\\') {
pPath[i] = '/';
}
}

return pPath;
}

// Based on
// https://nachtimwald.com/2019/07/10/recursive-create-directory-in-c-revisited/
bool RecursiveMkdir(const char *const pDirname) {
Expand Down Expand Up @@ -114,9 +142,11 @@ bool RecursiveMkdir(const char *const pDirname) {
// Adds null terminator after the seperator
pPathToCreate[dirnamePartLength] = '\0';

success = _CreateDir(pPathToCreate);
if (!success) {
break;
if (!PathExists(pPathToCreate)) {
success = _CreateDir(pPathToCreate);
if (!success) {
break;
}
}

pTargetPath++;
Expand Down Expand Up @@ -171,7 +201,8 @@ bool IsAbsolutePath(const char *pPath) {

bool GetAbsolutePath(const char *pPath, char *const pAbsolutePath,
const size_t absolutePathSize) {
if (pPath == NULL || pAbsolutePath == NULL || absolutePathSize == 0) {
if (pPath == NULL || pAbsolutePath == NULL || absolutePathSize == 0 ||
!IsPathNormalized(pPath)) {
return false;
}

Expand Down Expand Up @@ -201,6 +232,8 @@ bool GetAbsolutePath(const char *pPath, char *const pAbsolutePath,
memcpy(&pAbsolutePath[0], pPath, sizeof(char) * (pathLenght + 1));
}

NormailizePathSeparatorsInPlace(pAbsolutePath);

return true;
}

Expand Down Expand Up @@ -232,6 +265,8 @@ char *GetCurrentWorkingDirectory(char *pFullPath, size_t fullPathSize) {
pFullPath[currentPathLength + 1] = '\0';
}

NormailizePathSeparatorsInPlace(pFullPath);

return pFullPath;
}

Expand Down
143 changes: 88 additions & 55 deletions CoDeLib/Test/src/TestFileUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,62 @@ TEST_TEAR_DOWN(TestFileUtils) {
}
}

//==============================
// IsPathNormalized(...)
//==============================

TEST(TestFileUtils, test_IsPathNormalized_ReturnsFalseIfPathIsNull) {
TEST_ASSERT_FALSE(IsPathNormalized(NULL));
}

TEST(TestFileUtils, test_IsPathNormalized_ReturnsTrueIfPathIsEmpty) {
TEST_ASSERT_TRUE(IsPathNormalized(""));
}

TEST(TestFileUtils, test_IsPathNormalized_ReturnsFalseIfPathIsNotNormalized) {
TEST_ASSERT_FALSE(IsPathNormalized("a\\b/c\\d"));
}

TEST(TestFileUtils, test_IsPathNormalized_ReturnsTrueIfPathIsNormalized) {
TEST_ASSERT_TRUE(IsPathNormalized("a/b/c/d"));
}

//==============================
// NormailizePathSeparatorsInPlace(...)
//==============================

TEST(TestFileUtils,
test_NormailizePathSeparatorsInPlace_ReturnsFalseIfPathIsNull) {
TEST_ASSERT_NULL(NormailizePathSeparatorsInPlace(NULL));
}

TEST(TestFileUtils,
test_NormailizePathSeparatorsInPlace_NormalizesPathSeparators) {
RAII_STRING path = RaiiStringCreateFromCString("a\\b/c\\d");

char *pNormalizedPath = NormailizePathSeparatorsInPlace(path.pString);
TEST_ASSERT_EQUAL(pNormalizedPath, path.pString);
TEST_ASSERT_EQUAL_STRING("a/b/c/d", path.pString);
}

TEST(TestFileUtils,
test_NormailizePathSeparatorsInPlace_DoesNotChangeAlreadyNormalized) {
RAII_STRING path = RaiiStringCreateFromCString("a/b/c/d");

char *pNormalizedPath = NormailizePathSeparatorsInPlace(path.pString);
TEST_ASSERT_EQUAL(pNormalizedPath, path.pString);
TEST_ASSERT_EQUAL_STRING("a/b/c/d", path.pString);
}

TEST(TestFileUtils,
test_NormailizePathSeparatorsInPlace_NormalizesWithWindowsStylePrefix) {
RAII_STRING path = RaiiStringCreateFromCString("C:\\a\\b\\");

char *pNormalizedPath = NormailizePathSeparatorsInPlace(path.pString);
TEST_ASSERT_EQUAL(pNormalizedPath, path.pString);
TEST_ASSERT_EQUAL_STRING("C:/a/b/", path.pString);
}

//==============================
// IsAbsolutePath(...)
//==============================
Expand Down Expand Up @@ -213,39 +269,21 @@ TEST(TestFileUtils,
TEST_ASSERT_TRUE(success);
}

TEST(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_WindowsStyle) {
char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR];
char *pPath = "C:\\a\\b\\";

bool success =
GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR);

TEST_ASSERT_TRUE(success);
TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]);
TEST_ASSERT_EQUAL_STRING(pPath, localBuffer);
}

TEST(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_WindowsStyle) {
TEST(TestFileUtils, test_GetAbsolutePath_ReturnsFalseIfIfIsNotNormalized) {
char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR];
char *pPath = "C:\\a\\b";
char *pPath = "C:\\a/b\\";

bool success =
GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR);

TEST_ASSERT_TRUE(success);
TEST_ASSERT_NOT_EQUAL(pPath, &localBuffer[0]);
TEST_ASSERT_EQUAL_STRING(pPath, localBuffer);
TEST_ASSERT_FALSE(success);
}

TEST(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtention_WindowsStyle) {
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyleWindowsPrefix) {
char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR];
char *pPath = "C:\\a\\b.txt";
char *pPath = "C:/a/b/";

bool success =
GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR);
Expand Down Expand Up @@ -297,28 +335,6 @@ TEST(
TEST_ASSERT_EQUAL_STRING(pPath, localBuffer);
}

TEST(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_WindowsStyle) {
char expectedAbsolutePathBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR];
GetCurrentWorkingDirectory(expectedAbsolutePathBuffer,
MAX_PATH_LENGTH_WTH_TERMINATOR);
RAII_STRING expectedAbsolutePath =
RaiiStringCreateFromCString(expectedAbsolutePathBuffer);

char localBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR];
char *pPath = "b\\";

RaiiStringAppend_cString(&expectedAbsolutePath, pPath);

bool success =
GetAbsolutePath(pPath, localBuffer, MAX_PATH_LENGTH_WTH_TERMINATOR);

TEST_ASSERT_TRUE(success);
TEST_ASSERT_GREATER_THAN(strlen(pPath), strlen(&localBuffer[0]));
TEST_ASSERT_EQUAL_STRING(expectedAbsolutePath.pString, localBuffer);
}

TEST(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_PosixStyle) {
Expand Down Expand Up @@ -1204,6 +1220,30 @@ TEST(TestFileUtils,
//==============================

TEST_GROUP_RUNNER(TestFileUtils) {
// IsPathNormalized(...)
RUN_TEST_CASE(TestFileUtils,
test_IsPathNormalized_ReturnsFalseIfPathIsNull);
RUN_TEST_CASE(TestFileUtils,
test_IsPathNormalized_ReturnsTrueIfPathIsEmpty);
RUN_TEST_CASE(TestFileUtils,
test_IsPathNormalized_ReturnsFalseIfPathIsNotNormalized);
RUN_TEST_CASE(TestFileUtils,
test_IsPathNormalized_ReturnsTrueIfPathIsNormalized);

// NormalizePathSeparator(...)
RUN_TEST_CASE(
TestFileUtils,
test_NormailizePathSeparatorsInPlace_ReturnsFalseIfPathIsNull);
RUN_TEST_CASE(
TestFileUtils,
test_NormailizePathSeparatorsInPlace_NormalizesPathSeparators);
RUN_TEST_CASE(
TestFileUtils,
test_NormailizePathSeparatorsInPlace_DoesNotChangeAlreadyNormalized);
RUN_TEST_CASE(
TestFileUtils,
test_NormailizePathSeparatorsInPlace_NormalizesWithWindowsStylePrefix);

// IsAbsolutePath(...)
RUN_TEST_CASE(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsNull);
RUN_TEST_CASE(TestFileUtils, test_IsAbsolutePath_ReturnsFalseIfPathIsEmpty);
Expand Down Expand Up @@ -1257,15 +1297,11 @@ TEST_GROUP_RUNNER(TestFileUtils) {
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_AllowsBuffersToBeBiggerThanMaxPathLength);
RUN_TEST_CASE(TestFileUtils,
test_GetAbsolutePath_ReturnsFalseIfIfIsNotNormalized);
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_WindowsStyle);
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_NoTrailingSlash_WindowsStyle);
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtention_WindowsStyle);
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyleWindowsPrefix);
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_TrailingSlash_PosixStyle);
Expand All @@ -1275,9 +1311,6 @@ TEST_GROUP_RUNNER(TestFileUtils) {
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsAlreadyAbsoluteAndCopiesItToBuffer_FileExtentions_PosixStyle);
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_WindowsStyle);
RUN_TEST_CASE(
TestFileUtils,
test_GetAbsolutePath_ReturnsTrueIfPathIsRelativeAndCopiesItToBuffer_TrailingSlash_PosixStyle);
Expand Down
18 changes: 14 additions & 4 deletions CoDeLib/Test/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,29 @@ static void RunAllTests(void) {
}

int main(int argc, const char **argv) {
RAII_STRING fullPathToBenchmarkTestFiles;
RAII_STRING fullPathToBenchmarkTestFiles = {NULL, 0};

// Should end with '/'
RAII_STRING currentWorkingDirectory;
RAII_STRING currentWorkingDirectory = {NULL, 0};
bool runLongTests = false;

// TODO: use getopt(...)
if (argc < 4) {
printf("Not enough arguments provided\n");
return 1;
} else {
fullPathToBenchmarkTestFiles = RaiiStringCreateFromCString(argv[1]);
currentWorkingDirectory = RaiiStringCreateFromCString(argv[2]);
RAII_STRING unNormalizedFullPathToBenchmarkTestFiles =
RaiiStringCreateFromCString(argv[1]);
fullPathToBenchmarkTestFiles =
RaiiStringCreateFromCString(NormailizePathSeparatorsInPlace(
unNormalizedFullPathToBenchmarkTestFiles.pString));

RAII_STRING unNormalizedCurrentWorkingDirectory =
RaiiStringCreateFromCString(argv[2]);
currentWorkingDirectory =
RaiiStringCreateFromCString(NormailizePathSeparatorsInPlace(
unNormalizedCurrentWorkingDirectory.pString));

runLongTests = strcmp(argv[3], "true") == 0;
}

Expand Down
19 changes: 19 additions & 0 deletions CoDeLib/include/CoDeLib/FileUtils/FileUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@
#define MAX_PATH_LENGTH 256
#define MAX_PATH_LENGTH_WTH_TERMINATOR (MAX_PATH_LENGTH + 1)

#define PATH_SEPARATOR '/'

/*!
@brief Checks if a path is normalized. A normalized path has no escaped
backward slashes ('\\') and uses forward slashes ('/') as path separators.
@param pPath The path to check.
@return true if the path is normalized, false otherwise.
*/
bool IsPathNormalized(const char *const pPath);

/*!
@brief Normalizes the path separators in a path. This function replaces all
escaped backward slashes ('\\') with forward slashes ('/').
@param pPath The path to normalize. The path will be modified in place.
@return pPath if the path was successfully normalized and placed in pDestPath,
NULL otherwise
*/
char *NormailizePathSeparatorsInPlace(char *pPath);

/*!
@brief Recursively creates a directory. If the directory already exists, nothing
happens.
Expand Down