view dep/animone/src/fd/proc.cc @ 327:b5d6c27c308f

anime: refactor Anime::SeriesSeason to Season class ToLocalString has also been altered to take in both season and year because lots of locales actually treat formatting seasons differently! most notably is Russian which adds a suffix at the end to notate seasons(??)
author Paper <paper@paper.us.eu.org>
date Thu, 13 Jun 2024 01:49:18 -0400
parents a4257370de16
children a7d4e5107531
line wrap: on
line source

/*
 * fd/win32.cc: support for linux's /proc filesystem
 *
 * plan 9 caused this monstrocity...
 */
#include "animone/fd/proc.h"
#include "animone.h"
#include "animone/util.h"

#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

static constexpr std::string_view PROC_LOCATION = "/proc";

namespace animone::internal::proc {

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::filesystem::path path =
	    std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "fdinfo" / std::to_string(fd);

	std::ifstream file(path);
	if (!file)
		return false;

	int flags = 0;
	for (std::string line; std::getline(file, line);)
		if (line.find("flags:", 0) == 0)
			flags = util::StringToInt(line.substr(line.find_last_not_of("0123456789") + 1));

	/* check if the file was opened in a write mode */
	int accflags = flags & O_ACCMODE;
	if (accflags == O_WRONLY || accflags == O_RDWR)
		return false;

	return true;
}

static bool GetFilenameFromFd(std::string link, std::string& out) {
	/* /proc is a "virtual filesystem", so we have to guess the path size. yippee! */
	constexpr size_t OUT_MAX = (1ul << 15); // 32KiB
	out.resize(32);

	ssize_t exe_used = 0;
	do {
		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; // we got a bad result. SAD!
	} while (out.length() < OUT_MAX && exe_used >= static_cast<ssize_t>(out.length()));

	out.resize(out.find('\0'));

	return true;
}

static bool IsSystemFile(const std::string& path) {
	static constexpr std::array<std::string_view, 9> invalid_paths = {"/boot", "/dev", "/bin", "/usr", "/opt",
	                                                                  "/proc", "/var", "/etc", "/dev"};

	for (const auto& invalid_path : invalid_paths)
		if (!path.rfind(invalid_path, 0))
			return true;

	return false;
}

bool GetProcessName(pid_t pid, std::string& result) {
	const std::filesystem::path path = std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "comm";

	if (!util::ReadFile(path, result))
		return false;

	result.erase(std::remove(result.begin(), result.end(), '\n'), result.cend());
	return true;
}

bool EnumerateOpenProcesses(process_proc_t process_proc) {
	bool success = false;

	for (const auto& dir : std::filesystem::directory_iterator{PROC_LOCATION}) {
		Process proc;

		try {
			proc.pid = util::StringToInt(dir.path().stem());
			success = true;
		} catch (std::invalid_argument const& ex) {
			continue;
		}

		if (!GetProcessName(proc.pid, proc.name))
			continue;

		if (!process_proc(proc))
			return false;
	}

	return success;
}

bool 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::filesystem::path path = std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "fd";

		for (const auto& dir : std::filesystem::directory_iterator{path}) {
			if (!AreFlagsOk(pid, util::StringToInt(dir.path().stem())))
				continue;

			std::string name;
			if (!GetFilenameFromFd(dir.path(), name))
				continue;

			if (!IsRegularFile(name))
				continue;

			if (IsSystemFile(name))
				continue;

			if (!open_file_proc({pid, name}))
				return false;
		}
	}
	return true;
}

} // namespace animia::internal::proc