Skip to content

Instantly share code, notes, and snippets.

@karuboniru
Last active December 11, 2024 03:17
Show Gist options
  • Save karuboniru/70c07463682ca14f3098316d51c16976 to your computer and use it in GitHub Desktop.
Save karuboniru/70c07463682ca14f3098316d51c16976 to your computer and use it in GitHub Desktop.
utmp_reader
#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;
}
#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