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 ();