Regex free process reader
This commit is contained in:
parent
633713180d
commit
72ba68744e
10 changed files with 244 additions and 22 deletions
15
build.zig
15
build.zig
|
@ -77,6 +77,20 @@ pub fn build(b: *std.Build) void {
|
||||||
const run_procnet_unit_tests = b.addRunArtifact(procnet_unit_tests);
|
const run_procnet_unit_tests = b.addRunArtifact(procnet_unit_tests);
|
||||||
run_procnet_unit_tests.has_side_effects = true; // needed so tests aren't cached
|
run_procnet_unit_tests.has_side_effects = true; // needed so tests aren't cached
|
||||||
|
|
||||||
|
// Creates a step for unit testing. This only builds the test executable
|
||||||
|
// but does not run it.
|
||||||
|
const process_unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = b.path("src/process.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
process_unit_tests.addIncludePath(.{ .path = "lib" });
|
||||||
|
process_unit_tests.linkLibrary(relib);
|
||||||
|
|
||||||
|
const run_process_unit_tests = b.addRunArtifact(process_unit_tests);
|
||||||
|
run_process_unit_tests.has_side_effects = true; // needed so tests aren't cached
|
||||||
|
|
||||||
const exe_unit_tests = b.addTest(.{
|
const exe_unit_tests = b.addTest(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
|
@ -92,5 +106,6 @@ pub fn build(b: *std.Build) void {
|
||||||
// running the unit tests.
|
// running the unit tests.
|
||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
test_step.dependOn(&run_procnet_unit_tests.step);
|
test_step.dependOn(&run_procnet_unit_tests.step);
|
||||||
|
test_step.dependOn(&run_process_unit_tests.step);
|
||||||
test_step.dependOn(&run_exe_unit_tests.step);
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
const std = @import("std");
|
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const proc = @import("procnet.zig");
|
const proc = @import("procnet.zig");
|
||||||
const pid = @import("procpid.zig");
|
const pid = @import("procpid.zig");
|
||||||
const clog = @import("proc.zig");
|
const clog = @import("proc.zig");
|
||||||
|
const proces = @import("process.zig");
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
@cInclude("arpa/inet.h");
|
@cInclude("arpa/inet.h");
|
||||||
@cInclude("signal.h");
|
@cInclude("signal.h");
|
||||||
|
|
28
src/proc.zig
28
src/proc.zig
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
// Socket that currently clogs a port with protocol information
|
/// Socket that currently clogs a port with protocol information
|
||||||
pub const ClogSocket = struct {
|
pub const ClogSocket = struct {
|
||||||
port: u16,
|
port: u16,
|
||||||
inode: std.posix.ino_t,
|
inode: std.posix.ino_t,
|
||||||
|
@ -13,7 +13,7 @@ pub const ClogSocket = struct {
|
||||||
protocol_data: ProtocolData,
|
protocol_data: ProtocolData,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Known protocols
|
/// Known protocols
|
||||||
pub const Protocol = enum {
|
pub const Protocol = enum {
|
||||||
udp_v4,
|
udp_v4,
|
||||||
udp_v6,
|
udp_v6,
|
||||||
|
@ -21,7 +21,7 @@ pub const Protocol = enum {
|
||||||
tcp_v6,
|
tcp_v6,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Protocol specific data
|
/// Protocol specific data
|
||||||
pub const ProtocolData = union(Protocol) {
|
pub const ProtocolData = union(Protocol) {
|
||||||
udp_v4: struct { addr: u32 },
|
udp_v4: struct { addr: u32 },
|
||||||
udp_v6: struct { addr: u128 },
|
udp_v6: struct { addr: u128 },
|
||||||
|
@ -29,15 +29,17 @@ pub const ProtocolData = union(Protocol) {
|
||||||
tcp_v6: struct { addr: u128 },
|
tcp_v6: struct { addr: u128 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses `/proc/net` information into a flat list of ClogSockets.
|
/// Parses `/proc/net` information into a flat list of ClogSockets.
|
||||||
//
|
///
|
||||||
// Memory is owned by caller. []ClogSocket must be freed by caller.
|
/// Memory is owned by caller. []ClogSocket must be freed by caller.
|
||||||
|
///
|
||||||
|
/// If proc_path is `null` will default to `/proc/net`
|
||||||
pub fn parse(alloc: std.mem.Allocator, proc_path: ?[]u8) ![]ClogSocket {
|
pub fn parse(alloc: std.mem.Allocator, proc_path: ?[]u8) ![]ClogSocket {
|
||||||
|
const base = proc_path orelse "/proc/net";
|
||||||
|
|
||||||
var buf = std.ArrayList(ClogSocket).init(alloc);
|
var buf = std.ArrayList(ClogSocket).init(alloc);
|
||||||
defer buf.deinit(); // noop we re-own memory to parent at the end
|
defer buf.deinit(); // noop we re-own memory to parent at the end
|
||||||
|
|
||||||
const base = proc_path orelse "/proc/net";
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
var arena = std.heap.ArenaAllocator.init(alloc);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const child_alloc = arena.allocator();
|
const child_alloc = arena.allocator();
|
||||||
|
@ -133,6 +135,12 @@ fn parse_internal(path: []const u8, protocol: Protocol, buf: *std.ArrayList(Clog
|
||||||
|
|
||||||
test "parse" {
|
test "parse" {
|
||||||
var alloc = std.testing.allocator;
|
var alloc = std.testing.allocator;
|
||||||
const clogs = try parse(alloc, null);
|
|
||||||
alloc.free(clogs);
|
std.testing.log_level = .info;
|
||||||
|
|
||||||
|
const file_path = try std.fs.cwd().realpathAlloc(alloc, "./test/proc1");
|
||||||
|
defer alloc.free(file_path);
|
||||||
|
|
||||||
|
const clogs = try parse(alloc, file_path);
|
||||||
|
defer alloc.free(clogs);
|
||||||
}
|
}
|
||||||
|
|
184
src/process.zig
Normal file
184
src/process.zig
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
//! Find and get information about a processes
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Clogs = struct {
|
||||||
|
items: []ClogProcess,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
try writer.print("{any}", .{value.items});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: @This()) void {
|
||||||
|
for (self.items) |item| {
|
||||||
|
item.deinit();
|
||||||
|
}
|
||||||
|
self.alloc.free(self.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ClogProcess = struct {
|
||||||
|
// central data
|
||||||
|
pid: std.posix.pid_t,
|
||||||
|
inode: std.posix.ino_t,
|
||||||
|
fd: std.posix.fd_t,
|
||||||
|
comm: []u8,
|
||||||
|
exe: []u8,
|
||||||
|
cmdline: [][]u8,
|
||||||
|
|
||||||
|
// for housekeeping
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
try writer.writeAll("\nClogProcess{\n");
|
||||||
|
try writer.print("\t.pid={d}, .inode={d}, .fd={d},\n", .{ value.pid, value.inode, value.fd });
|
||||||
|
try writer.print("\t.comm={s}\n", .{value.comm});
|
||||||
|
try writer.print("\t.exe={s}\n", .{value.exe});
|
||||||
|
try writer.print("\t.cmdline={s}\n", .{value.cmdline});
|
||||||
|
try writer.writeAll("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: @This()) void {
|
||||||
|
self.alloc.free(self.comm);
|
||||||
|
self.alloc.free(self.exe);
|
||||||
|
for (self.cmdline) |cl| {
|
||||||
|
self.alloc.free(cl);
|
||||||
|
}
|
||||||
|
self.alloc.free(self.cmdline);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn find_by_inode(alloc: std.mem.Allocator, inode: std.posix.ino_t, proc_path: ?[]u8) !Clogs {
|
||||||
|
const base = proc_path orelse "/proc";
|
||||||
|
|
||||||
|
var clogs = std.ArrayList(ClogProcess).init(alloc);
|
||||||
|
defer clogs.deinit(); // noop we re-own memory to parent at the end
|
||||||
|
|
||||||
|
const proc_dir = try std.fs.openDirAbsolute(base, .{ .iterate = true });
|
||||||
|
|
||||||
|
var process_it = PidIterator{ .it = AccessSafeIterator{ .it = proc_dir.iterate() } };
|
||||||
|
while (try process_it.next()) |process_entry| {
|
||||||
|
const process_dir = proc_dir.openDir(process_entry.name, .{ .iterate = true }) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
const fd_dir = process_dir.openDir("fd", .{ .iterate = true }) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
var fd_it = FdIterator{ .it = AccessSafeIterator{ .it = fd_dir.iterate() } };
|
||||||
|
fdit: while (try fd_it.next()) |fd_entry| {
|
||||||
|
_ = std.fmt.parseInt(std.posix.pid_t, fd_entry.name, 10) catch continue;
|
||||||
|
const fd_stat = fd_dir.statFile(fd_entry.name) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fd_stat.inode == inode) {
|
||||||
|
std.log.debug("Found inode {d} in {s}/{s}", .{ fd_stat.inode, process_entry.name, fd_entry.name });
|
||||||
|
|
||||||
|
const pid = try std.fmt.parseInt(std.posix.pid_t, process_entry.name, 10);
|
||||||
|
for (clogs.items) |clog| {
|
||||||
|
if (clog.pid == pid) {
|
||||||
|
std.log.debug("pid {d} already in clogs", .{pid});
|
||||||
|
continue :fdit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var comm_file = process_dir.openFile("comm", .{}) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
var cmdline_file = process_dir.openFile("cmdline", .{}) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
var exe_path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const exe_path = process_dir.readLink("exe", &exe_path_buf) catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
const comm = try comm_file.readToEndAlloc(alloc, 4096);
|
||||||
|
defer alloc.free(comm);
|
||||||
|
|
||||||
|
var cmdline = std.ArrayList([]u8).init(alloc);
|
||||||
|
const cmdline_raw = try cmdline_file.readToEndAlloc(alloc, 4096);
|
||||||
|
defer alloc.free(cmdline_raw);
|
||||||
|
var cmdline_it = std.mem.splitScalar(u8, cmdline_raw, 0x0);
|
||||||
|
while (cmdline_it.next()) |cl| {
|
||||||
|
try cmdline.append(try alloc.dupe(u8, std.mem.trim(u8, cl, "\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
try clogs.append(ClogProcess{
|
||||||
|
.pid = pid,
|
||||||
|
.inode = fd_stat.inode,
|
||||||
|
.fd = try std.fmt.parseInt(std.posix.pid_t, fd_entry.name, 10),
|
||||||
|
.comm = try alloc.dupe(u8, std.mem.trim(u8, comm, "\n")),
|
||||||
|
.cmdline = try cmdline.toOwnedSlice(),
|
||||||
|
.exe = try alloc.dupe(u8, exe_path),
|
||||||
|
.alloc = alloc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Clogs{ .items = try clogs.toOwnedSlice(), .alloc = alloc };
|
||||||
|
}
|
||||||
|
|
||||||
|
const FdIterator = struct {
|
||||||
|
it: AccessSafeIterator,
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) !?std.fs.Dir.Entry {
|
||||||
|
return try self.it.next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Iterator over directories that look like a process directory
|
||||||
|
const PidIterator = struct {
|
||||||
|
it: AccessSafeIterator,
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) !?std.fs.Dir.Entry {
|
||||||
|
while (try self.it.next()) |entry| {
|
||||||
|
if (entry.kind == .directory) {
|
||||||
|
_ = std.fmt.parseInt(std.posix.pid_t, entry.name, 10) catch continue;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Iterator which ignores `AccessDenied` errors from child iterator
|
||||||
|
const AccessSafeIterator = struct {
|
||||||
|
it: std.fs.Dir.Iterator,
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) !?std.fs.Dir.Entry {
|
||||||
|
while (true) {
|
||||||
|
const elem = self.it.next() catch |err| switch (err) {
|
||||||
|
error.AccessDenied => continue,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (elem) |e| {
|
||||||
|
self.it.dir.access(e.name, .{}) catch continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "simple" {
|
||||||
|
std.testing.log_level = .info;
|
||||||
|
|
||||||
|
const clogs = try find_by_inode(std.testing.allocator, 3568, null);
|
||||||
|
defer clogs.deinit();
|
||||||
|
std.log.info("clogs: {any}", .{clogs});
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Protocol {
|
|
||||||
proto: enum {
|
|
||||||
TCP_V4,
|
|
||||||
TCP_V6,
|
|
||||||
UDP_V4,
|
|
||||||
UDP_V6
|
|
||||||
},
|
|
||||||
port_map: std.AutoHashMap(u16, Process);
|
|
||||||
};
|
|
8
test/proc1/tcp
Normal file
8
test/proc1/tcp
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
||||||
|
0: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 7987 1 00000000510949ee 99 0 0 10 0
|
||||||
|
1: 0100007F:0521 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 229198 1 000000000cc7de62 99 0 0 10 0
|
||||||
|
2: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12004 1 000000000e13fb3e 99 0 0 10 5
|
||||||
|
3: 9600A8C0:91BE 1A71528C:01BB 01 00000000:00000000 02:00005772 00000000 1000 0 312207 2 0000000039cb8c9e 61 3 31 10 -1
|
||||||
|
4: 9600A8C0:8CF0 5DF36B22:01BB 01 00000000:00000000 02:00001FA2 00000000 1000 0 264134 2 00000000a051e7c1 22 3 31 10 -1
|
||||||
|
5: 9600A8C0:B0A6 AC4C825E:01BB 01 00000000:00000000 02:00097528 00000000 1000 0 304874 2 000000009579cfcc 22 3 30 10 -1
|
||||||
|
6: 9600A8C0:D042 AC4C825E:01BB 01 00000000:00000000 02:0005F59F 00000000 1000 0 279726 2 00000000b9879d52 22 3 24 10 -1
|
7
test/proc1/tcp6
Normal file
7
test/proc1/tcp6
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
||||||
|
0: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 7986 1 00000000a819e168 99 0 0 10 0
|
||||||
|
1: 0000000000000000FFFF00000100007F:CE5D 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 44943 1 000000009005f71a 99 0 0 10 0
|
||||||
|
2: 8883022A00E0C11A96B7CA3E649815D5:9B08 00470626000000000000000034B11368:01BB 06 00000000:00000000 03:00001606 00000000 0 0 0 3 00000000f49b65c7
|
||||||
|
3: 8883022A00E0C11A96B7CA3E649815D5:9A5A 7C060120F02001140000000001000000:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 291092 1 0000000038d2a6ed 23 3 30 10 -1
|
||||||
|
4: 8883022A00E0C11A96B7CA3E649815D5:E52E 5014002A0C080D40000000000E200000:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 320332 1 00000000ec54adc7 23 3 31 10 -1
|
||||||
|
5: 8883022A00E0C11A96B7CA3E649815D5:A0BA C011002A000066000000000033000000:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 306175 1 00000000e41ac297 23 3 28 10 -1
|
6
test/proc1/udp
Normal file
6
test/proc1/udp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
|
||||||
|
17624: 017AA8C0:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 12003 2 000000000be6b861 0
|
||||||
|
17638: 00000000:0043 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 12000 2 00000000183b2711 0
|
||||||
|
17639: 9600A8C0:0044 0100A8C0:0043 01 00000000:00000000 00:00000000 00000000 0 0 276252 2 000000000a09b1ba 0
|
||||||
|
22924: 00000000:14E9 00000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3352 2 000000002c689e03 0
|
||||||
|
31811: 00000000:B7A0 00000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3354 2 0000000071428bb3 0
|
5
test/proc1/udp6
Normal file
5
test/proc1/udp6
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
|
||||||
|
18117: 000080FE000000000716539EF9245F4B:0222 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 274919 2 00000000503fce27 0
|
||||||
|
20307: 00000000000000000000000000000000:8AB0 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3355 2 00000000e8a66559 0
|
||||||
|
22924: 00000000000000000000000000000000:14E9 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 974 0 3353 2 00000000f71f77f1 0
|
||||||
|
30065: 00000000000000000000000000000000:B0CE 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 1000 0 316229 2 000000008792aef6 640
|
Loading…
Reference in a new issue