mirror of
https://github.com/SCST-project/scst.git
synced 2026-10-22 14:12:33 +00:00
kunit: Test event import, delivery and timeout accounting
Move the existing client layout into a private header and link separate suites with shared fixtures. Add task-scoped usercopy and allocation boundaries around the actual ioctl and timeout paths. Use private clients without scheduling work or accessing userspace memory. Preserve payload validation and counter behavior; the delivered-timeout assertion exposes the existing double decrement. Include the private event headers needed by ordinary in-tree builds in generated kernel patches.
This commit is contained in:
+1
-1
@@ -60,7 +60,7 @@ validates the selection against the supplied `.ko` files.
|
||||
|
||||
| Module | Labels |
|
||||
| --- | --- |
|
||||
| `scst.ko` | `ALUA CDB LIB ORDER UNMAP` |
|
||||
| `scst.ko` | `ALUA CDB EVENT LIB ORDER UNMAP` |
|
||||
|
||||
The runner reads suite metadata from the modules and loads selected owners
|
||||
with their dependencies. Unselected suites are not counted as skipped.
|
||||
|
||||
@@ -309,8 +309,9 @@ scst_03_public_headers="scst/include/scst.h scst/include/scst_const.h \
|
||||
scst/include/scst_event.h scst/include/backport.h"
|
||||
scst_04_main="scst/src/scst_main.c scst/src/scst_priv.h \
|
||||
scst/src/scst_copy_mgr.c scst/src/scst_dlm.c scst/src/scst_dlm.h \
|
||||
scst/src/scst_event.c scst/src/scst_no_dlm.c \
|
||||
scst/src/scst_test.h"
|
||||
scst/src/scst_event.c scst/src/scst_event_priv.h scst/src/scst_no_dlm.c \
|
||||
scst/src/scst_test.h \
|
||||
scst/src/scst_event_test.h"
|
||||
scst_05_targ="scst/src/scst_targ.c scst/src/scst_local_cmd.c \
|
||||
scst/src/scst_local_cmd.h \
|
||||
scst/src/scst_targ_test.h"
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include "scst_event_fixture.h"
|
||||
|
||||
void __user * const event_test_arg = (void __user *)0x1000UL;
|
||||
|
||||
static int event_test_get_size(s32 *size, const void __user *arg)
|
||||
{
|
||||
struct kunit *test = kunit_get_current_test();
|
||||
struct event_test_fixture *f = test->priv;
|
||||
|
||||
KUNIT_EXPECT_PTR_EQ(test, arg, event_test_arg);
|
||||
f->reads++;
|
||||
if (!f->read_error)
|
||||
*size = f->user_size;
|
||||
return f->read_error;
|
||||
}
|
||||
|
||||
static int event_test_get_payload_size(u32 *size, struct scst_event_user __user *arg)
|
||||
{
|
||||
s32 value;
|
||||
int res = event_test_get_size(&value, arg);
|
||||
|
||||
if (!res)
|
||||
*size = value;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int event_test_put_size(s32 size, void __user *arg)
|
||||
{
|
||||
struct kunit *test = kunit_get_current_test();
|
||||
struct event_test_fixture *f = test->priv;
|
||||
|
||||
KUNIT_EXPECT_PTR_EQ(test, arg, event_test_arg);
|
||||
f->puts++;
|
||||
f->needed_size = size;
|
||||
return f->put_error;
|
||||
}
|
||||
|
||||
static struct scst_event_entry *event_test_alloc(size_t size)
|
||||
{
|
||||
struct event_test_fixture *f = kunit_get_current_test()->priv;
|
||||
|
||||
f->allocations++;
|
||||
f->allocation_size = size;
|
||||
f->allocation = f->fail_alloc ? NULL : kzalloc(size, GFP_KERNEL);
|
||||
return f->allocation;
|
||||
}
|
||||
|
||||
static unsigned long event_test_copy_from(void *to, const void __user *from, size_t size)
|
||||
{
|
||||
struct kunit *test = kunit_get_current_test();
|
||||
struct event_test_fixture *f = test->priv;
|
||||
size_t left = min(f->from_left, size);
|
||||
|
||||
KUNIT_EXPECT_PTR_EQ(test, from, event_test_arg);
|
||||
f->imports++;
|
||||
f->imported = size;
|
||||
/* Reject the historical entry-size copy before it can overrun the fixture. */
|
||||
if (size != f->input_size || size > sizeof(f->input)) {
|
||||
KUNIT_FAIL(test, "incorrect usercopy size: %zu, expected %zu", size, f->input_size);
|
||||
return size;
|
||||
}
|
||||
if (f->importing &&
|
||||
(!f->allocation || to != &f->allocation->event ||
|
||||
size + offsetof(struct scst_event_entry, event) > f->allocation_size)) {
|
||||
KUNIT_FAIL(test, "import does not fit the allocated event member");
|
||||
return size;
|
||||
}
|
||||
memcpy(to, f->input, size - left);
|
||||
/* copy_from_user() zeroes the uncopied tail. */
|
||||
memset((u8 *)to + size - left, 0, left);
|
||||
return left;
|
||||
}
|
||||
|
||||
static unsigned long event_test_copy_to(void __user *to, const void *from, size_t size)
|
||||
{
|
||||
struct kunit *test = kunit_get_current_test();
|
||||
struct event_test_fixture *f = test->priv;
|
||||
size_t offset = offsetof(struct scst_event_user, out_event);
|
||||
size_t left = min(f->to_left, size);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, (unsigned long)to, (unsigned long)event_test_arg + offset);
|
||||
f->exports++;
|
||||
f->exported = size;
|
||||
if (size > sizeof(struct scst_event) + SCST_MAX_PAYLOAD) {
|
||||
KUNIT_FAIL(test, "oversized event export: %zu", size);
|
||||
return size;
|
||||
}
|
||||
memcpy(f->output + offset, from, size - left);
|
||||
return left;
|
||||
}
|
||||
|
||||
static void event_test_notify(struct scst_event *event, void *data, int status)
|
||||
{
|
||||
struct event_test_fixture *f = data;
|
||||
|
||||
lockdep_assert_not_held(&scst_event_mutex);
|
||||
f->notifications++;
|
||||
f->notify_status = status;
|
||||
f->notify_id = event->event_id;
|
||||
}
|
||||
|
||||
static void event_test_fill(struct scst_event *event, unsigned int length, u32 code,
|
||||
const char *issuer, u32 id)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
event->payload_len = length;
|
||||
event->event_code = code;
|
||||
event->event_id = id;
|
||||
strscpy(event->issuer_name, issuer, sizeof(event->issuer_name));
|
||||
for (i = 0; i < length; i++)
|
||||
event->payload[i] = i;
|
||||
}
|
||||
|
||||
struct scst_event *event_test_input(struct event_test_fixture *f, unsigned int length,
|
||||
u32 code, const char *issuer)
|
||||
{
|
||||
struct scst_event *event = (void *)f->input;
|
||||
|
||||
memset(f->input, 0, sizeof(f->input));
|
||||
event_test_fill(event, length, code, issuer, 77);
|
||||
f->user_size = length;
|
||||
f->input_size = sizeof(*event) + length;
|
||||
f->importing = true;
|
||||
return event;
|
||||
}
|
||||
|
||||
long event_test_ioctl(struct event_test_fixture *f, unsigned int command)
|
||||
{
|
||||
return scst_event_ioctl(&f->file, command, (unsigned long)event_test_arg);
|
||||
}
|
||||
|
||||
struct scst_event_entry *event_test_queue(struct event_test_fixture *f,
|
||||
unsigned int length, u32 id, bool notify)
|
||||
{
|
||||
struct scst_event_entry *e = kzalloc(sizeof(*e) + length, GFP_KERNEL);
|
||||
|
||||
if (!e)
|
||||
return NULL;
|
||||
event_test_fill(&e->event, length, 1, "kunit", id);
|
||||
INIT_DELAYED_WORK(&e->event_timeout_work, scst_event_timeout_fn);
|
||||
if (notify) {
|
||||
e->event_notify_fn = event_test_notify;
|
||||
e->notify_fn_priv = f;
|
||||
}
|
||||
e->pqueued_events_cnt = &f->priv.queued_events_cnt;
|
||||
list_add_tail(&e->events_list_entry, &f->priv.queued_events_list);
|
||||
f->priv.queued_events_cnt++;
|
||||
return e;
|
||||
}
|
||||
|
||||
void event_test_ack_input(struct event_test_fixture *f, u32 id, int status)
|
||||
{
|
||||
struct scst_event_notify_done n = { .event_id = id, .status = status };
|
||||
|
||||
memcpy(f->input, &n, sizeof(n));
|
||||
f->input_size = sizeof(n);
|
||||
f->importing = false;
|
||||
}
|
||||
|
||||
unsigned int event_test_list_count(struct list_head *head)
|
||||
{
|
||||
struct list_head *pos;
|
||||
unsigned int count = 0;
|
||||
|
||||
list_for_each(pos, head)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
int event_test_init(struct kunit *test)
|
||||
{
|
||||
struct event_test_fixture *f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL);
|
||||
|
||||
test->priv = f;
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
INIT_LIST_HEAD(&f->priv.privs_list_entry);
|
||||
INIT_LIST_HEAD(&f->priv.allowed_events_list);
|
||||
INIT_LIST_HEAD(&f->priv.queued_events_list);
|
||||
INIT_LIST_HEAD(&f->priv.processing_events_list);
|
||||
init_waitqueue_head(&f->priv.queued_events_waitQ);
|
||||
f->file.private_data = &f->priv;
|
||||
f->user_size = INT_MAX;
|
||||
memset(f->output, 0xa5, sizeof(f->output));
|
||||
kunit_activate_static_stub(test, scst_event_get_user_payload_size,
|
||||
event_test_get_payload_size);
|
||||
kunit_activate_static_stub(test, scst_event_get_user_size, event_test_get_size);
|
||||
kunit_activate_static_stub(test, scst_event_put_user_size, event_test_put_size);
|
||||
kunit_activate_static_stub(test, scst_event_alloc_user_entry, event_test_alloc);
|
||||
kunit_activate_static_stub(test, scst_event_copy_from_user, event_test_copy_from);
|
||||
kunit_activate_static_stub(test, scst_event_copy_to_user, event_test_copy_to);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void event_test_free_list(struct list_head *head, bool work_initialized)
|
||||
{
|
||||
struct scst_event_entry *e, *next;
|
||||
|
||||
list_for_each_entry_safe(e, next, head, events_list_entry) {
|
||||
list_del(&e->events_list_entry);
|
||||
if (work_initialized)
|
||||
cancel_delayed_work_sync(&e->event_timeout_work);
|
||||
kfree(e);
|
||||
}
|
||||
}
|
||||
|
||||
void event_test_exit(struct kunit *test)
|
||||
{
|
||||
struct event_test_fixture *f = test->priv;
|
||||
|
||||
if (!f)
|
||||
return;
|
||||
/* No entry or client is registered globally, and no work is scheduled. */
|
||||
event_test_free_list(&f->priv.allowed_events_list, false);
|
||||
event_test_free_list(&f->priv.queued_events_list, true);
|
||||
event_test_free_list(&f->priv.processing_events_list, true);
|
||||
kfree(f->detached);
|
||||
test->priv = NULL;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef SCST_EVENT_FIXTURE_H
|
||||
#define SCST_EVENT_FIXTURE_H
|
||||
|
||||
#include "../scst_kunit.h"
|
||||
|
||||
#ifdef INSIDE_KERNEL_TREE
|
||||
#include <scst/scst.h>
|
||||
#else
|
||||
#include "scst.h"
|
||||
#endif
|
||||
#include "../../scst_priv.h"
|
||||
#include <linux/fs.h>
|
||||
#include <linux/limits.h>
|
||||
#ifdef INSIDE_KERNEL_TREE
|
||||
#include <scst/scst_event.h>
|
||||
#else
|
||||
#include "scst_event.h"
|
||||
#endif
|
||||
#include "../../scst_event_priv.h"
|
||||
#include "../../scst_event_test.h"
|
||||
|
||||
struct event_test_fixture {
|
||||
struct file file;
|
||||
struct scst_event_priv priv;
|
||||
u8 input[sizeof(struct scst_event) + SCST_MAX_PAYLOAD];
|
||||
u8 output[sizeof(struct scst_event_user) + SCST_MAX_PAYLOAD + 16];
|
||||
size_t input_size;
|
||||
s32 user_size;
|
||||
s32 needed_size;
|
||||
int read_error;
|
||||
int put_error;
|
||||
unsigned int reads;
|
||||
unsigned int puts;
|
||||
unsigned int imports;
|
||||
unsigned int exports;
|
||||
unsigned int allocations;
|
||||
size_t from_left;
|
||||
size_t to_left;
|
||||
size_t imported;
|
||||
size_t exported;
|
||||
size_t allocation_size;
|
||||
struct scst_event_entry *allocation;
|
||||
struct scst_event_entry *detached;
|
||||
bool fail_alloc;
|
||||
bool importing;
|
||||
unsigned int notifications;
|
||||
int notify_status;
|
||||
u32 notify_id;
|
||||
};
|
||||
|
||||
/* Stubs interpret this token; it is never dereferenced as a userspace address. */
|
||||
|
||||
extern void __user * const event_test_arg;
|
||||
struct scst_event *event_test_input(struct event_test_fixture *f, unsigned int length,
|
||||
u32 code, const char *issuer);
|
||||
long event_test_ioctl(struct event_test_fixture *f, unsigned int command);
|
||||
struct scst_event_entry *event_test_queue(struct event_test_fixture *f,
|
||||
unsigned int length, u32 id, bool notify);
|
||||
void event_test_ack_input(struct event_test_fixture *f, u32 id, int status);
|
||||
unsigned int event_test_list_count(struct list_head *head);
|
||||
int event_test_init(struct kunit *test);
|
||||
void event_test_exit(struct kunit *test);
|
||||
|
||||
#endif /* SCST_EVENT_FIXTURE_H */
|
||||
@@ -0,0 +1,279 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include "../scst_event_fixture.h"
|
||||
/* Event ioctl ownership and error paths, without registering an event client. */
|
||||
|
||||
struct event_delivery_case {
|
||||
const char *name;
|
||||
unsigned int length;
|
||||
int delta;
|
||||
int result;
|
||||
size_t uncopied;
|
||||
bool large_buffer;
|
||||
bool zero_buffer;
|
||||
bool negative_buffer;
|
||||
bool read_fault;
|
||||
bool put_fault;
|
||||
bool notify;
|
||||
bool empty;
|
||||
bool exiting;
|
||||
};
|
||||
|
||||
static const struct event_delivery_case event_delivery_cases[] = {
|
||||
{ .name = "empty_payload_exact_buffer" },
|
||||
{ .name = "one_byte_exact_buffer", .length = 1 },
|
||||
{ .name = "binary_payload", .length = 256 },
|
||||
{ .name = "maximum_payload", .length = SCST_MAX_PAYLOAD },
|
||||
{ .name = "larger_buffer", .length = 3, .delta = 1 },
|
||||
{ .name = "int_max_buffer", .length = 3, .large_buffer = true },
|
||||
{ .name = "zero_buffer", .zero_buffer = true, .result = -ENOSPC },
|
||||
{ .name = "negative_buffer", .negative_buffer = true, .result = -ENOSPC },
|
||||
{ .name = "short_header", .delta = -1, .result = -ENOSPC },
|
||||
{ .name = "short_payload", .length = SCST_MAX_PAYLOAD, .delta = -1,
|
||||
.result = -ENOSPC },
|
||||
{ .name = "needed_size_write_fault", .delta = -1, .put_fault = true,
|
||||
.result = -EFAULT },
|
||||
{ .name = "capacity_read_fault", .read_fault = true, .result = -EFAULT },
|
||||
{ .name = "full_copy_fault", .uncopied = SIZE_MAX, .result = -EFAULT },
|
||||
{ .name = "last_header_byte_fault", .uncopied = 1, .result = -EFAULT },
|
||||
{ .name = "partial_payload_fault", .length = 256, .uncopied = 128,
|
||||
.result = -EFAULT },
|
||||
{ .name = "notify_moves_to_processing", .length = 3, .notify = true },
|
||||
{ .name = "notify_copy_fault_stays_queued", .length = 3, .notify = true,
|
||||
.uncopied = 1, .result = -EFAULT },
|
||||
{ .name = "notify_short_buffer_stays_queued", .length = 3, .notify = true,
|
||||
.delta = -1, .result = -ENOSPC },
|
||||
{ .name = "empty_nonblocking_queue", .empty = true, .result = -EAGAIN },
|
||||
{ .name = "client_exiting", .empty = true, .exiting = true, .result = -EINTR },
|
||||
};
|
||||
|
||||
static void event_delivery_desc(const struct event_delivery_case *p, char *desc)
|
||||
{
|
||||
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(event_delivery, event_delivery_cases, event_delivery_desc);
|
||||
|
||||
static void event_delivery_test(struct kunit *test)
|
||||
{
|
||||
const struct event_delivery_case *p = test->param_value;
|
||||
struct event_test_fixture *f = test->priv;
|
||||
struct scst_event_entry *e;
|
||||
size_t needed = sizeof(struct scst_event) + p->length;
|
||||
size_t offset = offsetof(struct scst_event_user, out_event);
|
||||
bool short_buffer, copied;
|
||||
unsigned int queued, processing;
|
||||
size_t i, bytes;
|
||||
|
||||
if (!p->empty) {
|
||||
e = event_test_queue(f, p->length, 42, p->notify);
|
||||
KUNIT_ASSERT_NOT_NULL(test, e);
|
||||
memcpy(f->input, &e->event, needed);
|
||||
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 0, 43, false));
|
||||
}
|
||||
f->user_size = needed + p->delta;
|
||||
if (p->large_buffer)
|
||||
f->user_size = INT_MAX;
|
||||
if (p->zero_buffer)
|
||||
f->user_size = 0;
|
||||
if (p->negative_buffer)
|
||||
f->user_size = -1;
|
||||
f->read_error = p->read_fault ? -EFAULT : 0;
|
||||
f->put_error = p->put_fault ? -EFAULT : 0;
|
||||
f->to_left = p->uncopied;
|
||||
f->priv.going_to_exit = p->exiting;
|
||||
short_buffer = !p->empty && !p->read_fault && (int)needed > f->user_size;
|
||||
copied = !p->empty && !p->read_fault && !short_buffer;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)p->result);
|
||||
KUNIT_EXPECT_EQ(test, f->reads, 1U);
|
||||
KUNIT_EXPECT_EQ(test, f->puts, (unsigned int)short_buffer);
|
||||
KUNIT_EXPECT_EQ(test, f->exports, (unsigned int)copied);
|
||||
KUNIT_EXPECT_EQ(test, f->notifications, 0U);
|
||||
if (short_buffer)
|
||||
KUNIT_EXPECT_EQ(test, f->needed_size, (s32)needed);
|
||||
bytes = copied ? needed - min(needed, p->uncopied) : 0;
|
||||
if (copied) {
|
||||
KUNIT_EXPECT_EQ(test, f->exported, needed);
|
||||
KUNIT_EXPECT_EQ(test, memcmp(f->output + offset, f->input, bytes), 0);
|
||||
}
|
||||
/* Neither the outer size field nor bytes after the event are exported. */
|
||||
for (i = 0; i < offset; i++)
|
||||
KUNIT_EXPECT_EQ(test, f->output[i], (u8)0xa5);
|
||||
KUNIT_EXPECT_NULL(test, memchr_inv(f->output + offset + bytes, 0xa5,
|
||||
sizeof(f->output) - offset - bytes));
|
||||
queued = p->empty ? 0 : p->result ? 2 : 1;
|
||||
processing = p->notify && !p->result;
|
||||
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, (int)queued);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.queued_events_list), queued);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.processing_events_list), processing);
|
||||
if (queued) {
|
||||
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.queued_events_list));
|
||||
e = list_first_entry(&f->priv.queued_events_list, struct scst_event_entry,
|
||||
events_list_entry);
|
||||
KUNIT_EXPECT_EQ(test, e->event.event_id, p->result ? 42U : 43U);
|
||||
}
|
||||
if (processing) {
|
||||
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.processing_events_list));
|
||||
e = list_first_entry(&f->priv.processing_events_list, struct scst_event_entry,
|
||||
events_list_entry);
|
||||
KUNIT_EXPECT_EQ(test, e->event.event_id, 42U);
|
||||
}
|
||||
}
|
||||
|
||||
static void event_delivery_retry_test(struct kunit *test)
|
||||
{
|
||||
struct event_test_fixture *f = test->priv;
|
||||
struct scst_event_user *output = (void *)f->output;
|
||||
|
||||
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 256, 42, false));
|
||||
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 0, 43, false));
|
||||
f->user_size = 0;
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)-ENOSPC);
|
||||
KUNIT_EXPECT_EQ(test, f->needed_size, (s32)(sizeof(struct scst_event) + 256));
|
||||
f->user_size = f->needed_size;
|
||||
f->to_left = 1;
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)-EFAULT);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, 2);
|
||||
f->to_left = 0;
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
|
||||
KUNIT_EXPECT_EQ(test, output->out_event.event_id, 42U);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, 1);
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
|
||||
KUNIT_EXPECT_EQ(test, output->out_event.event_id, 43U);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, 0);
|
||||
KUNIT_EXPECT_TRUE(test, list_empty(&f->priv.queued_events_list));
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)-EAGAIN);
|
||||
}
|
||||
|
||||
struct event_ack_case {
|
||||
const char *name;
|
||||
int status;
|
||||
int result;
|
||||
size_t uncopied;
|
||||
bool unknown;
|
||||
bool queued;
|
||||
bool repeat;
|
||||
};
|
||||
|
||||
static const struct event_ack_case event_ack_cases[] = {
|
||||
{ .name = "successful_notification" },
|
||||
{ .name = "negative_status", .status = -EIO },
|
||||
{ .name = "positive_status", .status = 1 },
|
||||
{ .name = "unknown_id", .unknown = true, .result = -ENOENT },
|
||||
{ .name = "queued_event_is_not_delivered", .queued = true, .result = -ENOENT },
|
||||
{ .name = "input_copy_fault", .uncopied = SIZE_MAX, .result = -EFAULT },
|
||||
{ .name = "partial_status_copy_fault", .uncopied = 1, .result = -EFAULT },
|
||||
{ .name = "acknowledge_once", .repeat = true },
|
||||
};
|
||||
|
||||
static void event_ack_desc(const struct event_ack_case *p, char *desc)
|
||||
{
|
||||
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(event_ack, event_ack_cases, event_ack_desc);
|
||||
|
||||
static void event_ack_test(struct kunit *test)
|
||||
{
|
||||
const struct event_ack_case *p = test->param_value;
|
||||
struct event_test_fixture *f = test->priv;
|
||||
struct scst_event_entry *e;
|
||||
unsigned int processing = p->queued ? 0 : p->result ? 2 : 1;
|
||||
|
||||
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 3, 42, true));
|
||||
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 3, 43, true));
|
||||
if (!p->queued) {
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
|
||||
}
|
||||
event_test_ack_input(f, p->unknown ? 99 : 42, p->status);
|
||||
f->from_left = p->uncopied;
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_NOTIFY_DONE), (long)p->result);
|
||||
KUNIT_EXPECT_EQ(test, f->imported, sizeof(struct scst_event_notify_done));
|
||||
KUNIT_EXPECT_EQ(test, f->notifications, (unsigned int)!p->result);
|
||||
if (!p->result) {
|
||||
KUNIT_EXPECT_EQ(test, f->notify_status, p->status);
|
||||
KUNIT_EXPECT_EQ(test, f->notify_id, 42U);
|
||||
}
|
||||
if (p->repeat) {
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_NOTIFY_DONE), (long)-ENOENT);
|
||||
KUNIT_EXPECT_EQ(test, f->notifications, 1U);
|
||||
}
|
||||
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, p->queued ? 2 : 0);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.queued_events_list),
|
||||
p->queued ? 2U : 0U);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.processing_events_list), processing);
|
||||
if (processing) {
|
||||
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.processing_events_list));
|
||||
e = list_first_entry(&f->priv.processing_events_list, struct scst_event_entry,
|
||||
events_list_entry);
|
||||
KUNIT_EXPECT_EQ(test, e->event.event_id, p->result ? 42U : 43U);
|
||||
}
|
||||
}
|
||||
|
||||
struct event_timeout_case {
|
||||
const char *name;
|
||||
bool delivered;
|
||||
bool detached;
|
||||
bool second;
|
||||
};
|
||||
|
||||
static const struct event_timeout_case event_timeout_cases[] = {
|
||||
{ .name = "expire_queued_event", .second = true },
|
||||
{ .name = "expire_delivered_event", .delivered = true },
|
||||
{ .name = "delivered_timeout_preserves_queued_count", .delivered = true, .second = true },
|
||||
{ .name = "already_detached_event", .detached = true },
|
||||
};
|
||||
|
||||
static void event_timeout_desc(const struct event_timeout_case *p, char *desc)
|
||||
{
|
||||
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(event_timeout, event_timeout_cases, event_timeout_desc);
|
||||
|
||||
static void event_timeout_test(struct kunit *test)
|
||||
{
|
||||
const struct event_timeout_case *p = test->param_value;
|
||||
struct event_test_fixture *f = test->priv;
|
||||
struct scst_event_entry *e = event_test_queue(f, 3, 42, true);
|
||||
|
||||
KUNIT_ASSERT_NOT_NULL(test, e);
|
||||
if (p->second)
|
||||
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 0, 43, false));
|
||||
if (p->delivered)
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
|
||||
if (p->detached) {
|
||||
list_del_init(&e->events_list_entry);
|
||||
f->priv.queued_events_cnt--;
|
||||
f->detached = e;
|
||||
}
|
||||
/* Deterministic expiry: the delayed work was initialized but never queued. */
|
||||
scst_event_timeout_fn(&e->event_timeout_work.work);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, (int)p->second);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.queued_events_list),
|
||||
(unsigned int)p->second);
|
||||
KUNIT_EXPECT_TRUE(test, list_empty(&f->priv.processing_events_list));
|
||||
KUNIT_EXPECT_EQ(test, f->notifications, (unsigned int)!p->detached);
|
||||
if (!p->detached) {
|
||||
KUNIT_EXPECT_EQ(test, f->notify_status, -ETIMEDOUT);
|
||||
KUNIT_EXPECT_EQ(test, f->notify_id, 42U);
|
||||
}
|
||||
event_test_ack_input(f, 42, 0);
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_NOTIFY_DONE), (long)-ENOENT);
|
||||
KUNIT_EXPECT_EQ(test, f->notifications, (unsigned int)!p->detached);
|
||||
}
|
||||
|
||||
static struct kunit_case scst_event_delivery_cases[] = {
|
||||
KUNIT_CASE_PARAM(event_delivery_test, event_delivery_gen_params),
|
||||
KUNIT_CASE(event_delivery_retry_test),
|
||||
KUNIT_CASE_PARAM(event_ack_test, event_ack_gen_params),
|
||||
KUNIT_CASE_PARAM(event_timeout_test, event_timeout_gen_params),
|
||||
{}
|
||||
};
|
||||
|
||||
scst_kunit_suite(scst_event_delivery_suite, "EVENT", "-delivery",
|
||||
.init = event_test_init,
|
||||
.exit = event_test_exit,
|
||||
.test_cases = scst_event_delivery_cases,
|
||||
);
|
||||
@@ -0,0 +1,228 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include "../scst_event_fixture.h"
|
||||
|
||||
struct event_import_case {
|
||||
const char *name;
|
||||
int length;
|
||||
int copied_delta;
|
||||
size_t uncopied;
|
||||
int result;
|
||||
bool read_fault;
|
||||
bool alloc_fault;
|
||||
bool unterminated;
|
||||
};
|
||||
|
||||
static const struct event_import_case event_import_cases[] = {
|
||||
{ .name = "wildcard_empty_payload" },
|
||||
{ .name = "one_byte", .length = 1 },
|
||||
{ .name = "binary_payload", .length = 256 },
|
||||
{ .name = "below_payload_limit", .length = SCST_MAX_PAYLOAD - 1 },
|
||||
{ .name = "at_payload_limit", .length = SCST_MAX_PAYLOAD },
|
||||
{ .name = "above_payload_limit", .length = SCST_MAX_PAYLOAD + 1, .result = -EINVAL },
|
||||
{ .name = "int_max_length", .length = INT_MAX, .result = -EINVAL },
|
||||
{ .name = "int_min_length", .length = INT_MIN, .result = -EINVAL },
|
||||
{ .name = "negative_length", .length = -1, .result = -EINVAL },
|
||||
{ .name = "size_read_fault", .read_fault = true, .result = -EFAULT },
|
||||
{ .name = "allocation_failure", .alloc_fault = true, .result = -ENOMEM },
|
||||
{ .name = "header_copy_fault", .uncopied = SIZE_MAX, .result = -EFAULT },
|
||||
{ .name = "last_header_byte_fault", .uncopied = 1, .result = -EFAULT },
|
||||
{ .name = "partial_payload_fault", .length = 256, .uncopied = 128, .result = -EFAULT },
|
||||
{ .name = "last_payload_byte_fault", .length = SCST_MAX_PAYLOAD,
|
||||
.uncopied = 1, .result = -EFAULT },
|
||||
{ .name = "length_grew_during_copy", .copied_delta = 1, .result = -EINVAL },
|
||||
{ .name = "length_shrank_during_copy", .length = 1, .copied_delta = -1,
|
||||
.result = -EINVAL },
|
||||
{ .name = "length_became_negative", .copied_delta = -1, .result = -EINVAL },
|
||||
{ .name = "length_crossed_limit_during_copy", .length = SCST_MAX_PAYLOAD,
|
||||
.copied_delta = 1, .result = -EINVAL },
|
||||
{ .name = "unterminated_issuer", .length = 1, .unterminated = true },
|
||||
};
|
||||
|
||||
static void event_import_desc(const struct event_import_case *p, char *desc)
|
||||
{
|
||||
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(event_import, event_import_cases, event_import_desc);
|
||||
|
||||
static void event_import_test(struct kunit *test)
|
||||
{
|
||||
const struct event_import_case *p = test->param_value;
|
||||
struct event_test_fixture *f = test->priv;
|
||||
bool valid_length = p->length >= 0 && p->length <= SCST_MAX_PAYLOAD;
|
||||
unsigned int allocations = !p->read_fault && valid_length;
|
||||
unsigned int imports = allocations && !p->alloc_fault;
|
||||
struct scst_event *input = event_test_input(f, valid_length ? p->length : 0, 0, "*");
|
||||
struct scst_event_entry *e;
|
||||
|
||||
f->user_size = p->length;
|
||||
input->payload_len = valid_length ? p->length + p->copied_delta : p->length;
|
||||
if (p->unterminated)
|
||||
memset(input->issuer_name, 'x', sizeof(input->issuer_name));
|
||||
f->read_error = p->read_fault ? -EFAULT : 0;
|
||||
f->fail_alloc = p->alloc_fault;
|
||||
f->from_left = p->uncopied;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), (long)p->result);
|
||||
KUNIT_EXPECT_EQ(test, f->reads, 1U);
|
||||
KUNIT_EXPECT_EQ(test, f->allocations, allocations);
|
||||
KUNIT_EXPECT_EQ(test, f->imports, imports);
|
||||
if (allocations)
|
||||
KUNIT_EXPECT_EQ(test, f->allocation_size, sizeof(*e) + p->length);
|
||||
if (imports)
|
||||
KUNIT_EXPECT_EQ(test, f->imported, sizeof(*input) + p->length);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, !p->result);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
|
||||
(unsigned int)!p->result);
|
||||
if (p->result)
|
||||
return;
|
||||
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.allowed_events_list));
|
||||
e = list_first_entry(&f->priv.allowed_events_list, struct scst_event_entry,
|
||||
events_list_entry);
|
||||
input->issuer_name[sizeof(input->issuer_name) - 1] = '\0';
|
||||
KUNIT_EXPECT_EQ(test, memcmp(&e->event, input, f->input_size), 0);
|
||||
KUNIT_EXPECT_NULL(test, e->event_notify_fn);
|
||||
KUNIT_EXPECT_NULL(test, e->notify_fn_priv);
|
||||
KUNIT_EXPECT_NULL(test, e->pqueued_events_cnt);
|
||||
}
|
||||
|
||||
struct event_subscription_case {
|
||||
const char *name;
|
||||
unsigned int command;
|
||||
u32 code;
|
||||
const char *issuer;
|
||||
unsigned int length;
|
||||
unsigned int remaining;
|
||||
int result;
|
||||
bool different_payload;
|
||||
bool copy_fault;
|
||||
};
|
||||
|
||||
static const struct event_subscription_case event_subscription_cases[] = {
|
||||
{ "duplicate_ignores_event_id", SCST_EVENT_ALLOW_EVENT, 1, "alpha", 3, 7, -EEXIST },
|
||||
{ "different_code", SCST_EVENT_ALLOW_EVENT, 3, "alpha", 3, 15 },
|
||||
{ "different_payload_length", SCST_EVENT_ALLOW_EVENT, 1, "alpha", 1, 15 },
|
||||
{ "different_binary_payload", SCST_EVENT_ALLOW_EVENT, 1, "alpha", 3, 15,
|
||||
.different_payload = true },
|
||||
{ "wildcard_duplicate", SCST_EVENT_ALLOW_EVENT, 0, "*", 0, 7, -EEXIST },
|
||||
{ "remove_exact", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 3, 6 },
|
||||
{ "wildcard_code", SCST_EVENT_DISALLOW_EVENT, 0, "beta", 3, 1 },
|
||||
{ "wildcard_issuer", SCST_EVENT_DISALLOW_EVENT, 1, "*", 3, 2 },
|
||||
{ "wildcard_payload", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 0, 6 },
|
||||
{ "remove_all", SCST_EVENT_DISALLOW_EVENT, 0, "*", 0, 0 },
|
||||
{ "missing_code", SCST_EVENT_DISALLOW_EVENT, 3, "*", 0, 7, -ENOENT },
|
||||
{ "different_length_does_not_match", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 1,
|
||||
7, -ENOENT },
|
||||
{ "different_bytes_do_not_match", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 3,
|
||||
7, -ENOENT, .different_payload = true },
|
||||
{ "failed_disallow_preserves_subscriptions", SCST_EVENT_DISALLOW_EVENT, 0, "*", 0,
|
||||
7, -EFAULT, .copy_fault = true },
|
||||
};
|
||||
|
||||
static void event_subscription_desc(const struct event_subscription_case *p, char *desc)
|
||||
{
|
||||
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(event_subscription, event_subscription_cases, event_subscription_desc);
|
||||
|
||||
static void event_subscription_test(struct kunit *test)
|
||||
{
|
||||
const struct event_subscription_case *p = test->param_value;
|
||||
struct event_test_fixture *f = test->priv;
|
||||
struct scst_event_entry *e;
|
||||
struct scst_event *input;
|
||||
unsigned int i, remaining = 0;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
input = event_test_input(f, 3, i == 1 ? 2 : 1, i ? "beta" : "alpha");
|
||||
input->event_id = i;
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), 0L);
|
||||
}
|
||||
input = event_test_input(f, p->length, p->code, p->issuer);
|
||||
input->event_id = 3;
|
||||
if (p->different_payload)
|
||||
input->payload[2] ^= 0xff;
|
||||
f->from_left = p->copy_fault ? 1 : 0;
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, p->command), (long)p->result);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, (int)hweight32(p->remaining));
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
|
||||
hweight32(p->remaining));
|
||||
list_for_each_entry(e, &f->priv.allowed_events_list, events_list_entry) {
|
||||
KUNIT_ASSERT_LT(test, e->event.event_id, 4U);
|
||||
remaining |= BIT(e->event.event_id);
|
||||
}
|
||||
KUNIT_EXPECT_EQ(test, remaining, p->remaining);
|
||||
}
|
||||
|
||||
static void event_subscription_limit_test(struct kunit *test)
|
||||
{
|
||||
struct event_test_fixture *f = test->priv;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 1; i <= SCST_MAX_EVENTS; i++) {
|
||||
event_test_input(f, 0, i, "limit");
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), 0L);
|
||||
}
|
||||
event_test_input(f, 0, SCST_MAX_EVENTS + 1, "limit");
|
||||
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), (long)-EMFILE);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, SCST_MAX_EVENTS);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
|
||||
(unsigned int)SCST_MAX_EVENTS);
|
||||
event_test_input(f, 0, 1, "limit");
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_DISALLOW_EVENT), 0L);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, SCST_MAX_EVENTS - 1);
|
||||
event_test_input(f, 0, SCST_MAX_EVENTS + 1, "limit");
|
||||
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), 0L);
|
||||
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, SCST_MAX_EVENTS);
|
||||
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
|
||||
(unsigned int)SCST_MAX_EVENTS);
|
||||
}
|
||||
|
||||
static const unsigned int event_clone_lengths[] = { 0, 1, 256, SCST_MAX_PAYLOAD };
|
||||
|
||||
static void event_clone_desc(const unsigned int *length, char *desc)
|
||||
{
|
||||
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "payload_%u", *length);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(event_clone, event_clone_lengths, event_clone_desc);
|
||||
|
||||
static void event_clone_test(struct kunit *test)
|
||||
{
|
||||
struct event_test_fixture *f = test->priv;
|
||||
unsigned int length = *(const unsigned int *)test->param_value;
|
||||
struct scst_event_entry *original = event_test_queue(f, length, 42, false);
|
||||
struct scst_event_entry *clone;
|
||||
|
||||
KUNIT_ASSERT_NOT_NULL(test, original);
|
||||
KUNIT_ASSERT_EQ(test, scst_clone_event(original, &f->detached), 0);
|
||||
clone = f->detached;
|
||||
KUNIT_ASSERT_NOT_NULL(test, clone);
|
||||
KUNIT_EXPECT_PTR_NE(test, clone, original);
|
||||
KUNIT_EXPECT_EQ(test, memcmp(&clone->event, &original->event,
|
||||
sizeof(clone->event) + length), 0);
|
||||
KUNIT_EXPECT_NULL(test, clone->pqueued_events_cnt);
|
||||
KUNIT_EXPECT_NULL(test, clone->event_notify_fn);
|
||||
KUNIT_EXPECT_NULL(test, clone->notify_fn_priv);
|
||||
clone->event.event_id = 99;
|
||||
if (length) {
|
||||
clone->event.payload[length - 1] ^= 0xff;
|
||||
KUNIT_EXPECT_EQ(test, original->event.payload[length - 1], (u8)(length - 1));
|
||||
}
|
||||
KUNIT_EXPECT_EQ(test, original->event.event_id, 42U);
|
||||
}
|
||||
|
||||
static struct kunit_case scst_event_import_cases[] = {
|
||||
KUNIT_CASE_PARAM(event_import_test, event_import_gen_params),
|
||||
KUNIT_CASE_PARAM(event_subscription_test, event_subscription_gen_params),
|
||||
KUNIT_CASE(event_subscription_limit_test),
|
||||
KUNIT_CASE_PARAM(event_clone_test, event_clone_gen_params),
|
||||
{}
|
||||
};
|
||||
|
||||
scst_kunit_suite(scst_event_import_suite, "EVENT", "-import",
|
||||
.init = event_test_init,
|
||||
.exit = event_test_exit,
|
||||
.test_cases = scst_event_import_cases,
|
||||
);
|
||||
@@ -4,6 +4,9 @@
|
||||
scst-kunit-scst := \
|
||||
kunit/alua/suites/scst_kunit_alua.o \
|
||||
kunit/cdb/suites/scst_kunit_cdb.o \
|
||||
kunit/event/scst_event_fixture.o \
|
||||
kunit/event/suites/scst_kunit_event_import.o \
|
||||
kunit/event/suites/scst_kunit_event_delivery.o \
|
||||
kunit/lib/suites/scst_kunit_lib.o \
|
||||
kunit/order/suites/scst_kunit_order.o \
|
||||
kunit/unmap/suites/scst_kunit_unmap.o
|
||||
|
||||
+60
-34
@@ -28,6 +28,8 @@
|
||||
#endif
|
||||
|
||||
#include "scst_priv.h"
|
||||
#include "scst_event_priv.h"
|
||||
#include "scst_event_test.h"
|
||||
|
||||
#undef DEFAULT_SYMBOL_NAMESPACE
|
||||
#define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE
|
||||
@@ -37,31 +39,52 @@ static struct class *scst_event_sysfs_class;
|
||||
|
||||
static int scst_event_major;
|
||||
|
||||
#define SCST_MAX_EVENTS 2048
|
||||
#define SCST_MAX_PAYLOAD (3 * 1024)
|
||||
#define SCST_DEFAULT_EVENT_TIMEOUT (60 * HZ)
|
||||
|
||||
struct scst_event_priv {
|
||||
struct list_head privs_list_entry;
|
||||
int allowed_events_cnt;
|
||||
int queued_events_cnt;
|
||||
unsigned int going_to_exit:1;
|
||||
unsigned int blocking:1;
|
||||
pid_t owner_pid;
|
||||
/*
|
||||
* WARNING: payloads in events in the allowed list queued AS IS from the
|
||||
* user space, so they are UNTRUSTED and can NOT be used in any other
|
||||
* way, except as BLOBs for comparison!
|
||||
*/
|
||||
struct list_head allowed_events_list;
|
||||
struct list_head queued_events_list;
|
||||
wait_queue_head_t queued_events_waitQ;
|
||||
struct list_head processing_events_list;
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(scst_event_mutex);
|
||||
SCST_KUNIT_VISIBLE
|
||||
struct mutex scst_event_mutex = __MUTEX_INITIALIZER(scst_event_mutex);
|
||||
static LIST_HEAD(scst_event_privs_list);
|
||||
|
||||
SCST_KUNIT_VISIBLE
|
||||
int scst_event_get_user_payload_size(u32 *size, struct scst_event_user __user *arg)
|
||||
{
|
||||
SCST_KUNIT_REDIRECT(scst_event_get_user_payload_size, size, arg);
|
||||
return get_user(*size, &arg->max_event_size);
|
||||
}
|
||||
|
||||
SCST_KUNIT_VISIBLE
|
||||
int scst_event_get_user_size(s32 *size, const void __user *arg)
|
||||
{
|
||||
SCST_KUNIT_REDIRECT(scst_event_get_user_size, size, arg);
|
||||
return get_user(*size, (const s32 __user *)arg);
|
||||
}
|
||||
|
||||
SCST_KUNIT_VISIBLE
|
||||
int scst_event_put_user_size(s32 size, void __user *arg)
|
||||
{
|
||||
SCST_KUNIT_REDIRECT(scst_event_put_user_size, size, arg);
|
||||
return put_user(size, (s32 __user *)arg);
|
||||
}
|
||||
|
||||
SCST_KUNIT_VISIBLE
|
||||
unsigned long scst_event_copy_from_user(void *to, const void __user *from, size_t size)
|
||||
{
|
||||
SCST_KUNIT_REDIRECT(scst_event_copy_from_user, to, from, size);
|
||||
return copy_from_user(to, from, size);
|
||||
}
|
||||
|
||||
SCST_KUNIT_VISIBLE
|
||||
unsigned long scst_event_copy_to_user(void __user *to, const void *from, size_t size)
|
||||
{
|
||||
SCST_KUNIT_REDIRECT(scst_event_copy_to_user, to, from, size);
|
||||
return copy_to_user(to, from, size);
|
||||
}
|
||||
|
||||
SCST_KUNIT_VISIBLE
|
||||
struct scst_event_entry *scst_event_alloc_user_entry(size_t size)
|
||||
{
|
||||
SCST_KUNIT_REDIRECT(scst_event_alloc_user_entry, size);
|
||||
return kzalloc(size, GFP_KERNEL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compares events e1_wild and e2, where e1_wild can have wildcard matching,
|
||||
* i.e.:
|
||||
@@ -97,7 +120,8 @@ out:
|
||||
return res;
|
||||
}
|
||||
|
||||
static void scst_event_timeout_fn(struct work_struct *work)
|
||||
SCST_KUNIT_VISIBLE
|
||||
void scst_event_timeout_fn(struct work_struct *work)
|
||||
{
|
||||
struct scst_event_entry *event_entry = container_of(work, struct scst_event_entry,
|
||||
event_timeout_work.work);
|
||||
@@ -132,8 +156,9 @@ out:
|
||||
TRACE_EXIT();
|
||||
}
|
||||
|
||||
static int scst_clone_event(const struct scst_event_entry *orig_entry,
|
||||
struct scst_event_entry **new_event_entry)
|
||||
SCST_KUNIT_VISIBLE
|
||||
int scst_clone_event(const struct scst_event_entry *orig_entry,
|
||||
struct scst_event_entry **new_event_entry)
|
||||
{
|
||||
int res = 0;
|
||||
const struct scst_event *event = &orig_entry->event;
|
||||
@@ -582,7 +607,7 @@ static int scst_event_get_event_from_user(struct scst_event_user __user *arg,
|
||||
|
||||
TRACE_ENTRY();
|
||||
|
||||
res = get_user(payload_len, &arg->max_event_size);
|
||||
res = scst_event_get_user_payload_size(&payload_len, arg);
|
||||
if (res != 0) {
|
||||
PRINT_ERROR("Failed to get payload len: %d", res);
|
||||
goto out;
|
||||
@@ -599,7 +624,7 @@ static int scst_event_get_event_from_user(struct scst_event_user __user *arg,
|
||||
|
||||
event_entry_len = sizeof(*event_entry) + payload_len;
|
||||
|
||||
event_entry = kzalloc(event_entry_len, GFP_KERNEL);
|
||||
event_entry = scst_event_alloc_user_entry(event_entry_len);
|
||||
if (!event_entry) {
|
||||
PRINT_ERROR("Unable to allocate event entry (size %d)",
|
||||
event_entry_len);
|
||||
@@ -612,7 +637,7 @@ static int scst_event_get_event_from_user(struct scst_event_user __user *arg,
|
||||
event = &event_entry->event;
|
||||
event_len = sizeof(*event) + payload_len;
|
||||
|
||||
rc = copy_from_user((u8 *)event, arg, event_len);
|
||||
rc = scst_event_copy_from_user((u8 *)event, arg, event_len);
|
||||
if (rc != 0) {
|
||||
PRINT_ERROR("Failed to copy %d user's bytes", rc);
|
||||
res = -EFAULT;
|
||||
@@ -737,7 +762,7 @@ static int scst_event_user_next_event(struct scst_event_priv *priv, void __user
|
||||
|
||||
TRACE_ENTRY();
|
||||
|
||||
res = get_user(max_event_size, (int32_t __user *)arg);
|
||||
res = scst_event_get_user_size(&max_event_size, arg);
|
||||
if (res != 0) {
|
||||
PRINT_ERROR("Failed to get max event size: %d", res);
|
||||
goto out;
|
||||
@@ -773,13 +798,13 @@ static int scst_event_user_next_event(struct scst_event_priv *priv, void __user
|
||||
if (needed_size > max_event_size) {
|
||||
TRACE_DBG("Too big event (size %d, max size %d)",
|
||||
needed_size, max_event_size);
|
||||
res = put_user(needed_size, (int32_t __user *)arg);
|
||||
res = scst_event_put_user_size(needed_size, arg);
|
||||
if (res == 0)
|
||||
res = -ENOSPC;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = copy_to_user(&event_user->out_event, &event_entry->event, needed_size);
|
||||
rc = scst_event_copy_to_user(&event_user->out_event, &event_entry->event, needed_size);
|
||||
if (rc != 0) {
|
||||
PRINT_ERROR("Copy to user failed (%d)", rc);
|
||||
res = -EFAULT;
|
||||
@@ -815,7 +840,7 @@ static int scst_event_user_notify_done(struct scst_event_priv *priv, void __user
|
||||
|
||||
TRACE_ENTRY();
|
||||
|
||||
rc = copy_from_user(&n, arg, sizeof(n));
|
||||
rc = scst_event_copy_from_user(&n, arg, sizeof(n));
|
||||
if (rc != 0) {
|
||||
PRINT_ERROR("Failed to copy %d user's bytes of notify done", rc);
|
||||
res = -EFAULT;
|
||||
@@ -911,7 +936,8 @@ out_put:
|
||||
goto out;
|
||||
}
|
||||
|
||||
static long scst_event_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
SCST_KUNIT_VISIBLE
|
||||
long scst_event_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
long res;
|
||||
struct scst_event_priv *priv;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef SCST_EVENT_PRIV_H
|
||||
#define SCST_EVENT_PRIV_H
|
||||
|
||||
#define SCST_MAX_EVENTS 2048
|
||||
#define SCST_MAX_PAYLOAD (3 * 1024)
|
||||
#define SCST_DEFAULT_EVENT_TIMEOUT (60 * HZ)
|
||||
|
||||
struct scst_event_priv {
|
||||
struct list_head privs_list_entry;
|
||||
int allowed_events_cnt;
|
||||
int queued_events_cnt;
|
||||
unsigned int going_to_exit:1;
|
||||
unsigned int blocking:1;
|
||||
pid_t owner_pid;
|
||||
/*
|
||||
* WARNING: payloads in events in the allowed list queued AS IS from the
|
||||
* user space, so they are UNTRUSTED and can NOT be used in any other
|
||||
* way, except as BLOBs for comparison!
|
||||
*/
|
||||
struct list_head allowed_events_list;
|
||||
struct list_head queued_events_list;
|
||||
wait_queue_head_t queued_events_waitQ;
|
||||
struct list_head processing_events_list;
|
||||
};
|
||||
|
||||
#endif /* SCST_EVENT_PRIV_H */
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef SCST_EVENT_TEST_H
|
||||
#define SCST_EVENT_TEST_H
|
||||
|
||||
#include "scst_test.h"
|
||||
|
||||
#ifdef SCST_KUNIT_ENABLED
|
||||
|
||||
extern struct mutex scst_event_mutex;
|
||||
|
||||
int scst_event_get_user_payload_size(u32 *size, struct scst_event_user __user *arg);
|
||||
int scst_event_get_user_size(s32 *size, const void __user *arg);
|
||||
int scst_event_put_user_size(s32 size, void __user *arg);
|
||||
unsigned long scst_event_copy_from_user(void *to, const void __user *from, size_t size);
|
||||
unsigned long scst_event_copy_to_user(void __user *to, const void *from, size_t size);
|
||||
struct scst_event_entry *scst_event_alloc_user_entry(size_t size);
|
||||
void scst_event_timeout_fn(struct work_struct *work);
|
||||
int scst_clone_event(const struct scst_event_entry *orig_entry,
|
||||
struct scst_event_entry **new_event_entry);
|
||||
long scst_event_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
||||
|
||||
#endif /* SCST_KUNIT_ENABLED */
|
||||
#endif /* SCST_EVENT_TEST_H */
|
||||
Reference in New Issue
Block a user