1// A zig test case that exercises some glibc symbols that have uncovered
2// problems in the past. This test must be compiled against a glibc.
3//
4// The build.zig tests the binary built from this source to see that
5// symbols are statically or dynamically linked, as expected.
6
7const std = @import("std");
8const builtin = @import("builtin");
9const assert = std.debug.assert;
10
11const c_malloc = @import("malloc.h"); // for reallocarray
12const c_stdlib = @import("stdlib.h"); // for atexit
13const c_string = @import("string.h"); // for strlcpy
14
15// Version of glibc this test is being built to run against
16const glibc_ver = builtin.os.versionRange().gnuLibCVersion().?;
17
18extern "c" fn fstatat(dirfd: i32, path: [*:0]const u8, buf: [*]const u8, flag: u32) c_int;
19extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: [*]const u8) c_int;
20
21// PR #17034 - fstat moved between libc_nonshared and libc
22fn checkStat() !void {
23 const cwd_fd = std.Io.Dir.cwd().handle;
24
25 var buf: [256]u8 = @splat(0);
26 var result = fstatat(cwd_fd, "a_file_that_definitely_does_not_exist", &buf, 0);
27 assert(result == -1);
28 assert(std.posix.errno(result) == .NOENT);
29
30 result = stat("a_file_that_definitely_does_not_exist", &buf);
31 assert(result == -1);
32 assert(std.posix.errno(result) == .NOENT);
33}
34
35// PR #17607 - reallocarray not visible in headers
36fn checkReallocarray() !void {
37 // reallocarray was introduced in v2.26
38 if (comptime glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
39 if (@hasDecl(c_malloc, "reallocarray")) {
40 @compileError("Before v2.26 'malloc.h' does not define 'reallocarray'");
41 }
42 } else {
43 return try checkReallocarray_v2_26();
44 }
45}
46
47fn checkReallocarray_v2_26() !void {
48 const size = 16;
49 const tenX = c_malloc.reallocarray(c_malloc.NULL, 10, size);
50 const elevenX = c_malloc.reallocarray(tenX, 11, size);
51
52 assert(tenX != c_malloc.NULL);
53 assert(elevenX != c_malloc.NULL);
54}
55
56// getauxval introduced in v2.16
57fn checkGetAuxVal() !void {
58 if (comptime glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
59 if (@hasDecl(std.c, "getauxval")) {
60 @compileError("Before v2.16 glibc does not define 'getauxval'");
61 }
62 } else {
63 try checkGetAuxVal_v2_16();
64 }
65}
66
67fn checkGetAuxVal_v2_16() !void {
68 const base = std.c.getauxval(std.elf.AT_BASE);
69 const pgsz = std.c.getauxval(std.elf.AT_PAGESZ);
70
71 assert(base != 0);
72 assert(pgsz != 0);
73}
74
75// strlcpy introduced in v2.38, which is newer than many installed glibcs
76fn checkStrlcpy() !void {
77 if (comptime glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
78 if (@hasDecl(c_string, "strlcpy")) {
79 @compileError("Before v2.38 glibc does not define 'strlcpy'");
80 }
81 } else {
82 try checkStrlcpy_v2_38();
83 }
84}
85
86fn checkStrlcpy_v2_38() !void {
87 var buf: [99]u8 = undefined;
88 const used = c_string.strlcpy(&buf, "strlcpy works!", buf.len);
89 assert(used == 14);
90}
91
92// atexit is part of libc_nonshared, so ensure its linked in correctly
93fn forceExit0Callback() callconv(.c) void {
94 std.c.exit(0); // Override the main() exit code
95}
96
97fn checkAtExit() !void {
98 const result = c_stdlib.atexit(forceExit0Callback);
99 assert(result == 0);
100}
101
102pub fn main() !u8 {
103 try checkStat();
104 try checkReallocarray();
105 try checkStrlcpy();
106
107 try checkGetAuxVal();
108 try checkAtExit();
109
110 std.c.exit(1); // overridden by atexit() callback
111}