397 lines
8.2 KiB
C
397 lines
8.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2017 Red Hat Inc. All Rights Reserved.
|
|
* Author: Xiong Zhou <xzhou@redhat.com>
|
|
*
|
|
* This is testing OFD locks racing with POSIX locks:
|
|
*
|
|
* OFD read lock vs OFD write lock
|
|
* OFD read lock vs POSIX write lock
|
|
* OFD write lock vs POSIX write lock
|
|
* OFD write lock vs POSIX read lock
|
|
* OFD write lock vs OFD write lock
|
|
*
|
|
* OFD r/w locks vs POSIX write locks
|
|
* OFD r/w locks vs POSIX read locks
|
|
*
|
|
* For example:
|
|
*
|
|
* Init an file with preset values.
|
|
*
|
|
* Threads acquire OFD READ locks to read a 4k section start from 0;
|
|
* checking data read back, there should not be any surprise
|
|
* values and data should be consistent in a 1k block.
|
|
*
|
|
* Threads acquire OFD WRITE locks to write a 4k section start from 1k,
|
|
* writing different values in different threads.
|
|
*
|
|
* Check file data after racing, there should not be any surprise values
|
|
* and data should be consistent in a 1k block.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <errno.h>
|
|
|
|
#include "lapi/fcntl.h"
|
|
#include "tst_safe_pthread.h"
|
|
#include "tst_test.h"
|
|
#include "fcntl_common.h"
|
|
|
|
static int thread_cnt;
|
|
static int fail_flag = 0;
|
|
static volatile int loop_flag = 1;
|
|
static const int max_thread_cnt = 32;
|
|
static const char fname[] = "tst_ofd_posix_locks";
|
|
static const long write_size = 4096;
|
|
static pthread_barrier_t barrier;
|
|
|
|
struct param {
|
|
long offset;
|
|
long length;
|
|
long cnt;
|
|
};
|
|
|
|
static void setup(void)
|
|
{
|
|
thread_cnt = tst_ncpus_conf() * 3;
|
|
if (thread_cnt > max_thread_cnt)
|
|
thread_cnt = max_thread_cnt;
|
|
}
|
|
|
|
/* OFD write lock writing data*/
|
|
static void *fn_ofd_w(void *arg)
|
|
{
|
|
struct param *pa = arg;
|
|
unsigned char buf[pa->length];
|
|
int fd = SAFE_OPEN(fname, O_RDWR);
|
|
long wt = pa->cnt;
|
|
|
|
struct flock64 lck = {
|
|
.l_whence = SEEK_SET,
|
|
.l_start = pa->offset,
|
|
.l_len = pa->length,
|
|
.l_pid = 0,
|
|
};
|
|
|
|
do {
|
|
|
|
memset(buf, wt, pa->length);
|
|
|
|
lck.l_type = F_WRLCK;
|
|
my_fcntl(fd, F_OFD_SETLKW, &lck);
|
|
|
|
SAFE_LSEEK(fd, pa->offset, SEEK_SET);
|
|
SAFE_WRITE(1, fd, buf, pa->length);
|
|
|
|
lck.l_type = F_UNLCK;
|
|
my_fcntl(fd, F_OFD_SETLKW, &lck);
|
|
|
|
wt++;
|
|
if (wt >= 255)
|
|
wt = pa->cnt;
|
|
|
|
sched_yield();
|
|
} while (loop_flag);
|
|
|
|
pthread_barrier_wait(&barrier);
|
|
SAFE_CLOSE(fd);
|
|
return NULL;
|
|
}
|
|
|
|
/* POSIX write lock writing data*/
|
|
static void *fn_posix_w(void *arg)
|
|
{
|
|
struct param *pa = arg;
|
|
unsigned char buf[pa->length];
|
|
int fd = SAFE_OPEN(fname, O_RDWR);
|
|
long wt = pa->cnt;
|
|
|
|
struct flock lck = {
|
|
.l_whence = SEEK_SET,
|
|
.l_start = pa->offset,
|
|
.l_len = pa->length,
|
|
};
|
|
|
|
do {
|
|
|
|
memset(buf, wt, pa->length);
|
|
|
|
lck.l_type = F_WRLCK;
|
|
SAFE_FCNTL(fd, F_SETLKW, &lck);
|
|
|
|
SAFE_LSEEK(fd, pa->offset, SEEK_SET);
|
|
SAFE_WRITE(1, fd, buf, pa->length);
|
|
|
|
lck.l_type = F_UNLCK;
|
|
SAFE_FCNTL(fd, F_SETLKW, &lck);
|
|
|
|
wt++;
|
|
if (wt >= 255)
|
|
wt = pa->cnt;
|
|
|
|
sched_yield();
|
|
} while (loop_flag);
|
|
|
|
pthread_barrier_wait(&barrier);
|
|
SAFE_CLOSE(fd);
|
|
return NULL;
|
|
}
|
|
|
|
/* OFD read lock reading data*/
|
|
static void *fn_ofd_r(void *arg)
|
|
{
|
|
struct param *pa = arg;
|
|
unsigned char buf[pa->length];
|
|
int i;
|
|
int fd = SAFE_OPEN(fname, O_RDWR);
|
|
|
|
struct flock64 lck = {
|
|
.l_whence = SEEK_SET,
|
|
.l_start = pa->offset,
|
|
.l_len = pa->length,
|
|
.l_pid = 0,
|
|
};
|
|
|
|
while (loop_flag) {
|
|
|
|
memset(buf, 0, pa->length);
|
|
|
|
lck.l_type = F_RDLCK;
|
|
my_fcntl(fd, F_OFD_SETLKW, &lck);
|
|
|
|
/* rlock acquired */
|
|
SAFE_LSEEK(fd, pa->offset, SEEK_SET);
|
|
SAFE_READ(1, fd, buf, pa->length);
|
|
|
|
/* Verifying data read */
|
|
for (i = 0; i < pa->length; i++) {
|
|
|
|
if (buf[i] < 1 || buf[i] > 254) {
|
|
|
|
tst_res(TFAIL, "Unexpected data "
|
|
"offset %ld value %d",
|
|
pa->offset + i, buf[i]);
|
|
fail_flag = 1;
|
|
break;
|
|
}
|
|
|
|
int j = (i / (pa->length/4)) * pa->length/4;
|
|
|
|
if (buf[i] != buf[j]) {
|
|
|
|
tst_res(TFAIL, "Unexpected data "
|
|
"offset %ld value %d",
|
|
pa->offset + i, buf[i]);
|
|
fail_flag = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
lck.l_type = F_UNLCK;
|
|
my_fcntl(fd, F_OFD_SETLK, &lck);
|
|
|
|
sched_yield();
|
|
}
|
|
|
|
pthread_barrier_wait(&barrier);
|
|
SAFE_CLOSE(fd);
|
|
return NULL;
|
|
}
|
|
|
|
/* POSIX read lock reading data */
|
|
static void *fn_posix_r(void *arg)
|
|
{
|
|
struct param *pa = arg;
|
|
unsigned char buf[pa->length];
|
|
int i;
|
|
int fd = SAFE_OPEN(fname, O_RDWR);
|
|
|
|
struct flock lck = {
|
|
.l_whence = SEEK_SET,
|
|
.l_start = pa->offset,
|
|
.l_len = pa->length,
|
|
};
|
|
|
|
while (loop_flag) {
|
|
|
|
memset(buf, 0, pa->length);
|
|
|
|
lck.l_type = F_RDLCK;
|
|
SAFE_FCNTL(fd, F_SETLKW, &lck);
|
|
|
|
/* rlock acquired */
|
|
SAFE_LSEEK(fd, pa->offset, SEEK_SET);
|
|
SAFE_READ(1, fd, buf, pa->length);
|
|
|
|
/* Verifying data read */
|
|
for (i = 0; i < pa->length; i++) {
|
|
|
|
if (buf[i] < 1 || buf[i] > 254) {
|
|
|
|
tst_res(TFAIL, "Unexpected data "
|
|
"offset %ld value %d",
|
|
pa->offset + i, buf[i]);
|
|
fail_flag = 1;
|
|
break;
|
|
}
|
|
|
|
int j = (i / (pa->length/4)) * pa->length/4;
|
|
|
|
if (buf[i] != buf[j]) {
|
|
|
|
tst_res(TFAIL, "Unexpected data "
|
|
"offset %ld value %d",
|
|
pa->offset + i, buf[i]);
|
|
fail_flag = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
lck.l_type = F_UNLCK;
|
|
SAFE_FCNTL(fd, F_SETLK, &lck);
|
|
|
|
sched_yield();
|
|
}
|
|
|
|
pthread_barrier_wait(&barrier);
|
|
SAFE_CLOSE(fd);
|
|
return NULL;
|
|
}
|
|
|
|
static void *fn_dummy(void *arg)
|
|
{
|
|
arg = NULL;
|
|
|
|
pthread_barrier_wait(&barrier);
|
|
return arg;
|
|
}
|
|
|
|
/* Test different functions and verify data */
|
|
static void test_fn(void *f0(void *), void *f1(void *),
|
|
void *f2(void *), const char *msg)
|
|
{
|
|
int i, k, fd;
|
|
pthread_t id0[thread_cnt];
|
|
pthread_t id1[thread_cnt];
|
|
pthread_t id2[thread_cnt];
|
|
struct param p0[thread_cnt];
|
|
struct param p1[thread_cnt];
|
|
struct param p2[thread_cnt];
|
|
unsigned char buf[write_size];
|
|
|
|
tst_res(TINFO, "%s", msg);
|
|
|
|
if (tst_fill_file(fname, 1, write_size, thread_cnt + 1))
|
|
tst_brk(TBROK, "Failed to create tst file");
|
|
|
|
if (pthread_barrier_init(&barrier, NULL, thread_cnt*3) != 0)
|
|
tst_brk(TBROK, "Failed to init pthread barrier");
|
|
|
|
for (i = 0; i < thread_cnt; i++) {
|
|
|
|
p0[i].offset = i * write_size;
|
|
p0[i].length = write_size;
|
|
p0[i].cnt = i + 2;
|
|
|
|
p1[i].offset = i * write_size + write_size / 4;
|
|
p1[i].length = write_size;
|
|
p1[i].cnt = i + 2;
|
|
|
|
p2[i].offset = i * write_size + write_size / 2;
|
|
p2[i].length = write_size;
|
|
p2[i].cnt = i + 2;
|
|
}
|
|
|
|
fail_flag = 0;
|
|
loop_flag = 1;
|
|
|
|
for (i = 0; i < thread_cnt; i++) {
|
|
|
|
SAFE_PTHREAD_CREATE(id0 + i, NULL, f0, (void *)&p0[i]);
|
|
SAFE_PTHREAD_CREATE(id1 + i, NULL, f1, (void *)&p1[i]);
|
|
SAFE_PTHREAD_CREATE(id2 + i, NULL, f2, (void *)&p2[i]);
|
|
}
|
|
|
|
sleep(1);
|
|
loop_flag = 0;
|
|
|
|
for (i = 0; i < thread_cnt; i++) {
|
|
|
|
SAFE_PTHREAD_JOIN(id0[i], NULL);
|
|
SAFE_PTHREAD_JOIN(id1[i], NULL);
|
|
SAFE_PTHREAD_JOIN(id2[i], NULL);
|
|
}
|
|
|
|
fd = SAFE_OPEN(fname, O_RDONLY);
|
|
|
|
for (i = 0; i < thread_cnt * 4; i++) {
|
|
|
|
SAFE_READ(1, fd, buf, write_size/4);
|
|
|
|
for (k = 0; k < write_size/4; k++) {
|
|
|
|
if (buf[k] < 2 || buf[k] > 254) {
|
|
|
|
if (i < 3 && buf[k] == 1)
|
|
continue;
|
|
tst_res(TFAIL, "Unexpected data "
|
|
"offset %ld value %d",
|
|
i * write_size / 4 + k, buf[k]);
|
|
SAFE_CLOSE(fd);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (k = 1; k < write_size/4; k++) {
|
|
|
|
if (buf[k] != buf[0]) {
|
|
tst_res(TFAIL, "Unexpected block read");
|
|
SAFE_CLOSE(fd);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pthread_barrier_destroy(&barrier) != 0)
|
|
tst_brk(TBROK, "Failed to destroy pthread barrier");
|
|
|
|
SAFE_CLOSE(fd);
|
|
if (fail_flag == 0)
|
|
tst_res(TPASS, "Access between threads synchronized");
|
|
}
|
|
|
|
static struct tcase {
|
|
void *(*fn0)(void *);
|
|
void *(*fn1)(void *);
|
|
void *(*fn2)(void *);
|
|
const char *desc;
|
|
} tcases[] = {
|
|
{fn_ofd_r, fn_ofd_w, fn_dummy, "OFD read lock vs OFD write lock"},
|
|
{fn_ofd_w, fn_posix_w, fn_dummy, "OFD write lock vs POSIX write lock"},
|
|
{fn_ofd_r, fn_posix_w, fn_dummy, "OFD read lock vs POSIX write lock"},
|
|
{fn_ofd_w, fn_posix_r, fn_dummy, "OFD write lock vs POSIX read lock"},
|
|
{fn_ofd_w, fn_ofd_w, fn_dummy, "OFD write lock vs OFD write lock"},
|
|
{fn_ofd_r, fn_ofd_w, fn_posix_w, "OFD r/w lock vs POSIX write lock"},
|
|
{fn_ofd_r, fn_ofd_w, fn_posix_r, "OFD r/w lock vs POSIX read lock"},
|
|
};
|
|
|
|
static void tests(unsigned int i)
|
|
{
|
|
test_fn(tcases[i].fn0, tcases[i].fn1, tcases[i].fn2, tcases[i].desc);
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.min_kver = "3.15",
|
|
.needs_tmpdir = 1,
|
|
.test = tests,
|
|
.tcnt = ARRAY_SIZE(tcases),
|
|
.setup = setup
|
|
};
|