// Copyright 2007-2021 David Robillard // SPDX-License-Identifier: ISC #include "filesystem.h" #include "lilv_config.h" #include "lilv_internal.h" #ifdef _WIN32 # include # include # include # define F_OK 0 # define mkdir(path, flags) _mkdir(path) # define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) #else # include # include #endif #if USE_FLOCK && USE_FILENO # include #endif #include #include #include #include #include #include #ifndef PAGE_SIZE # define PAGE_SIZE 4096 #endif static bool lilv_is_dir_sep(const char c) { return c == '/' || c == LILV_DIR_SEP[0]; } #ifdef _WIN32 static inline bool is_windows_path(const char* path) { return (isalpha(path[0]) && (path[1] == ':' || path[1] == '|') && (path[2] == '/' || path[2] == '\\')); } #endif char* lilv_temp_directory_path(void) { #ifdef _WIN32 DWORD len = GetTempPath(0, NULL); char* buf = (char*)calloc(len, 1); if (GetTempPath(len, buf) == 0) { free(buf); return NULL; } return buf; #else const char* const tmpdir = getenv("TMPDIR"); return tmpdir ? lilv_strdup(tmpdir) : lilv_strdup("/tmp"); #endif } bool lilv_path_is_absolute(const char* path) { if (lilv_is_dir_sep(path[0])) { return true; } #ifdef _WIN32 if (is_windows_path(path)) { return true; } #endif return false; } bool lilv_path_is_child(const char* path, const char* dir) { if (path && dir) { const size_t path_len = strlen(path); const size_t dir_len = strlen(dir); return dir && path_len >= dir_len && !strncmp(path, dir, dir_len); } return false; } char* lilv_path_current(void) { return getcwd(NULL, 0); } char* lilv_path_absolute(const char* path) { if (lilv_path_is_absolute(path)) { return lilv_strdup(path); } char* cwd = getcwd(NULL, 0); char* abs_path = lilv_path_join(cwd, path); free(cwd); return abs_path; } char* lilv_path_absolute_child(const char* path, const char* parent) { if (lilv_path_is_absolute(path)) { return lilv_strdup(path); } return lilv_path_join(parent, path); } char* lilv_path_relative_to(const char* path, const char* base) { const size_t path_len = strlen(path); const size_t base_len = strlen(base); const size_t min_len = (path_len < base_len) ? path_len : base_len; // Find the last separator common to both paths size_t last_shared_sep = 0; for (size_t i = 0; i < min_len && path[i] == base[i]; ++i) { if (lilv_is_dir_sep(path[i])) { last_shared_sep = i; } } if (last_shared_sep == 0) { // No common components, return path return lilv_strdup(path); } // Find the number of up references ("..") required size_t up = 0; for (size_t i = last_shared_sep + 1; i < base_len; ++i) { if (lilv_is_dir_sep(base[i])) { ++up; } } #ifdef _WIN32 const bool use_slash = strchr(path, '/'); #else static const bool use_slash = true; #endif // Write up references const size_t suffix_len = path_len - last_shared_sep; char* rel = (char*)calloc(1, suffix_len + (up * 3) + 1); for (size_t i = 0; i < up; ++i) { if (use_slash) { memcpy(rel + (i * 3), "../", 3); } else { memcpy(rel + (i * 3), "..\\", 3); } } // Write suffix memcpy(rel + (up * 3), path + last_shared_sep + 1, suffix_len); return rel; } char* lilv_path_parent(const char* path) { const char* s = path + strlen(path) - 1; // Last character // Last non-slash for (; s > path && lilv_is_dir_sep(*s); --s) { } // Last internal slash for (; s > path && !lilv_is_dir_sep(*s); --s) { } // Skip duplicates for (; s > path && lilv_is_dir_sep(*s); --s) { } if (s == path) { // Hit beginning return lilv_is_dir_sep(*s) ? lilv_strdup("/") : lilv_strdup("."); } // Pointing to the last character of the result (inclusive) char* dirname = (char*)malloc(s - path + 2); memcpy(dirname, path, s - path + 1); dirname[s - path + 1] = '\0'; return dirname; } char* lilv_path_filename(const char* path) { const size_t path_len = strlen(path); size_t last_sep = path_len; for (size_t i = 0; i < path_len; ++i) { if (lilv_is_dir_sep(path[i])) { last_sep = i; } } if (last_sep >= path_len) { return lilv_strdup(path); } const size_t ret_len = path_len - last_sep; char* const ret = (char*)calloc(ret_len + 1, 1); strncpy(ret, path + last_sep + 1, ret_len); return ret; } char* lilv_path_join(const char* a, const char* b) { if (!a) { return (b && b[0]) ? lilv_strdup(b) : NULL; } const size_t a_len = strlen(a); const size_t b_len = b ? strlen(b) : 0; const bool a_end_is_sep = a_len > 0 && lilv_is_dir_sep(a[a_len - 1]); const size_t pre_len = a_len - (a_end_is_sep ? 1 : 0); char* path = (char*)calloc(1, a_len + b_len + 2); memcpy(path, a, pre_len); #ifndef _WIN32 path[pre_len] = '/'; #else // Use forward slash if it seems that the input paths do const bool a_has_slash = strchr(a, '/'); const bool b_has_slash = b && strchr(b, '/'); if (a_has_slash || b_has_slash) { path[pre_len] = '/'; } else { path[pre_len] = '\\'; } #endif if (b) { memcpy(path + pre_len + 1, b + (lilv_is_dir_sep(b[0]) ? 1 : 0), lilv_is_dir_sep(b[0]) ? b_len - 1 : b_len); } return path; } char* lilv_path_canonical(const char* path) { if (!path) { return NULL; } #if defined(_WIN32) char* out = (char*)malloc(MAX_PATH); GetFullPathName(path, MAX_PATH, out, NULL); return out; #else char* real_path = realpath(path, NULL); return real_path ? real_path : lilv_strdup(path); #endif } bool lilv_path_exists(const char* path) { #if USE_LSTAT struct stat st; return !lstat(path, &st); #else return !access(path, F_OK); #endif } bool lilv_is_directory(const char* path) { #if defined(_WIN32) const DWORD attrs = GetFileAttributes(path); return (attrs != INVALID_FILE_ATTRIBUTES) && (attrs & FILE_ATTRIBUTE_DIRECTORY); #else struct stat st; return !stat(path, &st) && S_ISDIR(st.st_mode); #endif } int lilv_copy_file(const char* src, const char* dst) { FILE* in = fopen(src, "r"); if (!in) { return errno; } FILE* out = fopen(dst, "w"); if (!out) { fclose(in); return errno; } char* page = (char*)malloc(PAGE_SIZE); size_t n_read = 0; int st = 0; while ((n_read = fread(page, 1, PAGE_SIZE, in)) > 0) { if (fwrite(page, 1, n_read, out) != n_read) { st = errno; break; } } if (!st && fflush(out)) { st = errno; } if (!st && (ferror(in) || ferror(out))) { st = EBADF; } free(page); fclose(in); fclose(out); return st; } int lilv_symlink(const char* oldpath, const char* newpath) { int ret = 0; if (strcmp(oldpath, newpath)) { #ifdef _WIN32 ret = !CreateHardLink(newpath, oldpath, 0); #else char* target = lilv_path_relative_to(oldpath, newpath); ret = symlink(target, newpath); free(target); #endif } return ret; } int lilv_flock(FILE* file, bool lock, bool block) { #ifdef _WIN32 HANDLE handle = (HANDLE)_get_osfhandle(fileno(file)); OVERLAPPED overlapped = {0}; if (lock) { const DWORD flags = (LOCKFILE_EXCLUSIVE_LOCK | (block ? 0 : LOCKFILE_FAIL_IMMEDIATELY)); return !LockFileEx(handle, flags, 0, UINT32_MAX, UINT32_MAX, &overlapped); } else { return !UnlockFileEx(handle, 0, UINT32_MAX, UINT32_MAX, &overlapped); } #elif USE_FLOCK && USE_FILENO return flock(fileno(file), (lock ? LOCK_EX : LOCK_UN) | (block ? 0 : LOCK_NB)); #else (void)file; (void)lock; (void)block; return 0; #endif } void lilv_dir_for_each(const char* path, void* data, void (*f)(const char* path, const char* name, void* data)) { #ifdef _WIN32 char* pat = lilv_path_join(path, "*"); WIN32_FIND_DATA fd; HANDLE fh = FindFirstFile(pat, &fd); if (fh != INVALID_HANDLE_VALUE) { do { if (strcmp(fd.cFileName, ".") && strcmp(fd.cFileName, "..")) { f(path, fd.cFileName, data); } } while (FindNextFile(fh, &fd)); } FindClose(fh); free(pat); #else DIR* dir = opendir(path); if (dir) { for (struct dirent* entry = NULL; (entry = readdir(dir));) { if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { f(path, entry->d_name, data); } } closedir(dir); } #endif } char* lilv_create_temporary_directory_in(const char* pattern, const char* parent) { #ifdef _WIN32 static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static const int n_chars = sizeof(chars) - 1; const size_t pattern_len = strlen(pattern); if (pattern_len < 7 || strcmp(pattern + pattern_len - 6, "XXXXXX")) { errno = EINVAL; return NULL; } char* const path_pattern = lilv_path_join(parent, pattern); const size_t path_pattern_len = strlen(path_pattern); char* const suffix = path_pattern + path_pattern_len - 6; for (unsigned attempt = 0; attempt < 128; ++attempt) { for (unsigned i = 0; i < 6; ++i) { suffix[i] = chars[rand() % n_chars]; } if (!mkdir(path_pattern, 0700)) { return path_pattern; } } return NULL; #else char* const path_pattern = lilv_path_join(parent, pattern); return mkdtemp(path_pattern); // NOLINT (not a leak) #endif } char* lilv_create_temporary_directory(const char* pattern) { char* const tmpdir = lilv_temp_directory_path(); char* const result = lilv_create_temporary_directory_in(pattern, tmpdir); free(tmpdir); return result; } int lilv_create_directories(const char* dir_path) { char* path = lilv_strdup(dir_path); const size_t path_len = strlen(path); size_t i = 1; #ifdef _WIN32 if (is_windows_path(dir_path)) { i = 3; } #endif char prev = path[0]; for (; i <= path_len; ++i) { const char c = path[i]; if (lilv_is_dir_sep(c) || (c == '\0' && !lilv_is_dir_sep(prev))) { path[i] = '\0'; if (mkdir(path, 0755) && (errno != EEXIST || !lilv_is_directory(path))) { free(path); return errno; } path[i] = c; } prev = c; } free(path); return 0; } static off_t lilv_file_size(const char* path) { struct stat buf; if (stat(path, &buf)) { return 0; } return buf.st_size; } int lilv_remove(const char* path) { #ifdef _WIN32 if (lilv_is_directory(path)) { return !RemoveDirectory(path); } #endif return remove(path); } bool lilv_file_equals(const char* a_path, const char* b_path) { if (!strcmp(a_path, b_path)) { return true; // Paths match } bool match = false; FILE* a_file = NULL; FILE* b_file = NULL; char* const a_real = lilv_path_canonical(a_path); char* const b_real = lilv_path_canonical(b_path); if (!strcmp(a_real, b_real)) { match = true; // Real paths match } else if (lilv_file_size(a_path) != lilv_file_size(b_path)) { match = false; // Sizes differ } else if (!(a_file = fopen(a_real, "rb")) || !(b_file = fopen(b_real, "rb"))) { match = false; // Missing file matches nothing } else { // TODO: Improve performance by reading chunks match = true; while (!feof(a_file) && !feof(b_file)) { if (fgetc(a_file) != fgetc(b_file)) { match = false; break; } } } if (a_file) { fclose(a_file); } if (b_file) { fclose(b_file); } free(a_real); free(b_real); return match; }