freebsd-src/tests/sys/kern/fdgrowtable_test.c
Gleb Smirnoff 4c6ceca9cc tests/fdgrowtable: perform the threaded test in a child process
The test needs to be performed in a new process that was forked with
RFCFDG flag.  The will guarantee that the table will start to grow from 20
file descriptors, no matter what kyua(1) or a bare shell was doing before
executing this test.  This should fix  repetitive test runs from a shell
as well as failures with kyua(1) in some environments.
2024-02-23 17:49:53 -08:00

287 lines
6.8 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2020 Rob Wing
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <sys/filedesc.h>
#include <sys/queue.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <atf-c.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* linked libraries */
#include <kvm.h>
#include <libutil.h>
#include <libprocstat.h>
#include <pthread.h>
/* test-case macro */
#define AFILE "afile"
/*
* The following macros, struct freetable, struct fdescenttbl0
* and struct filedesc0 are copied from sys/kern/kern_descrip.c
*/
#define NDFILE 20
#define NDSLOTSIZE sizeof(NDSLOTTYPE)
#define NDENTRIES (NDSLOTSIZE * __CHAR_BIT)
#define NDSLOT(x) ((x) / NDENTRIES)
#define NDBIT(x) ((NDSLOTTYPE)1 << ((x) % NDENTRIES))
#define NDSLOTS(x) (((x) + NDENTRIES - 1) / NDENTRIES)
struct freetable {
struct fdescenttbl *ft_table;
SLIST_ENTRY(freetable) ft_next;
};
struct fdescenttbl0 {
int fdt_nfiles;
struct filedescent fdt_ofiles[NDFILE];
};
struct filedesc0 {
struct filedesc fd_fd;
SLIST_HEAD(, freetable) fd_free;
struct fdescenttbl0 fd_dfiles;
NDSLOTTYPE fd_dmap[NDSLOTS(NDFILE)];
};
static void
openfiles(int n)
{
int i, fd;
ATF_REQUIRE((fd = open(AFILE, O_CREAT, 0644)) != -1);
close(fd);
for (i = 0; i < n; i++)
ATF_REQUIRE((fd = open(AFILE, O_RDONLY, 0644)) != -1);
}
/*
* Get a count of the old file descriptor tables on the freelist.
*/
static int
old_tables(kvm_t *kd, struct kinfo_proc *kp)
{
struct filedesc0 fdp0;
struct freetable *ft, tft;
int counter;
counter = 0;
ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp0, sizeof(fdp0)) > 0);
SLIST_FOREACH(ft, &fdp0.fd_free, ft_next) {
ATF_REQUIRE(kvm_read(kd, (unsigned long) ft, &tft, sizeof(tft)) > 0 );
ft = &tft;
counter++;
}
return (counter);
}
/*
* The returning struct kinfo_proc stores kernel addresses that will be
* used by kvm_read to retrieve information for the current process.
*/
static struct kinfo_proc *
read_kinfo(kvm_t *kd)
{
struct kinfo_proc *kp;
int procs_found;
ATF_REQUIRE((kp = kvm_getprocs(kd, KERN_PROC_PID, (int) getpid(), &procs_found)) != NULL);
ATF_REQUIRE(procs_found == 1);
return (kp);
}
/*
* Test a single threaded process that doesn't have a shared
* file descriptor table. The old tables should be freed.
*/
ATF_TC(free_oldtables);
ATF_TC_HEAD(free_oldtables, tc)
{
atf_tc_set_md_var(tc, "require.user", "root");
}
ATF_TC_BODY(free_oldtables, tc)
{
kvm_t *kd;
struct kinfo_proc *kp;
ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
openfiles(128);
kp = read_kinfo(kd);
ATF_CHECK(old_tables(kd,kp) == 0);
}
static _Noreturn void *
exec_thread(void *args)
{
for (;;)
sleep(1);
}
/*
* Test a process with two threads that doesn't have a shared file
* descriptor table. The old tables should not be freed.
*/
ATF_TC(oldtables_shared_via_threads);
ATF_TC_HEAD(oldtables_shared_via_threads, tc)
{
atf_tc_set_md_var(tc, "require.user", "root");
}
ATF_TC_BODY(oldtables_shared_via_threads, tc)
{
pid_t child;
kvm_t *kd;
struct kinfo_proc *kp;
pthread_t thread;
if ((child = rfork(RFPROC | RFCFDG)) > 0) {
pid_t wpid;
int status;
wpid = waitpid(child, &status, 0);
ATF_REQUIRE(wpid == child);
ATF_REQUIRE(WIFEXITED(status));
ATF_REQUIRE(WEXITSTATUS(status) == EXIT_SUCCESS);
return;
}
#define REQUIRE(expression) do { \
if (!(expression)) \
exit(EXIT_FAILURE); \
} while (0)
REQUIRE(child == 0);
REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0);
openfiles(128);
kp = read_kinfo(kd);
REQUIRE(kp->ki_numthreads > 1);
REQUIRE(old_tables(kd,kp) > 1);
REQUIRE(pthread_cancel(thread) == 0);
REQUIRE(pthread_join(thread, NULL) == 0);
#undef REQUIRE
exit(EXIT_SUCCESS);
}
/*
* Get the reference count of a file descriptor table.
*/
static int
filedesc_refcnt(kvm_t *kd, struct kinfo_proc *kp)
{
struct filedesc fdp;
ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp, sizeof(fdp)) > 0);
return (fdp.fd_refcnt);
}
/*
* Test a single threaded process that shares a file descriptor
* table with another process. The old tables should not be freed.
*/
ATF_TC(oldtables_shared_via_process);
ATF_TC_HEAD(oldtables_shared_via_process, tc)
{
atf_tc_set_md_var(tc, "require.user", "root");
}
ATF_TC_BODY(oldtables_shared_via_process, tc)
{
kvm_t *kd;
struct kinfo_proc *kp;
int status;
pid_t child, wpid;
ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
/* share the file descriptor table */
ATF_REQUIRE((child = rfork(RFPROC)) != -1);
if (child == 0) {
openfiles(128);
raise(SIGSTOP);
exit(127);
}
/* let parent process open some files too */
openfiles(128);
/* get current status of child */
wpid = waitpid(child, &status, WUNTRACED);
ATF_REQUIRE(wpid == child);
/* child should be stopped */
ATF_REQUIRE(WIFSTOPPED(status));
/*
* We want to read kernel data
* before the child exits
* otherwise we'll lose a reference count
* to the file descriptor table
*/
kp = read_kinfo(kd);
ATF_CHECK(filedesc_refcnt(kd,kp) > 1);
ATF_CHECK(old_tables(kd,kp) > 1);
kill(child, SIGCONT);
/* child should have exited */
wpid = waitpid(child, &status, 0);
ATF_REQUIRE(wpid == child);
ATF_REQUIRE(WIFEXITED(status));
ATF_REQUIRE(WEXITSTATUS(status) == 127);
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, free_oldtables);
ATF_TP_ADD_TC(tp, oldtables_shared_via_threads);
ATF_TP_ADD_TC(tp, oldtables_shared_via_process);
return (atf_no_error());
}