From 7448aa490fb843089e5785d5a80f8a07ee50f93a Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Sat, 23 Jan 2021 13:41:14 -0500 Subject: got directory symlinks to work in file selector --- arr.c | 3 ++- filesystem-posix.c | 7 ------ filesystem.h | 1 - ui.c | 63 ++++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/arr.c b/arr.c index 9d2220b..2fa9b6b 100644 --- a/arr.c +++ b/arr.c @@ -223,7 +223,8 @@ static void arrcstr_append_strn_(char **a, char const *s, size_t s_len) { size_t new_len = curr_len + s_len; arr_reserve(*a, new_len + 1); arr_set_len(*a, new_len); - memcpy(*a + curr_len, s, s_len + 1); + memcpy(*a + curr_len, s, s_len); + (*a)[curr_len + s_len] = '\0'; } static void arrcstr_shrink_(char **a, u32 new_len) { diff --git a/filesystem-posix.c b/filesystem-posix.c index cf4a04b..ebf1407 100644 --- a/filesystem-posix.c +++ b/filesystem-posix.c @@ -7,15 +7,8 @@ FsType fs_path_type(char const *path) { struct stat statbuf = {0}; - char linkbuf[8]; - if (readlink(path, linkbuf, sizeof linkbuf) != -1) { - // unfortunately there is no way of telling from stat alone whether a directory is a symbolic link >:( - return FS_LINK; - } if (stat(path, &statbuf) != 0) return FS_NON_EXISTENT; - if (S_ISLNK(statbuf.st_mode)) - return FS_LINK; if (S_ISREG(statbuf.st_mode)) return FS_FILE; if (S_ISDIR(statbuf.st_mode)) diff --git a/filesystem.h b/filesystem.h index 725b231..2e561cd 100644 --- a/filesystem.h +++ b/filesystem.h @@ -4,7 +4,6 @@ typedef enum { FS_NON_EXISTENT, FS_FILE, - FS_LINK, FS_DIRECTORY, FS_OTHER } FsType; diff --git a/ui.c b/ui.c index 852428e..75057db 100644 --- a/ui.c +++ b/ui.c @@ -1,3 +1,7 @@ +#if __unix__ +#include +#endif + // where is the ith entry in the file selector on the screen? // returns false if it's completely offscreen static bool file_selector_entry_pos(Ted const *ted, FileSelector const *fs, @@ -42,8 +46,10 @@ static int qsort_file_entry_cmp(void const *av, void const *bv) { return strcmp_case_insensitive(a->name, b->name); } +static void file_selector_cd_(FileSelector *fs, char const *path, int symlink_depth); + // cd to the directory `name`. `name` cannot include any path separators. -static void file_selector_cd1(FileSelector *fs, char const *name, size_t name_len) { +static void file_selector_cd1(FileSelector *fs, char const *name, size_t name_len, int symlink_depth) { if (name_len == 0 || (name_len == 1 && name[0] == '.')) { // no name, or . return; @@ -64,19 +70,36 @@ static void file_selector_cd1(FileSelector *fs, char const *name, size_t name_le } } } else { + #if __unix__ + if (symlink_depth < 32) { // on my system, MAXSYMLINKS is 20, so this should be plenty + char path[TED_PATH_MAX], link_to[TED_PATH_MAX]; + // join fs->cwd with name to get full path + str_printf(path, TED_PATH_MAX, "%s%s%*s", fs->cwd, + fs->cwd[strlen(fs->cwd) - 1] == PATH_SEPARATOR ? + "" : PATH_SEPARATOR_STR, + (int)name_len, name); + ssize_t bytes = readlink(path, link_to, sizeof link_to); + if (bytes != -1) { + // this is a symlink + link_to[bytes] = '\0'; + file_selector_cd_(fs, link_to, symlink_depth + 1); + return; + } + } + #else + (void)symlink_depth; + #endif // add path separator to end if not already there (which could happen in the case of /) if (fs->cwd[strlen(fs->cwd) - 1] != PATH_SEPARATOR) arrcstr_append_str(fs->cwd, PATH_SEPARATOR_STR); // add name itself arrcstr_append_strn(fs->cwd, name, name_len); } + } -// go to the directory `path`. make sure `path` only contains path separators like PATH_SEPARATOR, not any -// other members of ALL_PATH_SEPARATORS -static void file_selector_cd(FileSelector *fs, char const *path) { - size_t path_len = strlen(path); - if (path_len == 0) return; +static void file_selector_cd_(FileSelector *fs, char const *path, int symlink_depth) { + if (path[0] == '\0') return; if (path[0] == PATH_SEPARATOR #if _WIN32 @@ -84,29 +107,37 @@ static void file_selector_cd(FileSelector *fs, char const *path) { #endif ) { // absolute path (e.g. /foo, c:\foo) + // start out by replacing cwd with the start of the absolute path arr_clear(fs->cwd); - if (path_len > 1 && -#if _WIN32 - !(path_len == 3 && path[1] == ':') -#endif - path[path_len - 1] == PATH_SEPARATOR) { - // path ends with path separator - --path_len; + if (path[0] == PATH_SEPARATOR) { + arrcstr_append_str(fs->cwd, PATH_SEPARATOR_STR); + ++path; } - arrcstr_append_strn(fs->cwd, path, path_len); - return; + #if _WIN32 + else { + char s[] = {path[0], path[1], path[2], 0}; + arrcstr_append_str(fs->cwd, s); + path += 3; + } + #endif } char const *p = path; while (*p) { size_t len = strcspn(p, PATH_SEPARATOR_STR); - file_selector_cd1(fs, p, len); + file_selector_cd1(fs, p, len, symlink_depth); p += len; p += strspn(p, PATH_SEPARATOR_STR); } } +// go to the directory `path`. make sure `path` only contains path separators like PATH_SEPARATOR, not any +// other members of ALL_PATH_SEPARATORS +static void file_selector_cd(FileSelector *fs, char const *path) { + file_selector_cd_(fs, path, 0); +} + // returns the name of the selected file, or NULL // if none was selected. the returned pointer should be freed. static char *file_selector_update(Ted *ted, FileSelector *fs) { -- cgit v1.2.3