blob: 2f1290fa287fbee19212e7d6d7c40f8eb9fb902a [file] [log] [blame]
// 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_run_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"},
{}
}
};