Last active
December 11, 2024 03:17
-
-
Save karuboniru/70c07463682ca14f3098316d51c16976 to your computer and use it in GitHub Desktop.
utmp_reader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "mmapio.h" | |
#include <algorithm> | |
#include <arpa/inet.h> | |
#include <chrono> | |
#include <print> | |
#include <ranges> | |
#include <unordered_map> | |
#include <utmp.h> | |
#include <vector> | |
std::string address_to_string(const int32_t *addr) { | |
std::array<char, INET6_ADDRSTRLEN> buf; | |
if (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] == 0) { | |
return "unknown"; | |
} | |
auto address_type = (addr[1] || addr[2] || addr[3]) ? AF_INET6 : AF_INET; | |
if (inet_ntop(address_type, addr, buf.data(), buf.size()) == nullptr) { | |
return "unknown"; | |
} | |
return buf.data(); | |
} | |
int main(int argc, char *argv[]) { | |
if (argc != 2) { | |
std::cerr << "Usage: " << argv[0] << " <filename>\n"; | |
return 1; | |
} | |
try { | |
auto local_time_zone = std::chrono::current_zone(); | |
mmapio<utmp[]> m(argv[1], false); | |
using record_t = | |
std::pair<size_t, std::chrono::time_point<std::chrono::system_clock>>; | |
std::unordered_map<std::string, record_t> address_count; | |
for (const auto &[address, time] : | |
m | std::views::filter([](const auto &u) { | |
return u.ut_type == LOGIN_PROCESS || u.ut_type == USER_PROCESS; | |
}) | std::views::transform([&](const auto &u) { | |
return std::make_tuple( | |
address_to_string(u.ut_addr_v6), | |
std::chrono::system_clock::from_time_t(u.ut_tv.tv_sec)); | |
})) { | |
address_count[address].first++; | |
address_count[address].second = | |
std::max(address_count[address].second, time); | |
} | |
auto address_count_vector = | |
address_count | std::views::filter([](auto &entry) { | |
return entry.second.first > 50; | |
}) | | |
std::ranges::to<std::vector<std::pair<std::string, record_t>>>(); | |
auto count_to_list = std::min(address_count_vector.size(), 10ul); | |
auto max_address_len = std::ranges::max( | |
address_count_vector | | |
std::views::transform([](const auto &a) { return a.first.length(); })); | |
std::ranges::partial_sort(address_count_vector, | |
address_count_vector.begin() + count_to_list, | |
[](const auto &a, const auto &b) -> bool { | |
return a.second.first > b.second.first; | |
}); | |
std::println("top 10 addresses by count"); | |
for (const auto &[address, count_time] : | |
address_count_vector | std::views::take(count_to_list)) { | |
auto &[count, time] = count_time; | |
auto local_time = std::chrono::zoned_time{local_time_zone, time}; | |
std::println("{0:<{3}} {1:>5} {2:%F} {2:%R}", address, count, local_time, | |
max_address_len); | |
} | |
std::println("top 10 addresses by time (at least 50 logins)"); | |
std::ranges::partial_sort(address_count_vector, | |
address_count_vector.begin() + count_to_list, | |
[](const auto &a, const auto &b) -> bool { | |
return a.second.second > b.second.second; | |
}); | |
for (const auto &[address, count_time] : | |
address_count_vector | std::views::take(count_to_list)) { | |
auto &[count, time] = count_time; | |
auto local_time = std::chrono::zoned_time{local_time_zone, time}; | |
std::println("{0:<{3}} {1:>5} {2:%F} {2:%R}", address, count, local_time, | |
max_address_len); | |
} | |
} catch (const std::exception &e) { | |
std::cerr << e.what() << '\n'; | |
} | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <cstddef> | |
#include <cstring> | |
#include <iostream> | |
#include <print> | |
#include <stdexcept> | |
#include <string> | |
#include <type_traits> | |
#include <fcntl.h> | |
#include <sys/mman.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
template <typename T_> class mmapio { | |
constexpr static auto is_array = std::is_unbounded_array_v<T_>; | |
using T = std::conditional_t<is_array, std::remove_extent_t<T_>, T_>; | |
private: | |
T *data{static_cast<T *>(MAP_FAILED)}; | |
size_t m_size{0}; | |
public: | |
mmapio(const std::string &filename, const bool allow_write, | |
const size_t count_or_size = 0) | |
: mmapio(filename.c_str(), allow_write, count_or_size) {} | |
mmapio(const char *filename, const bool allow_write, | |
const size_t count_or_size = 0) { | |
int fd{-1}; | |
try { | |
auto size = count_or_size * sizeof(T); | |
// always set count to 1 if allow_write and T is not an array | |
if (allow_write && !is_array && count_or_size == 0) { | |
size = sizeof(T); | |
} else if (allow_write && !is_array) { | |
size = count_or_size; | |
} | |
if (allow_write) | |
fd = ::open(filename, O_RDWR | O_CREAT, 0644); | |
else | |
fd = ::open(filename, O_RDONLY); | |
if (fd == -1) { | |
throw std::runtime_error( | |
std::format("open failed, {}", std::strerror(errno))); | |
} | |
struct stat64 file_stat{}; | |
if (::fstat64(fd, &file_stat) == -1) { | |
throw std::runtime_error( | |
std::format("fstat failed, {}", std::strerror(errno))); | |
} | |
if (size) { | |
if (file_stat.st_size < size) { | |
if (allow_write) { | |
if (::ftruncate(fd, size) == -1) { | |
std::println(std::cerr, "ftruncate failed, {}", | |
std::strerror(errno)); | |
} | |
} else { | |
throw std::runtime_error("file size is smaller than requested, " | |
"while open in read-only mode"); | |
} | |
} | |
} else { | |
size = file_stat.st_size; | |
} | |
if (size == 0) { | |
throw std::runtime_error("size is 0"); | |
} | |
m_size = size; | |
auto page_mode = allow_write ? (PROT_READ | PROT_WRITE) : PROT_READ; | |
auto mmap_mode = allow_write ? MAP_SHARED : MAP_PRIVATE; | |
data = | |
static_cast<T *>(::mmap(nullptr, size, page_mode, mmap_mode, fd, 0)); | |
if (data == MAP_FAILED) { | |
throw std::runtime_error( | |
std::format("mmap failed, errno = {}", std::strerror(errno))); | |
} | |
close(fd); | |
fd = -1; | |
} catch (std::runtime_error &e) { | |
if (data != MAP_FAILED) { | |
::munmap(data, m_size); | |
} | |
if (fd != -1) { | |
close(fd); | |
} | |
throw e; | |
} | |
} | |
mmapio(const mmapio &) = delete; | |
mmapio(mmapio &&other) noexcept : data(other.data), m_size(other.m_size) { | |
other.data = static_cast<T *>(MAP_FAILED); | |
other.m_size = 0; | |
} | |
mmapio &operator=(const mmapio &) = delete; | |
mmapio &operator=(mmapio &&other) noexcept { | |
if (this != &other) { | |
if (data != MAP_FAILED) { | |
::munmap(data, m_size); | |
} | |
data = other.data; | |
m_size = other.m_size; | |
other.data = static_cast<T *>(MAP_FAILED); | |
other.m_size = 0; | |
} | |
return *this; | |
} | |
~mmapio() { | |
if (::munmap(data, m_size) == -1) { | |
std::println(std::cerr, "munmap failed, {}", std::strerror(errno)); | |
} | |
} | |
auto &operator[](size_t index) const | |
requires(is_array) | |
{ | |
return data[index]; | |
} | |
[[nodiscard]] size_t size() const | |
requires(is_array) | |
{ | |
return m_size / sizeof(T); | |
} | |
T *begin() const | |
requires(is_array) | |
{ | |
return data; | |
} | |
T *end() const | |
requires(is_array) | |
{ | |
return data + size(); | |
} | |
auto &operator*() const | |
requires(!is_array) | |
{ | |
return *data; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment