1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) SUSE LLC, 2019
4 * Copyright (c) Linux Test Project, 2019-2023
5 * Author: Christian Amann <[email protected]>
6 */
7 /*\
8 * [Description]
9 *
10 * This tests if the kernel writes correct data to the
11 * process accounting file.
12 *
13 * First, system-wide process accounting is turned on and the output gets
14 * directed to a defined file. After that a dummy program is run in order
15 * to generate data and the process accounting gets turned off again.
16 *
17 * To verify the written data, the entries of the accounting file get
18 * parsed into the corresponding acct structure. Since it cannot be guaranteed
19 * that only the command issued by this test gets written into the accounting
20 * file, the contents get parsed until the correct entry is found, or EOF
21 * is reached.
22 *
23 * This is also regression test for commit:
24 * 4d9570158b62 ("kernel/acct.c: fix the acct->needcheck check in check_free_space()")
25 */
26
27 #include <sys/stat.h>
28 #include <errno.h>
29 #include <string.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include "tst_kconfig.h"
33 #include "tst_test.h"
34 #include "lapi/acct.h"
35
36 #define COMMAND "acct02_helper"
37 #define OUTPUT_FILE "acct_file"
38
39 #define UNPACK(x) ((x & 0x1fff) << (((x >> 13) & 0x7) * 3))
40 #define ACCT_MEMBER(x) (v3 ? ((struct acct_v3 *)acc)->x : ((struct acct *)acc)->x)
41 #define ACCT_MEMBER_V3(x) (((struct acct_v3 *)acc)->x)
42
43 static int fd;
44 static int v3;
45 static int acct_size;
46 static int clock_ticks;
47 static unsigned int rc;
48 static unsigned int start_time;
49
50 static union acct_union {
51 struct acct v0;
52 struct acct_v3 v3;
53 } acct_struct;
54
55 #define ACCT_V3 "CONFIG_BSD_PROCESS_ACCT_V3"
56
acct_version_is_3(void)57 static int acct_version_is_3(void)
58 {
59 struct tst_kconfig_var kconfig = TST_KCONFIG_INIT(ACCT_V3);
60
61 tst_kconfig_read(&kconfig, 1);
62
63 tst_res(TINFO, ACCT_V3 "=%c", kconfig.choice);
64
65 return kconfig.choice == 'y';
66 }
67
run_command(void)68 static void run_command(void)
69 {
70 const char *const cmd[] = {COMMAND, NULL};
71
72 rc = tst_cmd(cmd, NULL, NULL, TST_CMD_PASS_RETVAL) << 8;
73 }
74
verify_acct(void * acc,int elap_time)75 static int verify_acct(void *acc, int elap_time)
76 {
77 int sys_time = UNPACK(ACCT_MEMBER(ac_stime));
78 int user_time = UNPACK(ACCT_MEMBER(ac_stime));
79 unsigned int btime_diff;
80 int ret = 0;
81 float tmp2;
82
83 if (strcmp(ACCT_MEMBER(ac_comm), COMMAND)) {
84 tst_res(TINFO, "ac_comm != '%s' ('%s')", COMMAND,
85 ACCT_MEMBER(ac_comm));
86 ret = 1;
87 }
88
89 if (start_time > ACCT_MEMBER(ac_btime))
90 btime_diff = start_time - ACCT_MEMBER(ac_btime);
91 else
92 btime_diff = ACCT_MEMBER(ac_btime) - start_time;
93
94 if (btime_diff > 7200) {
95 tst_res(TINFO, "ac_btime_diff %u", btime_diff);
96 ret = 1;
97 }
98
99 if (ACCT_MEMBER(ac_uid) != getuid()) {
100 tst_res(TINFO, "ac_uid != %d (%d)", getuid(),
101 ACCT_MEMBER(ac_uid));
102 ret = 1;
103 }
104
105 if (ACCT_MEMBER(ac_gid) != getgid()) {
106 tst_res(TINFO, "ac_gid != %d (%d)", getgid(),
107 ACCT_MEMBER(ac_gid));
108 ret = 1;
109 }
110
111 tmp2 = user_time/clock_ticks;
112 if (tmp2 > 1) {
113 tst_res(TINFO, "user_time/clock_ticks > 1 (%d/%d: %.2f)",
114 user_time, clock_ticks, tmp2);
115 ret = 1;
116 }
117
118 tmp2 = sys_time/clock_ticks;
119 if (tmp2 > 1) {
120 tst_res(TINFO, "sys_time/clock_ticks > 1 (%d/%d: %.2f)",
121 sys_time, clock_ticks, tmp2);
122 ret = 1;
123 }
124
125 tmp2 = elap_time/clock_ticks;
126 if (tmp2 >= 2) {
127 tst_res(TINFO, "elap_time/clock_ticks >= 2 (%d/%d: %.2f)",
128 elap_time, clock_ticks, tmp2);
129 ret = 1;
130 }
131
132 if (ACCT_MEMBER(ac_exitcode) != rc) {
133 tst_res(TINFO, "ac_exitcode != %d (%d)", rc,
134 ACCT_MEMBER(ac_exitcode));
135 ret = 1;
136 }
137 if (!v3)
138 return ret;
139
140 if (ACCT_MEMBER_V3(ac_ppid) != (uint32_t)getpid()) {
141 tst_res(TINFO, "ac_ppid != %d (%d)", (uint32_t)getpid(),
142 ACCT_MEMBER_V3(ac_ppid));
143 ret = 1;
144 }
145
146 if (ACCT_MEMBER_V3(ac_version) != (char)(3 | ACCT_BYTEORDER)) {
147 tst_res(TINFO, "ac_version != 3 (%d)",
148 ACCT_MEMBER_V3(ac_version));
149 ret = 1;
150 }
151
152 if (ACCT_MEMBER_V3(ac_pid) < 1) {
153 tst_res(TINFO, "ac_pid < 1 (%d)", ACCT_MEMBER_V3(ac_pid));
154 ret = 1;
155 }
156 return ret;
157 }
158
run(void)159 static void run(void)
160 {
161 int read_bytes, ret;
162 int entry_count = 0, i = 0;
163
164 fd = SAFE_OPEN(OUTPUT_FILE, O_RDWR | O_CREAT, 0644);
165
166 TEST(acct(OUTPUT_FILE));
167 if (TST_RET == -1)
168 tst_brk(TBROK | TTERRNO, "Could not set acct output file");
169
170 start_time = time(NULL);
171 run_command();
172 acct(NULL);
173
174 do {
175 read_bytes = SAFE_READ(0, fd, &acct_struct, acct_size);
176
177 if (i == 0 && read_bytes == 0) {
178 tst_res(TFAIL, "acct file is empty");
179 goto exit;
180 }
181
182 if (read_bytes == 0) {
183 tst_res(TFAIL, "end of file reached");
184 goto exit;
185 }
186
187 if (read_bytes != acct_size) {
188 tst_res(TFAIL, "incomplete read %i bytes, expected %i",
189 read_bytes, acct_size);
190 goto exit;
191 }
192
193 tst_res(TINFO, "== entry %d ==", ++i);
194
195 if (v3)
196 ret = verify_acct(&acct_struct.v3, acct_struct.v3.ac_etime);
197 else
198 ret = verify_acct(&acct_struct.v0, UNPACK(acct_struct.v0.ac_etime));
199
200 if (read_bytes)
201 entry_count++;
202 } while (read_bytes == acct_size && ret);
203
204 tst_res(TINFO, "Number of accounting file entries tested: %d",
205 entry_count);
206
207 if (ret)
208 tst_res(TFAIL, "acct() wrote incorrect file contents!");
209 else
210 tst_res(TPASS, "acct() wrote correct file contents!");
211
212 exit:
213 SAFE_CLOSE(fd);
214 }
215
setup(void)216 static void setup(void)
217 {
218 struct statfs buf;
219
220 clock_ticks = SAFE_SYSCONF(_SC_CLK_TCK);
221
222 SAFE_STATFS(".", &buf);
223
224 float avail = (100.00 * buf.f_bavail) / buf.f_blocks;
225
226 /* The accounting data are silently discarded on nearly FS */
227 if (avail < 4.1) {
228 tst_brk(TCONF,
229 "Less than 4.1%% (%.2f) of free space on filesystem",
230 avail);
231 }
232
233 TEST(acct(NULL));
234 if (TST_RET == -1)
235 tst_brk(TBROK | TTERRNO,
236 "acct() system call returned with error");
237
238 v3 = acct_version_is_3();
239 if (v3) {
240 tst_res(TINFO, "Verifying using 'struct acct_v3'");
241 acct_size = sizeof(struct acct_v3);
242 } else {
243 tst_res(TINFO, "Verifying using 'struct acct'");
244 acct_size = sizeof(struct acct);
245 }
246 }
247
cleanup(void)248 static void cleanup(void)
249 {
250 if (fd > 0)
251 SAFE_CLOSE(fd);
252 acct(NULL);
253 }
254
255 static struct tst_test test = {
256 .test_all = run,
257 .needs_kconfigs = (const char *[]) {
258 "CONFIG_BSD_PROCESS_ACCT",
259 NULL
260 },
261 .setup = setup,
262 .cleanup = cleanup,
263 .needs_tmpdir = 1,
264 .needs_root = 1,
265 .tags = (const struct tst_tag[]) {
266 {"linux-git", "4d9570158b626"},
267 {}
268 }
269 };
270