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