/* * portable_basename.c * * Author: Intel A80486DX2-66 * License: Unlicense */ #include "portable_basename.h" static char* correct_slashes(const char* path) { char* new_path = strdup(path); if (new_path == NULL) return NULL; char* ptr = new_path; while (*ptr != '\0') { if (*ptr == '\\') *ptr = '/'; ptr++; } return new_path; } char* portable_basename(const char* raw_path) { char* path = correct_slashes(raw_path); if (path == NULL) return NULL; char* base = malloc(FILENAME_MAX * sizeof(char)); if (base == NULL) { free(path); return NULL; } size_t fname_len = strlen(path); const char* last_slash = strrchr(path, '/'); if (last_slash != NULL) fname_len = strlen(last_slash + 1); memcpy(base, last_slash + 1, fname_len); base[fname_len] = '\0'; return base; } #ifdef TEST # include # include # include # include // system identification # ifdef _WIN32 # define SYS_NT # include # endif # if defined(__unix__) || defined(__APPLE__) # define SYS_UNIX # endif // macros # if defined(SYS_UNIX) && !defined(CLOCK_MONOTONIC_RAW) # define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC # endif // function prototypes long double clock_hr_microsec(void); # ifdef SYS_NT void WinAPI_perror(const char* s); # endif static void func_expect(const char* path, const char* expected_output, bool expect_to_succeed); long double clock_hr_microsec(void) { # ifdef SYS_NT // TODO: FIXME: less accurate than Unix LARGE_INTEGER counter; if (!QueryPerformanceCounter(&counter)) { WinAPI_perror("QueryPerformanceCounter"); exit(EXIT_FAILURE); } return (long double) (counter.QuadPart * 1000000ULL); # elif SYS_UNIX struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); return ((long double) (ts.tv_sec * 1000000000ULL + ts.tv_nsec)) / 1000.l; # else return (long double) (clock() * 1000000ULL); # endif } // macros # ifdef SYS_NT # define WinAPI_perror_COMMON_MACRO \ fprintf(stderr, "%s: %lu, %s", s, errorCode, errorString) // function implementations void WinAPI_perror(const char* s) { DWORD errorCode = GetLastError(); LPSTR errorString = NULL; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&errorString, 0, NULL ); if (errorString == NULL) { errorString = "???\n"; WinAPI_perror_COMMON_MACRO; } else { WinAPI_perror_COMMON_MACRO; LocalFree(errorString); } } # endif // macros # if defined(SYS_NT) LONGLONG QPF_frequency; # define CLOCK_HR_DIFFTIME_MS(t2, t1) \ ((long double) ((t2) - (t1))) / ((long double) QPF_frequency) # elif defined(SYS_UNIX) # define CLOCK_HR_DIFFTIME_MS(t2, t1) \ ((t2) - (t1)) # else # define CLOCK_HR_DIFFTIME_MS(t2, t1) \ ((long double) ((t2) - (t1)) * 1000000.L) / \ ((long double) CLOCKS_PER_SEC) # endif // global variables uintmax_t tests_failed = 0; // function implementations static void func_expect(const char* path, const char* expected_output, bool expect_to_succeed) { long double t1 = 0, t2 = 0; t1 = clock_hr_microsec(); char* output = portable_basename(path); t2 = clock_hr_microsec(); if (output == NULL) { perror("portable_basename"); exit(EXIT_FAILURE); } const char* caption = expect_to_succeed ? "Expected output " : "Unexpected output"; long double t = CLOCK_HR_DIFFTIME_MS(t2, t1); printf("Input path : '%s'\n" "%s: '%s'\n" "Actual output : '%s'\n" "Time, clock_t : %.3Lf microsec\n" "Test result : ", path, caption, expected_output, output, t); if ((bool) (strcmp(output, expected_output)) == expect_to_succeed) { tests_failed++; free(output); puts("Failed!\n"); return; } puts("Passed\n"); free(output); fflush(stdout); } // macros #define STRINGIZE(x) #x #define INT2STR(x) STRINGIZE(x) #define TIMES_TO_CALL 65535 int main(void) { # ifdef SYS_NT // set console output code page to the native one SetConsoleOutputCP(GetACP()); // get CPU frequency LARGE_INTEGER freq; if (!QueryPerformanceFrequency(&freq)) { WinAPI_perror("QueryPerformanceFrequency"); return EXIT_FAILURE; } QPF_frequency = freq.QuadPart; # endif // positive tests func_expect("/", "", true); func_expect("/usr/include/stdlib.h", "stdlib.h", true); func_expect("C:\\Windows\\Fonts\\consola.ttf", "consola.ttf", true); func_expect("\\..\\..\\directory\\.e.x.e.c.u.t.a.b.l.e.", ".e.x.e.c.u.t.a.b.l.e.", true); // negative tests func_expect("/a/.b.c.", "b.c", false); func_expect("/a/.b.c.", ".b.c", false); func_expect("/a/.b.c.", "b.c.", false); func_expect("/a/.b.c.\\", ".b.c.", false); printf("Failed tests: %" PRIuMAX "\n", tests_failed); // timing test puts("\nBenchmarking (" INT2STR(TIMES_TO_CALL) " calls):"); fflush(stdout); #define BENCHMARKING_STRING_SET \ "a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r\\s\\t\\u\\v\\w\\x\\y\\z\\.\\file", \ "/mnt/cdrom/usr/bin/include/library/test_files/test.h" const char* string_set[] = { BENCHMARKING_STRING_SET, // HACK: try to prevent caching by using different pointers BENCHMARKING_STRING_SET, BENCHMARKING_STRING_SET, BENCHMARKING_STRING_SET }; const size_t string_set_len = sizeof(string_set) / sizeof(char*); long double t_beginning = 0, t_end = 0; long double t_total = 0.l; for (uint_fast16_t i = 0; i < TIMES_TO_CALL; i++) { const char* selected_string = string_set[i % string_set_len]; t_beginning = clock_hr_microsec(); char* output = portable_basename(selected_string); t_end = clock_hr_microsec(); if (output == NULL) { perror("portable_basename"); return EXIT_FAILURE; } free(output); t_total += CLOCK_HR_DIFFTIME_MS(t_end, t_beginning); } printf("Total time: %.3Lf microsec, avg. %.3Lf microsec\n", t_total, t_total / ((long double) TIMES_TO_CALL)); return (tests_failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS); } #endif /* TEST */