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