Embrace paths with symbolic links

This commit is contained in:
Přemysl Eric Janouch 2018-12-08 01:41:41 +01:00
parent 4ce6454ebb
commit 71fbaca9e5
Signed by: p
GPG Key ID: A0420B94F92B9493

125
sdn.cpp
View File

@ -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<string> &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<string> &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<string> &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<string> 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<string> 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 ();