Embrace paths with symbolic links
This commit is contained in:
		
							
								
								
									
										125
									
								
								sdn.cpp
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								sdn.cpp
									
									
									
									
									
								
							@@ -98,6 +98,7 @@ fun prefix_length (const wstring &in, const wstring &of) -> int {
 | 
				
			|||||||
	return score;
 | 
						return score;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: this omits empty elements, check usages
 | 
				
			||||||
fun split (const string &s, const string &sep, vector<string> &out) {
 | 
					fun split (const string &s, const string &sep, vector<string> &out) {
 | 
				
			||||||
	size_t mark = 0, p = s.find (sep);
 | 
						size_t mark = 0, p = s.find (sep);
 | 
				
			||||||
	for (; p != string::npos; p = s.find (sep, (mark = p + sep.length ())))
 | 
						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;
 | 
						return a.filename < b.filename;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun reload () {
 | 
					fun reload (const string &old_cwd) {
 | 
				
			||||||
	g.unames.clear();
 | 
						g.unames.clear();
 | 
				
			||||||
	while (auto *ent = getpwent ())
 | 
						while (auto *ent = getpwent ())
 | 
				
			||||||
		g.unames.emplace (ent->pw_uid, ent->pw_name);
 | 
							g.unames.emplace (ent->pw_uid, ent->pw_name);
 | 
				
			||||||
@@ -757,10 +758,7 @@ fun reload () {
 | 
				
			|||||||
	if (!g.entries.empty ())
 | 
						if (!g.entries.empty ())
 | 
				
			||||||
		anchor = g.entries[g.cursor].filename;
 | 
							anchor = g.entries[g.cursor].filename;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto old_cwd = g.cwd;
 | 
					 | 
				
			||||||
	auto now = time (NULL); g.now = *localtime (&now);
 | 
						auto now = time (NULL); g.now = *localtime (&now);
 | 
				
			||||||
	char buf[4096]; g.cwd = getcwd (buf, sizeof buf);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	auto dir = opendir (".");
 | 
						auto dir = opendir (".");
 | 
				
			||||||
	g.entries.clear ();
 | 
						g.entries.clear ();
 | 
				
			||||||
	while (auto f = readdir (dir)) {
 | 
						while (auto f = readdir (dir)) {
 | 
				
			||||||
@@ -792,6 +790,7 @@ fun reload () {
 | 
				
			|||||||
		inotify_rm_watch (g.inotify_fd, g.inotify_wd);
 | 
							inotify_rm_watch (g.inotify_fd, g.inotify_wd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// We don't show atime, so access and open are merely spam
 | 
						// 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,
 | 
						g.inotify_wd = inotify_add_watch (g.inotify_fd, buf,
 | 
				
			||||||
		(IN_ALL_EVENTS | IN_ONLYDIR | IN_EXCL_UNLINK) & ~(IN_ACCESS | IN_OPEN));
 | 
							(IN_ALL_EVENTS | IN_ONLYDIR | IN_EXCL_UNLINK) & ~(IN_ACCESS | IN_OPEN));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -954,15 +953,88 @@ fun pop_levels () {
 | 
				
			|||||||
		search (to_wide (anchor));
 | 
							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) {
 | 
					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));
 | 
							show_message (strerror (errno));
 | 
				
			||||||
		beep ();
 | 
							beep ();
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	level last {g.offset, g.cursor, g.cwd, g.entries[g.cursor].filename};
 | 
						auto old_cwd = g.cwd;
 | 
				
			||||||
	reload ();
 | 
						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)) {
 | 
						if (is_ancestor_dir (last.path, g.cwd)) {
 | 
				
			||||||
		g.levels.push_back (last);
 | 
							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) {
 | 
					fun choose (const entry &entry) {
 | 
				
			||||||
	// Dive into directories and accessible symlinks to them
 | 
						// Dive into directories and accessible symlinks to them
 | 
				
			||||||
	if (!S_ISDIR (entry.info.st_mode)
 | 
						if (!S_ISDIR (entry.info.st_mode)
 | 
				
			||||||
@@ -1048,12 +1143,12 @@ fun handle (wint_t c) -> bool {
 | 
				
			|||||||
	case ACTION_SORT_LEFT:
 | 
						case ACTION_SORT_LEFT:
 | 
				
			||||||
		g.sort_column = (g.sort_column + entry::COLUMNS - 1) % entry::COLUMNS;
 | 
							g.sort_column = (g.sort_column + entry::COLUMNS - 1) % entry::COLUMNS;
 | 
				
			||||||
		g.sort_flash_ttl = 2;
 | 
							g.sort_flash_ttl = 2;
 | 
				
			||||||
		reload ();
 | 
							reload (g.cwd);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case ACTION_SORT_RIGHT:
 | 
						case ACTION_SORT_RIGHT:
 | 
				
			||||||
		g.sort_column = (g.sort_column + entry::COLUMNS + 1) % entry::COLUMNS;
 | 
							g.sort_column = (g.sort_column + entry::COLUMNS + 1) % entry::COLUMNS;
 | 
				
			||||||
		g.sort_flash_ttl = 2;
 | 
							g.sort_flash_ttl = 2;
 | 
				
			||||||
		reload ();
 | 
							reload (g.cwd);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case ACTION_UP:
 | 
						case ACTION_UP:
 | 
				
			||||||
@@ -1115,7 +1210,7 @@ fun handle (wint_t c) -> bool {
 | 
				
			|||||||
		g.editor_on_confirm = [] {
 | 
							g.editor_on_confirm = [] {
 | 
				
			||||||
			auto mb = to_mb (g.editor_line);
 | 
								auto mb = to_mb (g.editor_line);
 | 
				
			||||||
			rename (g.entries[g.cursor].filename.c_str (), mb.c_str ());
 | 
								rename (g.entries[g.cursor].filename.c_str (), mb.c_str ());
 | 
				
			||||||
			reload ();
 | 
								reload (g.cwd);
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1124,17 +1219,17 @@ fun handle (wint_t c) -> bool {
 | 
				
			|||||||
		break;
 | 
							break;
 | 
				
			||||||
	case ACTION_REVERSE_SORT:
 | 
						case ACTION_REVERSE_SORT:
 | 
				
			||||||
		g.reverse_sort = !g.reverse_sort;
 | 
							g.reverse_sort = !g.reverse_sort;
 | 
				
			||||||
		reload ();
 | 
							reload (g.cwd);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case ACTION_SHOW_HIDDEN:
 | 
						case ACTION_SHOW_HIDDEN:
 | 
				
			||||||
		g.show_hidden = !g.show_hidden;
 | 
							g.show_hidden = !g.show_hidden;
 | 
				
			||||||
		reload ();
 | 
							reload (g.cwd);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case ACTION_REDRAW:
 | 
						case ACTION_REDRAW:
 | 
				
			||||||
		clear ();
 | 
							clear ();
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case ACTION_RELOAD:
 | 
						case ACTION_RELOAD:
 | 
				
			||||||
		reload ();
 | 
							reload (g.cwd);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		if (c != KEY (RESIZE) && c != WEOF)
 | 
							if (c != KEY (RESIZE) && c != WEOF)
 | 
				
			||||||
@@ -1448,8 +1543,8 @@ int main (int argc, char *argv[]) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	load_colors ();
 | 
						load_colors ();
 | 
				
			||||||
	reload ();
 | 
						g.start_dir = g.cwd = initial_cwd ();
 | 
				
			||||||
	g.start_dir = g.cwd;
 | 
						reload (g.cwd);
 | 
				
			||||||
	pop_levels ();
 | 
						pop_levels ();
 | 
				
			||||||
	update ();
 | 
						update ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user