| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) SUSE LLC, 2019 |
| * Author: Christian Amann <camann@suse.com> |
| */ |
| /* |
| * This tests if the kernel writes correct data to the |
| * process accounting file. |
| * |
| * First, system-wide process accounting is turned on and the output gets |
| * directed to a defined file. After that a dummy program is run in order |
| * to generate data and the process accounting gets turned off again. |
| * |
| * To verify the written data, the entries of the accounting file get |
| * parsed into the corresponding acct structure. Since it cannot be guaranteed |
| * that only the command issued by this test gets written into the accounting |
| * file, the contents get parsed until the correct entry is found, or EOF |
| * is reached. |
| * |
| * This is also accidental regression test for: |
| * 4d9570158b626 kernel/acct.c: fix the acct->needcheck check in check_free_space() |
| */ |
| |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include "tst_kconfig.h" |
| #include "tst_test.h" |
| #include "lapi/acct.h" |
| |
| #define COMMAND "acct02_helper" |
| #define OUTPUT_FILE "acct_file" |
| |
| #define UNPACK(x) ((x & 0x1fff) << (((x >> 13) & 0x7) * 3)) |
| #define ACCT_MEMBER(x) (v3 ? ((struct acct_v3 *)acc)->x : ((struct acct *)acc)->x) |
| #define ACCT_MEMBER_V3(x) (((struct acct_v3 *)acc)->x) |
| |
| static int fd; |
| static int v3; |
| static int acct_size; |
| static int clock_ticks; |
| static unsigned int rc; |
| static unsigned int start_time; |
| |
| static union acct_union { |
| struct acct v0; |
| struct acct_v3 v3; |
| } acct_struct; |
| |
| static int acct_version_is_3(void) |
| { |
| const char *kconfig_acct_v3[] = { |
| "CONFIG_BSD_PROCESS_ACCT_V3", |
| NULL |
| }; |
| |
| struct tst_kconfig_res results[1]; |
| |
| tst_kconfig_read(kconfig_acct_v3, results, 1); |
| |
| return results[0].match == 'y'; |
| } |
| |
| static void run_command(void) |
| { |
| const char *const cmd[] = {COMMAND, NULL}; |
| |
| rc = tst_cmd(cmd, NULL, NULL, 1) << 8; |
| } |
| |
| static int verify_acct(void *acc, int elap_time) |
| { |
| int sys_time = UNPACK(ACCT_MEMBER(ac_stime)); |
| int user_time = UNPACK(ACCT_MEMBER(ac_stime)); |
| unsigned int btime_diff; |
| int ret = 0; |
| float tmp2; |
| |
| if (strcmp(ACCT_MEMBER(ac_comm), COMMAND)) { |
| tst_res(TINFO, "ac_comm != '%s' ('%s')", COMMAND, |
| ACCT_MEMBER(ac_comm)); |
| ret = 1; |
| } |
| |
| if (start_time > ACCT_MEMBER(ac_btime)) |
| btime_diff = start_time - ACCT_MEMBER(ac_btime); |
| else |
| btime_diff = ACCT_MEMBER(ac_btime) - start_time; |
| |
| if (btime_diff > 7200) { |
| tst_res(TINFO, "ac_btime_diff %u", btime_diff); |
| ret = 1; |
| } |
| |
| if (ACCT_MEMBER(ac_uid) != getuid()) { |
| tst_res(TINFO, "ac_uid != %d (%d)", getuid(), |
| ACCT_MEMBER(ac_uid)); |
| ret = 1; |
| } |
| |
| if (ACCT_MEMBER(ac_gid) != getgid()) { |
| tst_res(TINFO, "ac_gid != %d (%d)", getgid(), |
| ACCT_MEMBER(ac_gid)); |
| ret = 1; |
| } |
| |
| tmp2 = user_time/clock_ticks; |
| if (tmp2 > 1) { |
| tst_res(TINFO, "user_time/clock_ticks > 1 (%d/%d: %.2f)", |
| user_time, clock_ticks, tmp2); |
| ret = 1; |
| } |
| |
| tmp2 = sys_time/clock_ticks; |
| if (tmp2 > 1) { |
| tst_res(TINFO, "sys_time/clock_ticks > 1 (%d/%d: %.2f)", |
| sys_time, clock_ticks, tmp2); |
| ret = 1; |
| } |
| |
| tmp2 = elap_time/clock_ticks; |
| if (tmp2 >= 2) { |
| tst_res(TINFO, "elap_time/clock_ticks >= 2 (%d/%d: %.2f)", |
| elap_time, clock_ticks, tmp2); |
| ret = 1; |
| } |
| |
| if (ACCT_MEMBER(ac_exitcode) != rc) { |
| tst_res(TINFO, "ac_exitcode != %d (%d)", rc, |
| ACCT_MEMBER(ac_exitcode)); |
| ret = 1; |
| } |
| if (!v3) |
| return ret; |
| |
| if (ACCT_MEMBER_V3(ac_ppid) != (uint32_t)getpid()) { |
| tst_res(TINFO, "ac_ppid != %d (%d)", (uint32_t)getpid(), |
| ACCT_MEMBER_V3(ac_ppid)); |
| ret = 1; |
| } |
| |
| if (ACCT_MEMBER_V3(ac_version) != (3 | ACCT_BYTEORDER)) { |
| tst_res(TINFO, "ac_version != 3 (%d)", |
| ACCT_MEMBER_V3(ac_version)); |
| ret = 1; |
| } |
| |
| if (ACCT_MEMBER_V3(ac_pid) < 1) { |
| tst_res(TINFO, "ac_pid < 1 (%d)", ACCT_MEMBER_V3(ac_pid)); |
| ret = 1; |
| } |
| return ret; |
| } |
| |
| static void run(void) |
| { |
| int read_bytes, ret; |
| int entry_count = 0, i = 0; |
| |
| fd = SAFE_OPEN(OUTPUT_FILE, O_RDWR | O_CREAT, 0644); |
| |
| TEST(acct(OUTPUT_FILE)); |
| if (TST_RET == -1) |
| tst_brk(TBROK | TTERRNO, "Could not set acct output file"); |
| |
| start_time = time(NULL); |
| run_command(); |
| acct(NULL); |
| |
| do { |
| read_bytes = SAFE_READ(0, fd, &acct_struct, acct_size); |
| |
| if (i == 0 && read_bytes == 0) { |
| tst_res(TFAIL, "acct file is empty"); |
| goto exit; |
| } |
| |
| if (read_bytes == 0) { |
| tst_res(TFAIL, "end of file reached"); |
| goto exit; |
| } |
| |
| if (read_bytes != acct_size) { |
| tst_res(TFAIL, "incomplete read %i bytes, expected %i", |
| read_bytes, acct_size); |
| goto exit; |
| } |
| |
| tst_res(TINFO, "== entry %d ==", ++i); |
| |
| if (v3) |
| ret = verify_acct(&acct_struct.v3, acct_struct.v3.ac_etime); |
| else |
| ret = verify_acct(&acct_struct.v0, UNPACK(acct_struct.v0.ac_etime)); |
| |
| if (read_bytes) |
| entry_count++; |
| } while (read_bytes == acct_size && ret); |
| |
| tst_res(TINFO, "Number of accounting file entries tested: %d", |
| entry_count); |
| |
| if (ret) |
| tst_res(TFAIL, "acct() wrote incorrect file contents!"); |
| else |
| tst_res(TPASS, "acct() wrote correct file contents!"); |
| |
| exit: |
| SAFE_CLOSE(fd); |
| } |
| |
| static void setup(void) |
| { |
| struct statfs buf; |
| |
| clock_ticks = SAFE_SYSCONF(_SC_CLK_TCK); |
| |
| SAFE_STATFS(".", &buf); |
| |
| float avail = (100.00 * buf.f_bavail) / buf.f_blocks; |
| |
| /* The accounting data are silently discarded on nearly FS */ |
| if (avail < 4.1) { |
| tst_brk(TCONF, |
| "Less than 4.1%% (%.2f) of free space on filesystem", |
| avail); |
| } |
| |
| TEST(acct(NULL)); |
| if (TST_RET == -1) |
| tst_brk(TBROK | TTERRNO, |
| "acct() system call returned with error"); |
| |
| v3 = acct_version_is_3(); |
| if (v3) { |
| tst_res(TINFO, "Verifying using 'struct acct_v3'"); |
| acct_size = sizeof(struct acct_v3); |
| } else { |
| tst_res(TINFO, "Verifying using 'struct acct'"); |
| acct_size = sizeof(struct acct); |
| } |
| } |
| |
| static void cleanup(void) |
| { |
| if (fd > 0) |
| SAFE_CLOSE(fd); |
| acct(NULL); |
| } |
| |
| static const char *kconfigs[] = { |
| "CONFIG_BSD_PROCESS_ACCT", |
| NULL |
| }; |
| |
| static struct tst_test test = { |
| .test_all = run, |
| .needs_kconfigs = kconfigs, |
| .setup = setup, |
| .cleanup = cleanup, |
| .needs_tmpdir = 1, |
| .needs_root = 1, |
| .tags = (const struct tst_tag[]) { |
| {"linux-git", "4d9570158b626"}, |
| {} |
| } |
| }; |