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 }