changeset 166:54c5d80a737e

dep/animia: add libutil method I changed the "linux" method to be "proc", because it isn't exactly Linux specific this commit also has some changes to the x11 stuff: instead of enumerating over only top-level windows, we iterate over ALL of them this is because many X11 apps actually use multiple windows for some reason, I still can't get it to work with VLC, but it picks up Firefox...
author paper@DavesDouble.local
date Sun, 19 Nov 2023 04:21:56 -0500
parents 8937fb7f2d66
children 31735c8592bc
files dep/animia/CMakeLists.txt dep/animia/data/players.anisthesia dep/animia/include/animia/fd/libutil.h dep/animia/include/animia/fd/linux.h dep/animia/include/animia/fd/proc.h dep/animia/src/animia.cc dep/animia/src/fd.cc dep/animia/src/fd/libutil.cc dep/animia/src/fd/linux.cc dep/animia/src/fd/proc.cc dep/animia/src/fd/xnu.cc dep/animia/src/win/x11.cc
diffstat 12 files changed, 349 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/dep/animia/CMakeLists.txt	Sat Nov 18 01:12:02 2023 -0500
+++ b/dep/animia/CMakeLists.txt	Sun Nov 19 04:21:56 2023 -0500
@@ -57,11 +57,25 @@
 	if(LINUX)
 		list(APPEND DEFINES LINUX)
 		list(APPEND SRC_FILES
-			# linux
-			src/fd/linux.cc
+			src/fd/proc.cc
 		)
 	endif() # LINUX
 
+	# FreeBSD
+	find_library(LIBUTIL_LIBRARY util)
+	if(LIBUTIL_LIBRARY)
+		get_filename_component(LIBUTIL_DIR ${LIBUTIL_LIBRARY} DIRECTORY)
+
+		include(CheckLibraryExists)
+		check_library_exists(util kinfo_getfile ${LIBUTIL_DIR} LIBUTIL_GOOD)
+
+		if(LIBUTIL_GOOD)
+			list(APPEND LIBRARIES ${LIBUTIL_LIBRARY})
+			list(APPEND DEFINES LIBUTIL)
+			list(APPEND SRC_FILES src/fd/libutil.cc)
+		endif() # LIBUTIL_GOOD
+	endif() # LIBUTIL_LIBRARY
+
 	# X11
 	find_package(X11 COMPONENTS X11)
 	if(X11_FOUND)
@@ -90,7 +104,7 @@
 					${X11_INCLUDE_DIRS}
 				)
 				list(APPEND LIBRARIES
-					${X11_LIBRARIES}
+					${X11_LINK_LIBRARIES}
 				)
 			endif() # X11_FOUND
 		endif() # PKG_CONFIG_FOUND
--- a/dep/animia/data/players.anisthesia	Sat Nov 18 01:12:02 2023 -0500
+++ b/dep/animia/data/players.anisthesia	Sun Nov 19 04:21:56 2023 -0500
@@ -355,6 +355,9 @@
 		QWidget
 		# Skinnable interface
 		SkinWindowClass
+
+		# X11
+		vlc
 	executables:
 		vlc
 	strategies:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/include/animia/fd/libutil.h	Sun Nov 19 04:21:56 2023 -0500
@@ -0,0 +1,20 @@
+#ifndef __animia__animia__fd__libutil_h
+#define __animia__animia__fd__libutil_h
+
+#include <set>
+#include <string>
+
+#include "animia/fd.h"
+#include "animia/types.h"
+
+namespace animia::internal::libutil {
+
+class LibutilFdTools final : public BaseFdTools {
+	public:
+		bool EnumerateOpenProcesses(process_proc_t process_proc) override;
+		bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) override;
+};
+
+} // namespace animia::internal::libutil
+
+#endif // __animia__animia__fd__libutil_h
--- a/dep/animia/include/animia/fd/linux.h	Sat Nov 18 01:12:02 2023 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#ifndef __animia__animia__fd__linux_h
-#define __animia__animia__fd__linux_h
-
-#include <set>
-#include <string>
-
-#include "animia/fd.h"
-#include "animia/types.h"
-
-/* Russian warship, go fuck yourself */
-#ifdef linux
-#	undef linux
-#endif
-
-namespace animia::internal::linux {
-
-class LinuxFdTools final : public BaseFdTools {
-	public:
-		bool EnumerateOpenProcesses(process_proc_t process_proc) override;
-		bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) override;
-};
-
-} // namespace animia::internal::linux
-
-#endif // __animia__animia__fd__linux_h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/include/animia/fd/proc.h	Sun Nov 19 04:21:56 2023 -0500
@@ -0,0 +1,20 @@
+#ifndef __animia__animia__fd__proc_h
+#define __animia__animia__fd__proc_h
+
+#include <set>
+#include <string>
+
+#include "animia/fd.h"
+#include "animia/types.h"
+
+namespace animia::internal::proc {
+
+class ProcFdTools final : public BaseFdTools {
+	public:
+		bool EnumerateOpenProcesses(process_proc_t process_proc) override;
+		bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) override;
+};
+
+} // namespace animia::internal::proc
+
+#endif // __animia__animia__fd__proc_h
--- a/dep/animia/src/animia.cc	Sat Nov 18 01:12:02 2023 -0500
+++ b/dep/animia/src/animia.cc	Sun Nov 19 04:21:56 2023 -0500
@@ -9,8 +9,6 @@
 #include <string>
 #include <vector>
 
-#include <iostream>
-
 namespace animia {
 
 namespace internal {
--- a/dep/animia/src/fd.cc	Sat Nov 18 01:12:02 2023 -0500
+++ b/dep/animia/src/fd.cc	Sun Nov 19 04:21:56 2023 -0500
@@ -2,20 +2,24 @@
 
 #ifdef WIN32
 #	include "animia/fd/win32.h"
-#elif defined(LINUX)
-#	include "animia/fd/linux.h"
+#elif defined(LINUX) || defined(FREEBSD)
+#	include "animia/fd/proc.h"
 #elif defined(MACOSX)
 #	include "animia/fd/xnu.h"
+#elif defined(LIBUTIL)
+#   include "animia/fd/libutil.h"
 #endif
 
 namespace animia::internal {
 
 #ifdef WIN32
 win32::Win32FdTools os_fd;
-#elif defined(LINUX)
-linux::LinuxFdTools os_fd;
+#elif defined(LINUX) || defined(FREEBSD)
+proc::ProcFdTools os_fd;
 #elif defined(MACOSX)
 xnu::XnuFdTools os_fd;
+#elif defined(LIBUTIL)
+libutil::LibutilFdTools os_fd;
 #else
 BaseFdTools os_fd;
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/src/fd/libutil.cc	Sun Nov 19 04:21:56 2023 -0500
@@ -0,0 +1,71 @@
+/* This file uses the FreeBSD-specific libprocstat
+*/
+
+#include "animia/fd/libutil.h"
+#include "animia/fd.h"
+#include "animia.h"
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <libutil.h>
+
+#include <memory>
+
+namespace animia::internal::libutil {
+
+static bool IsSystemFile(const std::string& file) {
+	return (!file.find("/usr") || !file.find("/lib") || !file.find("/dev") ||
+		    !file.find("/proc"));
+}
+
+bool LibutilFdTools::EnumerateOpenProcesses(process_proc_t process_proc) {
+	static const int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
+	size_t length = 0;
+
+	sysctl((int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, NULL, &length, NULL, 0);
+
+	std::unique_ptr<struct kinfo_proc[]> result;
+	result.reset(new struct kinfo_proc[length]);
+
+	if (!result.get())
+		return false;
+
+	/* actually get our results */
+	if (sysctl((const int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, result.get(), &length, NULL, 0) == ENOMEM) {
+		result.reset();
+		throw std::bad_alloc();
+	}
+
+	if (length < sizeof(struct kinfo_proc))
+		return false;
+
+	for (int i = 0; i < length / sizeof(result[0]); i++)
+		if (!process_proc({result[i].ki_pid, result[i].ki_comm}))
+			return false;
+
+	return true;
+}
+
+bool LibutilFdTools::EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
+	for (const auto& pid : pids) {
+		int cnt;
+		std::unique_ptr<struct kinfo_file[]> files;
+		files.reset(kinfo_getfile(pid, &cnt));
+		if (!files.get())
+			return false;
+
+		for (int i = 0; i < cnt; i++) {
+			const struct kinfo_file& current = files[i];
+			if (current.kf_vnode_type != KF_VTYPE_VREG)
+				continue;
+
+			if (!open_file_proc({pid, current.kf_path}))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+}
\ No newline at end of file
--- a/dep/animia/src/fd/linux.cc	Sat Nov 18 01:12:02 2023 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-#include "animia/fd/linux.h"
-#include "animia.h"
-#include "animia/util.h"
-
-#include <algorithm>
-#include <cstring>
-#include <filesystem>
-#include <fstream>
-#include <sstream>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <dirent.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#define PROC_LOCATION "/proc"
-
-namespace animia::internal::linux {
-
-/* this uses dirent instead of std::filesystem; it would make a bit
-   more sense to use the latter, but this is platform dependent already :) */
-static std::vector<std::string> GetAllFilesInDir(const std::string& _dir) {
-	std::vector<std::string> ret;
-
-	DIR* dir = opendir(_dir.c_str());
-	if (!dir)
-		return ret;
-
-	struct dirent* dp;
-	while ((dp = readdir(dir)) != NULL) {
-		if (!(!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")))
-			ret.push_back(_dir + "/" + dp->d_name);
-	}
-
-	closedir(dir);
-	return ret;
-}
-
-static std::string Basename(const std::string& path) {
-	return path.substr(path.find_last_of("/") + 1, path.length());
-}
-
-static bool IsRegularFile(std::string link) {
-	struct stat sb;
-	if (stat(link.c_str(), &sb) == -1)
-		return false;
-	return S_ISREG(sb.st_mode);
-}
-
-static bool AreFlagsOk(pid_t pid, int fd) {
-	const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fdinfo/" + std::to_string(fd);
-
-	std::ifstream file(path.c_str());
-	if (!file)
-		return false;
-
-	int flags = 0;
-	for (std::string line; std::getline(file, line); )
-		if (line.find("flags:", 0) == 0)
-			flags = std::stoi(line.substr(line.find_last_not_of("0123456789") + 1));
-
-	if (flags & O_WRONLY || flags & O_RDWR)
-		return false;
-	return true;
-}
-
-static std::string GetFilenameFromFd(std::string link) {
-	/* gets around stupid linux limitation where /proc doesn't
-	   give actual filesize readings */
-	size_t exe_size = 1024;
-	ssize_t exe_used;
-	std::string ret;
-
-	while (1) {
-		ret = std::string(exe_size, '\0');
-		exe_used = readlink(link.c_str(), &ret.front(), ret.length());
-		if (exe_used == (ssize_t)-1)
-			return NULL;
-
-		if (exe_used < (ssize_t)1) {
-			errno = ENOENT;
-			return NULL;
-		}
-
-		if (exe_used < (ssize_t)(exe_size - 1))
-			break;
-
-		exe_size += 1024;
-	}
-
-	return ret.c_str();
-}
-
-static std::string GetProcessName(pid_t pid) {
-	const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/comm";
-
-	std::string result;
-
-	if (!util::ReadFile(path, result))
-		return "";
-
-	result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
-
-	return result;
-}
-
-bool LinuxFdTools::EnumerateOpenProcesses(process_proc_t process_proc) {
-	bool success = false;
-	for (const auto& dir : GetAllFilesInDir(PROC_LOCATION)) {
-		pid_t pid;
-		try {
-			pid = std::stoul(Basename(dir));
-			success = true;
-		} catch (std::invalid_argument) {
-			continue;
-		}
-		if (!process_proc({pid, GetProcessName(pid)}))
-			return false;
-	}
-	return success;
-}
-
-bool LinuxFdTools::EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
-	if (!open_file_proc)
-		return false;
-
-	for (const auto& pid : pids) {
-		const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fd";
-
-		for (const auto& dir : GetAllFilesInDir(path)) {
-			if (!AreFlagsOk(pid, std::stoi(Basename(dir))))
-				continue;
-
-			std::string name = GetFilenameFromFd(dir);
-
-			if (!IsRegularFile(name))
-				continue;
-
-			if (!open_file_proc({pid, name}))
-				return false;
-		}
-	}
-	return true;
-}
-
-} // namespace animia::internal::linux
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/src/fd/proc.cc	Sun Nov 19 04:21:56 2023 -0500
@@ -0,0 +1,151 @@
+#include "animia/fd/proc.h"
+#include "animia.h"
+#include "animia/util.h"
+
+#include <algorithm>
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef FREEBSD
+#	include <sys/types.h>
+#	include <sys/user.h>
+#	include <libutil.h>
+#endif
+
+#define PROC_LOCATION "/proc"
+
+namespace animia::internal::proc {
+
+/* this uses dirent instead of std::filesystem; it would make a bit
+   more sense to use the latter, but this is platform dependent already :) */
+static std::vector<std::string> GetAllFilesInDir(const std::string& _dir) {
+	std::vector<std::string> ret;
+
+	DIR* dir = opendir(_dir.c_str());
+	if (!dir)
+		return ret;
+
+	struct dirent* dp;
+	while ((dp = readdir(dir)) != NULL) {
+		if (!(!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")))
+			ret.push_back(_dir + "/" + dp->d_name);
+	}
+
+	closedir(dir);
+	return ret;
+}
+
+static std::string Basename(const std::string& path) {
+	return path.substr(path.find_last_of("/") + 1, path.length());
+}
+
+static bool IsRegularFile(std::string link) {
+	struct stat sb;
+	if (stat(link.c_str(), &sb) == -1)
+		return false;
+	return S_ISREG(sb.st_mode);
+}
+
+static bool AreFlagsOk(pid_t pid, int fd) {
+	const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fdinfo/" + std::to_string(fd);
+
+	std::ifstream file(path.c_str());
+	if (!file)
+		return false;
+
+	int flags = 0;
+	for (std::string line; std::getline(file, line); )
+		if (line.find("flags:", 0) == 0)
+			flags = std::stoi(line.substr(line.find_last_not_of("0123456789") + 1));
+
+	if (flags & O_WRONLY || flags & O_RDWR)
+		return false;
+	return true;
+}
+
+static bool GetFilenameFromFd(std::string link, std::string& out) {
+	/* gets around stupid linux limitation where /proc doesn't
+	   give actual filesize readings */
+	constexpr size_t OUT_MAX = (1 << 15); // 32KiB
+	out.resize(1024);
+
+	for (ssize_t exe_used = 0;
+	     out.length() < OUT_MAX && exe_used >= (ssize_t)(out.length() - 1);
+		 out.resize(out.length() * 2)) {
+		exe_used = readlink(link.c_str(), &out.front(), out.length());
+		if (exe_used == (ssize_t)-1 || exe_used < (ssize_t)1)
+			return false;
+	}
+}
+
+static std::string GetProcessName(pid_t pid) {
+	std::string result;
+
+#ifdef FREEBSD
+	struct kinfo_proc* proc = kinfo_getproc(pid);
+	if (!proc)
+		return "";
+	result = proc->ki_comm;
+	free(proc);
+#elif defined(LINUX)
+	const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/comm";
+
+	if (!util::ReadFile(path, result))
+		return "";
+
+	result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
+#endif
+
+	return result;
+}
+
+bool ProcFdTools::EnumerateOpenProcesses(process_proc_t process_proc) {
+	bool success = false;
+	for (const auto& dir : GetAllFilesInDir(PROC_LOCATION)) {
+		pid_t pid;
+		try {
+			pid = std::stoul(Basename(dir));
+			success = true;
+		} catch (std::invalid_argument) {
+			continue;
+		}
+		if (!process_proc({pid, GetProcessName(pid)}))
+			return false;
+	}
+	return success;
+}
+
+bool ProcFdTools::EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
+	if (!open_file_proc)
+		return false;
+
+	for (const auto& pid : pids) {
+		const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fd";
+
+		for (const auto& dir : GetAllFilesInDir(path)) {
+			if (!AreFlagsOk(pid, std::stoi(Basename(dir))))
+				continue;
+
+			std::string name = GetFilenameFromFd(dir);
+
+			if (!IsRegularFile(name))
+				continue;
+
+			if (!open_file_proc({pid, name}))
+				return false;
+		}
+	}
+	return true;
+}
+
+} // namespace animia::internal::linux
--- a/dep/animia/src/fd/xnu.cc	Sat Nov 18 01:12:02 2023 -0500
+++ b/dep/animia/src/fd/xnu.cc	Sun Nov 19 04:21:56 2023 -0500
@@ -20,8 +20,6 @@
 #include <sys/types.h>
 #include <sys/user.h>
 
-#include <iostream>
-
 namespace animia::internal::xnu {
 
 bool XnuFdTools::EnumerateOpenProcesses(process_proc_t process_proc) {
--- a/dep/animia/src/win/x11.cc	Sat Nov 18 01:12:02 2023 -0500
+++ b/dep/animia/src/win/x11.cc	Sun Nov 19 04:21:56 2023 -0500
@@ -8,7 +8,6 @@
 
 #include <cstdint>
 #include <string>
-#include <memory>
 #include <set>
 
 /* The code for this is very fugly because X11 uses lots of generic type names
@@ -23,12 +22,16 @@
 	unsigned long leftover_bytes, num_of_items;
 	::Atom type;
 	unsigned char* data;
-	if (!::XGetWindowProperty(display, window, atom, 0L, (~0L), False, reqtype,
-	                       &type, &format, &num_of_items, &leftover_bytes, &data))
+
+	int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, reqtype,
+	                                  &type, &format, &num_of_items, &leftover_bytes, &data);
+	if (status != Success || !(reqtype == AnyPropertyType || type == reqtype) || !num_of_items)
 		return false;
 
 	result = std::string((char*)data, num_of_items);
 
+	::XFree(data);
+
 	return true;
 }
 
@@ -39,19 +42,26 @@
 	::Atom atom = ::XInternAtom(display, "_NET_WM_PID", False), type;
 	unsigned char* data;
 
-	if (!::XGetWindowProperty(display, window, atom, 0L, (~0L), False, XA_CARDINAL,
-	                       &type, &format, &num_of_items, &leftover_bytes, &data))
+	int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, XA_CARDINAL,
+	                                  &type, &format, &num_of_items, &leftover_bytes, &data);
+	if (status != Success || type != XA_CARDINAL || num_of_items < 1)
 		return false;
 
 	result = static_cast<pid_t>(*(uint32_t*)data);
 
+	::XFree(data);
+
 	return true;
 }
 
 static bool FetchName(::Display* display, ::Window window, std::string& result) {
 	/* TODO: Check if XInternAtom created None or not... */
 	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "_NET_WM_NAME", False),
-		                           result, ::XInternAtom(display, "UTF8_STRING", False)))
+		                          result, ::XInternAtom(display, "UTF8_STRING", False)))
+		return true;
+
+	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "WM_NAME", False),
+		                           result, XA_STRING))
 		return true;
 
 	/* Fallback to XGetWMName() */
@@ -69,7 +79,7 @@
 		int count;
 
 		int status = ::XmbTextPropertyToTextList(display, &text, &list, &count);
-		if (status < Success || !count || !*list)
+		if (status != Success || !count || !*list)
 			return false;
 	}
 
@@ -82,28 +92,55 @@
 	return true;
 }
 
+static bool WalkWindows(::Display* display, std::set<::Window>& children, const std::set<::Window>& windows) {
+	if (windows.empty())
+		return false;
+
+	for (const ::Window& window : windows) {
+		unsigned int num_children = 0;
+		::Window* children_arr = nullptr;
+
+		::Window root_return;
+		::Window parent_return;
+
+		int status = ::XQueryTree(display, window, &root_return, &parent_return, &children_arr, &num_children);
+		if (status < Success)
+			continue;
+
+		if (num_children < 1) {
+			::XFree(children_arr);
+			continue;
+		}
+
+		for (int i = 0; i < num_children; i++)
+			if (!children.count(children_arr[i]))
+				children.insert(children_arr[i]);
+
+		::XFree(children_arr);
+
+		std::set<::Window> children_children;
+
+		if (!WalkWindows(display, children_children, children))
+			children.insert(children_children.begin(), children_children.end());
+	}
+
+	return true;
+}
+
 bool X11WinTools::EnumerateWindows(window_proc_t window_proc) {
 	if (!window_proc)
 		return false;
 
 	::Display* display = ::XOpenDisplay(nullptr);
+	if (!display)
+		return false;
+
 	::Window root = DefaultRootWindow(display);
 
-	unsigned int num_windows = 0;
-	::Window* windows = nullptr;
-
-	{
-		::Window root_return;
-		::Window parent_return;
+	std::set<::Window> windows;
+	WalkWindows(display, windows, {root});
 
-		int status = ::XQueryTree(display, root, &root_return, &parent_return, &windows, &num_windows);
-		if (status < Success)
-			return false;
-	}
-
-	for (long k = 0; k < num_windows; k++) {
-		const ::Window window = windows[k];
-
+	for (const auto& window : windows) {
 		Window win;
 		win.id = window;
 		{
@@ -113,7 +150,7 @@
 				::XFree(hint);
 			}
 		}
-		FetchName(display, windows[k], win.text);
+		FetchName(display, window, win.text);
 
 		Process proc;
 		GetWindowPID(display, window, proc.pid);
@@ -122,8 +159,6 @@
 			return false;
 	}
 
-	::XFree(windows);
-
 	return true;
 }