The fixed path and ports get in the way of running our tests and builds in parallel. Instead of using TESTPATH we use mkdtemp() and instead of a fixed port we allow the kernel to assign one and query it afterwards. Ideally test directory creation should be common functionally across all TCG tests but this could complicate an already huge patch series so we mark it as a TODO for next time. Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
532 lines
13 KiB
C
532 lines
13 KiB
C
/*
|
|
* linux and CPU test
|
|
*
|
|
* Copyright (c) 2003 Fabrice Bellard
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <errno.h>
|
|
#include <utime.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <sched.h>
|
|
#include <dirent.h>
|
|
#include <setjmp.h>
|
|
#include <sys/shm.h>
|
|
#include <assert.h>
|
|
|
|
#define STACK_SIZE 16384
|
|
|
|
static void error1(const char *filename, int line, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
fprintf(stderr, "%s:%d: ", filename, line);
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
exit(1);
|
|
}
|
|
|
|
static int __chk_error(const char *filename, int line, int ret)
|
|
{
|
|
if (ret < 0) {
|
|
error1(filename, line, "%m (ret=%d, errno=%d/%s)",
|
|
ret, errno, strerror(errno));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define error(fmt, ...) error1(__FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
|
|
|
#define chk_error(ret) __chk_error(__FILE__, __LINE__, (ret))
|
|
|
|
/*******************************************************/
|
|
|
|
#define FILE_BUF_SIZE 300
|
|
|
|
static void test_file(void)
|
|
{
|
|
int fd, i, len, ret;
|
|
uint8_t buf[FILE_BUF_SIZE];
|
|
uint8_t buf2[FILE_BUF_SIZE];
|
|
uint8_t buf3[FILE_BUF_SIZE];
|
|
char cur_dir[1024];
|
|
struct stat st;
|
|
struct utimbuf tbuf;
|
|
struct iovec vecs[2];
|
|
DIR *dir;
|
|
struct dirent *de;
|
|
/* TODO: make common tempdir creation for tcg tests */
|
|
char template[] = "/tmp/linux-test-XXXXXX";
|
|
char *tmpdir = mkdtemp(template);
|
|
|
|
assert(tmpdir);
|
|
|
|
if (getcwd(cur_dir, sizeof(cur_dir)) == NULL)
|
|
error("getcwd");
|
|
|
|
chk_error(chdir(tmpdir));
|
|
|
|
/* open/read/write/close/readv/writev/lseek */
|
|
|
|
fd = chk_error(open("file1", O_WRONLY | O_TRUNC | O_CREAT, 0644));
|
|
for(i=0;i < FILE_BUF_SIZE; i++)
|
|
buf[i] = i;
|
|
len = chk_error(write(fd, buf, FILE_BUF_SIZE / 2));
|
|
if (len != (FILE_BUF_SIZE / 2))
|
|
error("write");
|
|
vecs[0].iov_base = buf + (FILE_BUF_SIZE / 2);
|
|
vecs[0].iov_len = 16;
|
|
vecs[1].iov_base = buf + (FILE_BUF_SIZE / 2) + 16;
|
|
vecs[1].iov_len = (FILE_BUF_SIZE / 2) - 16;
|
|
len = chk_error(writev(fd, vecs, 2));
|
|
if (len != (FILE_BUF_SIZE / 2))
|
|
error("writev");
|
|
chk_error(close(fd));
|
|
|
|
chk_error(rename("file1", "file2"));
|
|
|
|
fd = chk_error(open("file2", O_RDONLY));
|
|
|
|
len = chk_error(read(fd, buf2, FILE_BUF_SIZE));
|
|
if (len != FILE_BUF_SIZE)
|
|
error("read");
|
|
if (memcmp(buf, buf2, FILE_BUF_SIZE) != 0)
|
|
error("memcmp");
|
|
|
|
#define FOFFSET 16
|
|
ret = chk_error(lseek(fd, FOFFSET, SEEK_SET));
|
|
if (ret != 16)
|
|
error("lseek");
|
|
vecs[0].iov_base = buf3;
|
|
vecs[0].iov_len = 32;
|
|
vecs[1].iov_base = buf3 + 32;
|
|
vecs[1].iov_len = FILE_BUF_SIZE - FOFFSET - 32;
|
|
len = chk_error(readv(fd, vecs, 2));
|
|
if (len != FILE_BUF_SIZE - FOFFSET)
|
|
error("readv");
|
|
if (memcmp(buf + FOFFSET, buf3, FILE_BUF_SIZE - FOFFSET) != 0)
|
|
error("memcmp");
|
|
|
|
chk_error(close(fd));
|
|
|
|
/* access */
|
|
chk_error(access("file2", R_OK));
|
|
|
|
/* stat/chmod/utime/truncate */
|
|
|
|
chk_error(chmod("file2", 0600));
|
|
tbuf.actime = 1001;
|
|
tbuf.modtime = 1000;
|
|
chk_error(truncate("file2", 100));
|
|
chk_error(utime("file2", &tbuf));
|
|
chk_error(stat("file2", &st));
|
|
if (st.st_size != 100)
|
|
error("stat size");
|
|
if (!S_ISREG(st.st_mode))
|
|
error("stat mode");
|
|
if ((st.st_mode & 0777) != 0600)
|
|
error("stat mode2");
|
|
if (st.st_atime != 1001 ||
|
|
st.st_mtime != 1000)
|
|
error("stat time");
|
|
|
|
chk_error(stat(tmpdir, &st));
|
|
if (!S_ISDIR(st.st_mode))
|
|
error("stat mode");
|
|
|
|
/* fstat */
|
|
fd = chk_error(open("file2", O_RDWR));
|
|
chk_error(ftruncate(fd, 50));
|
|
chk_error(fstat(fd, &st));
|
|
chk_error(close(fd));
|
|
|
|
if (st.st_size != 50)
|
|
error("stat size");
|
|
if (!S_ISREG(st.st_mode))
|
|
error("stat mode");
|
|
|
|
/* symlink/lstat */
|
|
chk_error(symlink("file2", "file3"));
|
|
chk_error(lstat("file3", &st));
|
|
if (!S_ISLNK(st.st_mode))
|
|
error("stat mode");
|
|
|
|
/* getdents */
|
|
dir = opendir(tmpdir);
|
|
if (!dir)
|
|
error("opendir");
|
|
len = 0;
|
|
for(;;) {
|
|
de = readdir(dir);
|
|
if (!de)
|
|
break;
|
|
if (strcmp(de->d_name, ".") != 0 &&
|
|
strcmp(de->d_name, "..") != 0 &&
|
|
strcmp(de->d_name, "file2") != 0 &&
|
|
strcmp(de->d_name, "file3") != 0)
|
|
error("readdir");
|
|
len++;
|
|
}
|
|
closedir(dir);
|
|
if (len != 4)
|
|
error("readdir");
|
|
|
|
chk_error(unlink("file3"));
|
|
chk_error(unlink("file2"));
|
|
chk_error(chdir(cur_dir));
|
|
chk_error(rmdir(tmpdir));
|
|
}
|
|
|
|
static void test_fork(void)
|
|
{
|
|
int pid, status;
|
|
|
|
pid = chk_error(fork());
|
|
if (pid == 0) {
|
|
/* child */
|
|
sleep(2);
|
|
exit(2);
|
|
}
|
|
chk_error(waitpid(pid, &status, 0));
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 2)
|
|
error("waitpid status=0x%x", status);
|
|
}
|
|
|
|
static void test_time(void)
|
|
{
|
|
struct timeval tv, tv2;
|
|
struct timespec ts, rem;
|
|
struct rusage rusg1, rusg2;
|
|
int ti, i;
|
|
|
|
chk_error(gettimeofday(&tv, NULL));
|
|
rem.tv_sec = 1;
|
|
ts.tv_sec = 0;
|
|
ts.tv_nsec = 20 * 1000000;
|
|
chk_error(nanosleep(&ts, &rem));
|
|
if (rem.tv_sec != 1)
|
|
error("nanosleep");
|
|
chk_error(gettimeofday(&tv2, NULL));
|
|
ti = tv2.tv_sec - tv.tv_sec;
|
|
if (ti >= 2)
|
|
error("gettimeofday");
|
|
|
|
chk_error(getrusage(RUSAGE_SELF, &rusg1));
|
|
for(i = 0;i < 10000; i++);
|
|
chk_error(getrusage(RUSAGE_SELF, &rusg2));
|
|
if ((rusg2.ru_utime.tv_sec - rusg1.ru_utime.tv_sec) < 0 ||
|
|
(rusg2.ru_stime.tv_sec - rusg1.ru_stime.tv_sec) < 0)
|
|
error("getrusage");
|
|
}
|
|
|
|
static int server_socket(void)
|
|
{
|
|
int val, fd;
|
|
struct sockaddr_in sockaddr;
|
|
|
|
/* server socket */
|
|
fd = chk_error(socket(PF_INET, SOCK_STREAM, 0));
|
|
|
|
val = 1;
|
|
chk_error(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)));
|
|
|
|
sockaddr.sin_family = AF_INET;
|
|
sockaddr.sin_port = htons(0); /* choose random ephemeral port) */
|
|
sockaddr.sin_addr.s_addr = 0;
|
|
chk_error(bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)));
|
|
chk_error(listen(fd, 0));
|
|
return fd;
|
|
|
|
}
|
|
|
|
static int client_socket(uint16_t port)
|
|
{
|
|
int fd;
|
|
struct sockaddr_in sockaddr;
|
|
|
|
/* server socket */
|
|
fd = chk_error(socket(PF_INET, SOCK_STREAM, 0));
|
|
sockaddr.sin_family = AF_INET;
|
|
sockaddr.sin_port = htons(port);
|
|
inet_aton("127.0.0.1", &sockaddr.sin_addr);
|
|
chk_error(connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)));
|
|
return fd;
|
|
}
|
|
|
|
static const char socket_msg[] = "hello socket\n";
|
|
|
|
static void test_socket(void)
|
|
{
|
|
int server_fd, client_fd, fd, pid, ret, val;
|
|
struct sockaddr_in sockaddr;
|
|
struct sockaddr_in server_addr;
|
|
socklen_t len, socklen;
|
|
uint16_t server_port;
|
|
char buf[512];
|
|
|
|
server_fd = server_socket();
|
|
/* find out what port we got */
|
|
socklen = sizeof(server_addr);
|
|
ret = getsockname(server_fd, &server_addr, &socklen);
|
|
chk_error(ret);
|
|
server_port = ntohs(server_addr.sin_port);
|
|
|
|
/* test a few socket options */
|
|
len = sizeof(val);
|
|
chk_error(getsockopt(server_fd, SOL_SOCKET, SO_TYPE, &val, &len));
|
|
if (val != SOCK_STREAM)
|
|
error("getsockopt");
|
|
|
|
pid = chk_error(fork());
|
|
if (pid == 0) {
|
|
client_fd = client_socket(server_port);
|
|
send(client_fd, socket_msg, sizeof(socket_msg), 0);
|
|
close(client_fd);
|
|
exit(0);
|
|
}
|
|
len = sizeof(sockaddr);
|
|
fd = chk_error(accept(server_fd, (struct sockaddr *)&sockaddr, &len));
|
|
|
|
ret = chk_error(recv(fd, buf, sizeof(buf), 0));
|
|
if (ret != sizeof(socket_msg))
|
|
error("recv");
|
|
if (memcmp(buf, socket_msg, sizeof(socket_msg)) != 0)
|
|
error("socket_msg");
|
|
chk_error(close(fd));
|
|
chk_error(close(server_fd));
|
|
}
|
|
|
|
#define WCOUNT_MAX 512
|
|
|
|
static void test_pipe(void)
|
|
{
|
|
fd_set rfds, wfds;
|
|
int fds[2], fd_max, ret;
|
|
uint8_t ch;
|
|
int wcount, rcount;
|
|
|
|
chk_error(pipe(fds));
|
|
chk_error(fcntl(fds[0], F_SETFL, O_NONBLOCK));
|
|
chk_error(fcntl(fds[1], F_SETFL, O_NONBLOCK));
|
|
wcount = 0;
|
|
rcount = 0;
|
|
for(;;) {
|
|
FD_ZERO(&rfds);
|
|
fd_max = fds[0];
|
|
FD_SET(fds[0], &rfds);
|
|
|
|
FD_ZERO(&wfds);
|
|
FD_SET(fds[1], &wfds);
|
|
if (fds[1] > fd_max)
|
|
fd_max = fds[1];
|
|
|
|
ret = chk_error(select(fd_max + 1, &rfds, &wfds, NULL, NULL));
|
|
if (ret > 0) {
|
|
if (FD_ISSET(fds[0], &rfds)) {
|
|
chk_error(read(fds[0], &ch, 1));
|
|
rcount++;
|
|
if (rcount >= WCOUNT_MAX)
|
|
break;
|
|
}
|
|
if (FD_ISSET(fds[1], &wfds)) {
|
|
ch = 'a';
|
|
chk_error(write(fds[1], &ch, 1));
|
|
wcount++;
|
|
}
|
|
}
|
|
}
|
|
chk_error(close(fds[0]));
|
|
chk_error(close(fds[1]));
|
|
}
|
|
|
|
static int thread1_res;
|
|
static int thread2_res;
|
|
|
|
static int thread1_func(void *arg)
|
|
{
|
|
int i;
|
|
for(i=0;i<5;i++) {
|
|
thread1_res++;
|
|
usleep(10 * 1000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int thread2_func(void *arg)
|
|
{
|
|
int i;
|
|
for(i=0;i<6;i++) {
|
|
thread2_res++;
|
|
usleep(10 * 1000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void wait_for_child(pid_t pid)
|
|
{
|
|
int status;
|
|
chk_error(waitpid(pid, &status, 0));
|
|
}
|
|
|
|
/* For test_clone we must match the clone flags used by glibc, see
|
|
* CLONE_THREAD_FLAGS in the QEMU source code.
|
|
*/
|
|
static void test_clone(void)
|
|
{
|
|
uint8_t *stack1, *stack2;
|
|
pid_t pid1, pid2;
|
|
|
|
stack1 = malloc(STACK_SIZE);
|
|
pid1 = chk_error(clone(thread1_func, stack1 + STACK_SIZE,
|
|
CLONE_VM | CLONE_FS | CLONE_FILES |
|
|
CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM,
|
|
"hello1"));
|
|
|
|
stack2 = malloc(STACK_SIZE);
|
|
pid2 = chk_error(clone(thread2_func, stack2 + STACK_SIZE,
|
|
CLONE_VM | CLONE_FS | CLONE_FILES |
|
|
CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM,
|
|
"hello2"));
|
|
|
|
wait_for_child(pid1);
|
|
free(stack1);
|
|
wait_for_child(pid2);
|
|
free(stack2);
|
|
|
|
if (thread1_res != 5 ||
|
|
thread2_res != 6)
|
|
error("clone");
|
|
}
|
|
|
|
/***********************************/
|
|
|
|
volatile int alarm_count;
|
|
jmp_buf jmp_env;
|
|
|
|
static void sig_alarm(int sig)
|
|
{
|
|
if (sig != SIGALRM)
|
|
error("signal");
|
|
alarm_count++;
|
|
}
|
|
|
|
static void sig_segv(int sig, siginfo_t *info, void *puc)
|
|
{
|
|
if (sig != SIGSEGV)
|
|
error("signal");
|
|
longjmp(jmp_env, 1);
|
|
}
|
|
|
|
static void test_signal(void)
|
|
{
|
|
struct sigaction act;
|
|
struct itimerval it, oit;
|
|
|
|
/* timer test */
|
|
|
|
alarm_count = 0;
|
|
|
|
act.sa_handler = sig_alarm;
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = 0;
|
|
chk_error(sigaction(SIGALRM, &act, NULL));
|
|
|
|
it.it_interval.tv_sec = 0;
|
|
it.it_interval.tv_usec = 10 * 1000;
|
|
it.it_value.tv_sec = 0;
|
|
it.it_value.tv_usec = 10 * 1000;
|
|
chk_error(setitimer(ITIMER_REAL, &it, NULL));
|
|
chk_error(getitimer(ITIMER_REAL, &oit));
|
|
|
|
while (alarm_count < 5) {
|
|
usleep(10 * 1000);
|
|
getitimer(ITIMER_REAL, &oit);
|
|
}
|
|
|
|
it.it_interval.tv_sec = 0;
|
|
it.it_interval.tv_usec = 0;
|
|
it.it_value.tv_sec = 0;
|
|
it.it_value.tv_usec = 0;
|
|
memset(&oit, 0xff, sizeof(oit));
|
|
chk_error(setitimer(ITIMER_REAL, &it, &oit));
|
|
|
|
/* SIGSEGV test */
|
|
act.sa_sigaction = sig_segv;
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = SA_SIGINFO;
|
|
chk_error(sigaction(SIGSEGV, &act, NULL));
|
|
if (setjmp(jmp_env) == 0) {
|
|
*(uint8_t *)0 = 0;
|
|
}
|
|
|
|
act.sa_handler = SIG_DFL;
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = 0;
|
|
chk_error(sigaction(SIGSEGV, &act, NULL));
|
|
}
|
|
|
|
#define SHM_SIZE 32768
|
|
|
|
static void test_shm(void)
|
|
{
|
|
void *ptr;
|
|
int shmid;
|
|
|
|
shmid = chk_error(shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0777));
|
|
ptr = shmat(shmid, NULL, 0);
|
|
if (!ptr)
|
|
error("shmat");
|
|
|
|
memset(ptr, 0, SHM_SIZE);
|
|
|
|
chk_error(shmctl(shmid, IPC_RMID, 0));
|
|
chk_error(shmdt(ptr));
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
test_file();
|
|
test_pipe();
|
|
test_fork();
|
|
test_time();
|
|
test_socket();
|
|
|
|
if (argc > 1) {
|
|
printf("test_clone still considered buggy\n");
|
|
test_clone();
|
|
}
|
|
|
|
test_signal();
|
|
test_shm();
|
|
return 0;
|
|
}
|