From 71fbaca9e5ab5ec2792b5de095354dfd9eb7f518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 8 Dec 2018 01:41:41 +0100 Subject: [PATCH] Embrace paths with symbolic links --- sdn.cpp | 125 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 110 insertions(+), 15 deletions(-) diff --git a/sdn.cpp b/sdn.cpp index 14a337a..38ae63f 100644 --- a/sdn.cpp +++ b/sdn.cpp @@ -98,6 +98,7 @@ fun prefix_length (const wstring &in, const wstring &of) -> int { return score; } +// TODO: this omits empty elements, check usages fun split (const string &s, const string &sep, vector &out) { size_t mark = 0, p = s.find (sep); for (; p != string::npos; p = s.find (sep, (mark = p + sep.length ()))) @@ -742,7 +743,7 @@ fun operator< (const entry &e1, const entry &e2) -> bool { return a.filename < b.filename; } -fun reload () { +fun reload (const string &old_cwd) { g.unames.clear(); while (auto *ent = getpwent ()) g.unames.emplace (ent->pw_uid, ent->pw_name); @@ -757,10 +758,7 @@ fun reload () { if (!g.entries.empty ()) anchor = g.entries[g.cursor].filename; - auto old_cwd = g.cwd; auto now = time (NULL); g.now = *localtime (&now); - char buf[4096]; g.cwd = getcwd (buf, sizeof buf); - auto dir = opendir ("."); g.entries.clear (); while (auto f = readdir (dir)) { @@ -792,6 +790,7 @@ fun reload () { inotify_rm_watch (g.inotify_fd, g.inotify_wd); // We don't show atime, so access and open are merely spam + char buf[PATH_MAX]; g.inotify_wd = inotify_add_watch (g.inotify_fd, buf, (IN_ALL_EVENTS | IN_ONLYDIR | IN_EXCL_UNLINK) & ~(IN_ACCESS | IN_OPEN)); } @@ -954,15 +953,88 @@ fun pop_levels () { search (to_wide (anchor)); } +fun explode_path (const string &path, vector &out) { + size_t mark = 0, p = path.find ("/"); + for (; p != string::npos; p = path.find ("/", (mark = p + 1))) + out.push_back (path.substr (mark, p - mark)); + if (mark < path.length ()) + out.push_back (path.substr (mark)); +} + +fun serialize_path (const vector &components) -> string { + string result; + for (const auto &i : components) + result.append (i).append ("/"); + + auto n = result.find_last_not_of ('/'); + if (n != result.npos) + return result.erase (n + 1); + return result; +} + +fun absolutize (const string &abs_base, const string &path) -> string { + if (path[0] == '/') + return path; + if (!abs_base.empty () && abs_base.back () == '/') + return abs_base + path; + return abs_base + "/" + path; +} + +fun relativize (string current, const string &path) -> string { + if (current == path) + return "."; + if (current.back () != '/') + current += '/'; + if (!strncmp (current.c_str (), path.c_str (), current.length ())) + return path.substr (current.length ()); + return path; +} + +// Roughly follows the POSIX description of `cd -L` because of symlinks. +// HOME and CDPATH handling is ommitted. fun change_dir (const string &path) { - if (chdir (path.c_str ())) { + if (g.cwd[0] != '/') { + show_message ("cannot figure out absolute path"); + beep (); + return; + } + + vector in, out; + explode_path (absolutize (g.cwd, path), in); + + // Paths with exactly two leading slashes may get special treatment + int startempty = 1; + if (in.size () >= 2 && in[1] == "" && (in.size () < 3 || in[2] != "")) + startempty = 2; + + struct stat s{}; + for (size_t i = 0; i < in.size (); i++) + if (in[i] == "..") { + auto parent = relativize (g.cwd, serialize_path (out)); + if (errno = 0, !stat (parent.c_str (), &s) && !S_ISDIR (s.st_mode)) + errno = ENOTDIR; + if (errno) { + show_message (parent + ": " + strerror (errno)); + beep (); + return; + } + if (!out.back().empty ()) + out.pop_back (); + } else if (in[i] != "." && (!in[i].empty () || i < startempty)) { + out.push_back (in[i]); + } + + auto full_path = serialize_path (out); + if (chdir (relativize (g.cwd, full_path).c_str ())) { show_message (strerror (errno)); beep (); return; } - level last {g.offset, g.cursor, g.cwd, g.entries[g.cursor].filename}; - reload (); + auto old_cwd = g.cwd; + level last {g.offset, g.cursor, old_cwd, g.entries[g.cursor].filename}; + g.cwd = full_path; + reload (old_cwd); if (is_ancestor_dir (last.path, g.cwd)) { g.levels.push_back (last); @@ -972,6 +1044,29 @@ fun change_dir (const string &path) { } } +// Roughly follows the POSIX description of the PWD environment variable +fun initial_cwd () -> string { + char cwd[4096] = ""; getcwd (cwd, sizeof cwd); + const char *pwd = getenv ("PWD"); + if (!pwd || pwd[0] != '/' || strlen (pwd) >= PATH_MAX) + return cwd; + + // Extra slashes shouldn't break anything for us + vector components; + explode_path (pwd, components); + for (const auto &i : components) { + if (i == "." || i == "..") + return cwd; + } + + // Check if it "is an absolute pathname of the current working directory." + // This particular method won't match on bind mounts, which is desired. + char *real = realpath (pwd, nullptr); + bool ok = real && !strcmp (cwd, real); + free (real); + return ok ? pwd : cwd; +} + fun choose (const entry &entry) { // Dive into directories and accessible symlinks to them if (!S_ISDIR (entry.info.st_mode) @@ -1048,12 +1143,12 @@ fun handle (wint_t c) -> bool { case ACTION_SORT_LEFT: g.sort_column = (g.sort_column + entry::COLUMNS - 1) % entry::COLUMNS; g.sort_flash_ttl = 2; - reload (); + reload (g.cwd); break; case ACTION_SORT_RIGHT: g.sort_column = (g.sort_column + entry::COLUMNS + 1) % entry::COLUMNS; g.sort_flash_ttl = 2; - reload (); + reload (g.cwd); break; case ACTION_UP: @@ -1115,7 +1210,7 @@ fun handle (wint_t c) -> bool { g.editor_on_confirm = [] { auto mb = to_mb (g.editor_line); rename (g.entries[g.cursor].filename.c_str (), mb.c_str ()); - reload (); + reload (g.cwd); }; break; @@ -1124,17 +1219,17 @@ fun handle (wint_t c) -> bool { break; case ACTION_REVERSE_SORT: g.reverse_sort = !g.reverse_sort; - reload (); + reload (g.cwd); break; case ACTION_SHOW_HIDDEN: g.show_hidden = !g.show_hidden; - reload (); + reload (g.cwd); break; case ACTION_REDRAW: clear (); break; case ACTION_RELOAD: - reload (); + reload (g.cwd); break; default: if (c != KEY (RESIZE) && c != WEOF) @@ -1448,8 +1543,8 @@ int main (int argc, char *argv[]) { } load_colors (); - reload (); - g.start_dir = g.cwd; + g.start_dir = g.cwd = initial_cwd (); + reload (g.cwd); pop_levels (); update ();