lib/memtrace/src/stack/identity.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty_json = @import("pretty").json;
3 const sys = @import("sys");
4
5 pub const Digest = [std.crypto.hash.sha2.Sha256.digest_length]u8;
6 pub const digest_hex_len = @sizeOf(Digest) * 2;
7 const metadata_tag = "\"meta\":\"memtrace.executable\"";
8
9 pub fn isMetadataLine(line: []const u8) bool {
10 return std.mem.indexOf(u8, line, metadata_tag) != null;
11 }
12
13 pub fn runningDigest() !Digest {
14 if (comptime @import("builtin").os.tag != .linux) {
15 return error.UnsupportedExecutableIdentity;
16 }
17 const file = try sys.fs.openAbsoluteFile("/proc/self/exe", .{});
18 defer sys.fs.closeHandle(file);
19 return try digestHandle(file);
20 }
21
22 pub fn fileDigest(allocator: std.mem.Allocator, path: []const u8) !Digest {
23 const absolute = try sys.fs.realPathAlloc(allocator, path);
24 defer allocator.free(absolute);
25 const file = try sys.fs.openAbsoluteFile(absolute, .{});
26 defer sys.fs.closeHandle(file);
27 return try digestHandle(file);
28 }
29
30 pub fn artifactPathAlloc(
31 allocator: std.mem.Allocator,
32 events_path: []const u8,
33 ) ![]u8 {
34 return try std.fmt.allocPrint(allocator, "{s}.exe", .{events_path});
35 }
36
37 pub fn copyRunningExecutable(
38 path: []const u8,
39 expected: Digest,
40 ) !void {
41 if (comptime @import("builtin").os.tag != .linux) {
42 return error.UnsupportedExecutableIdentity;
43 }
44 const source = try sys.fs.openAbsoluteFile("/proc/self/exe", .{});
45 defer sys.fs.closeHandle(source);
46 const destination = try sys.fs.createFile(path, .{ .truncate = true });
47 defer sys.fs.closeHandle(destination);
48 var hasher = std.crypto.hash.sha2.Sha256.init(.{});
49 var buffer: [64 * 1024]u8 = undefined;
50 while (true) {
51 const count = try sys.fs.readHandle(source, &buffer);
52 if (count == 0) break;
53 hasher.update(buffer[0..count]);
54 try sys.fs.writeHandleAll(destination, buffer[0..count]);
55 }
56 var actual: Digest = undefined;
57 hasher.final(&actual);
58 if (!std.mem.eql(u8, &expected, &actual)) {
59 return error.ExecutableIdentityChanged;
60 }
61 }
62
63 pub fn writeMetadata(writer: *std.Io.Writer, digest: Digest) !void {
64 var stream = pretty_json.Writer.init(writer, .minified);
65 const object = try stream.object();
66 try object.field("v", 1);
67 try object.field("meta", "memtrace.executable");
68 try object.hexString("sha256", &digest);
69 try object.endLine();
70 }
71
72 pub fn parseDigest(text: []const u8) !Digest {
73 if (text.len != digest_hex_len) return error.InvalidExecutableIdentity;
74 var digest: Digest = undefined;
75 _ = std.fmt.hexToBytes(&digest, text) catch
76 return error.InvalidExecutableIdentity;
77 return digest;
78 }
79
80 fn digestHandle(file: std.Io.File) !Digest {
81 var hasher = std.crypto.hash.sha2.Sha256.init(.{});
82 var buffer: [64 * 1024]u8 = undefined;
83 while (true) {
84 const count = try sys.fs.readHandle(file, &buffer);
85 if (count == 0) break;
86 hasher.update(buffer[0..count]);
87 }
88 var digest: Digest = undefined;
89 hasher.final(&digest);
90 return digest;
91 }
92
93 test "digest parser accepts exact lowercase identity" {
94 const expected: Digest = @splat(0xab);
95 const hex = std.fmt.bytesToHex(expected, .lower);
96 const actual = try parseDigest(&hex);
97 try std.testing.expectEqualSlices(u8, &expected, &actual);
98 }