272 lines
6.0 KiB
C
272 lines
6.0 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) SUSE LLC, 2019
|
|
* Author: Christian Amann <camann@suse.com>
|
|
*/
|
|
/*\
|
|
* [DOCUMENTATION]
|
|
*
|
|
* 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;
|
|
|
|
#define ACCT_V3 "CONFIG_BSD_PROCESS_ACCT_V3"
|
|
|
|
static int acct_version_is_3(void)
|
|
{
|
|
struct tst_kconfig_var kconfig = {
|
|
.id = ACCT_V3,
|
|
.id_len = sizeof(ACCT_V3)-1,
|
|
};
|
|
|
|
tst_kconfig_read(&kconfig, 1);
|
|
|
|
tst_res(TINFO, ACCT_V3 "=%c", kconfig.choice);
|
|
|
|
return kconfig.choice == 'y';
|
|
}
|
|
|
|
static void run_command(void)
|
|
{
|
|
const char *const cmd[] = {COMMAND, NULL};
|
|
|
|
rc = tst_cmd(cmd, NULL, NULL, TST_CMD_PASS_RETVAL) << 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) != (char)(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 struct tst_test test = {
|
|
.test_all = run,
|
|
.needs_kconfigs = (const char *[]) {
|
|
"CONFIG_BSD_PROCESS_ACCT",
|
|
NULL
|
|
},
|
|
.setup = setup,
|
|
.cleanup = cleanup,
|
|
.needs_tmpdir = 1,
|
|
.needs_root = 1,
|
|
.tags = (const struct tst_tag[]) {
|
|
{"linux-git", "4d9570158b626"},
|
|
{}
|
|
}
|
|
};
|