diff --git a/Dockerfile b/Dockerfile index ef39da2c78dd36360300f8f8edf3d36e020def43..e3683390b4ef98919c51516334d40cc606366341 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,6 @@ RUN echo "Hello from Docker" RUN mkdir -p /linux/tools/labs/skels/assignments/1-tracer RUN mkdir -p /linux/tools/labs/skels/assignments/1-tracer-checker -COPY ./checker/1-tracer-checker /linux/tools/labs/skels/assignments/1-tracer-checker - COPY ./checker/checker_daemons/so2_vm_checker_daemon.sh /linux/tools/labs/rootfs/etc/init.d RUN chmod +x /linux/tools/labs/rootfs/etc/init.d/so2_vm_checker_daemon.sh RUN chroot /linux/tools/labs/rootfs update-rc.d so2_vm_checker_daemon.sh defaults diff --git a/checker/2-uart-checker/Makefile b/checker/2-uart-checker/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9cb87cb293e9cca5710751fab0cded1f2908aaae --- /dev/null +++ b/checker/2-uart-checker/Makefile @@ -0,0 +1,14 @@ +CFLAGS = -Wall -g -static -m32 + +.PHONY: all run clean + +all: test solution.ko + +test: _test/test.o + $(CC) $(CFLAGS) -o $@ $^ + +solution.ko: _test/solution.ko + ln -s $< $@ + +clean: + -rm -f *~ test _test/test.o solution.ko diff --git a/checker/2-uart-checker/README b/checker/2-uart-checker/README new file mode 100644 index 0000000000000000000000000000000000000000..67ae7234cd192c44e00caf9fe5aa4e26bcbf9296 --- /dev/null +++ b/checker/2-uart-checker/README @@ -0,0 +1,38 @@ += UART16550 TEST SUITE == + +Test suite for UART16550 + +== FILES == + +README + * this file + +Makefile + * Makefile to build the test suite executable + +_checker + * script to run all tests defined in _test/test.c + +_test/test.c + * test suite for UART16550 + +_test/solution.ko + * kernel module implementing UART16550, + used to transmit/receive data to/from your kernel module + +== BUILDING == + +Use the Makefile to properly build the test executable: + + make + +== RUNNING == + +Copy your uart16550.ko module and _checker, test and solution.ko +to fsimg/root directory on your QEMU/KVM virtual machine. + +In order to run the test suite you can use the _checker script. + +The _checker script runs all tests and computes assignment grade: + + ./_checker diff --git a/checker/2-uart-checker/_checker b/checker/2-uart-checker/_checker new file mode 100755 index 0000000000000000000000000000000000000000..a118d02b621c642d8d2bd960594f21bf4d1cda5f --- /dev/null +++ b/checker/2-uart-checker/_checker @@ -0,0 +1,4 @@ +#!/bin/sh + +insmod uart16550.ko; cat /proc/modules > /dev/kmsg; rmmod uart16550 +./test diff --git a/checker/2-uart-checker/_test/solution.ko b/checker/2-uart-checker/_test/solution.ko new file mode 100644 index 0000000000000000000000000000000000000000..996dd21ac572b8fd91bc14645e9bf6e57f4093d8 Binary files /dev/null and b/checker/2-uart-checker/_test/solution.ko differ diff --git a/checker/2-uart-checker/_test/test.c b/checker/2-uart-checker/_test/test.c new file mode 100644 index 0000000000000000000000000000000000000000..6e87f7055daac8f8750c8a7c301bcf8c4f4d148e --- /dev/null +++ b/checker/2-uart-checker/_test/test.c @@ -0,0 +1,593 @@ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdarg.h> +#include <time.h> +#include <string.h> +#include <assert.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <signal.h> + +#include "uart16550.h" + +#define UART16550_MAJOR 42 +#define COM1_MAJOR 42 +#define COM2_MAJOR 42 +#define STR(x) #x +#define XSTR(x) STR(x) +#define OPTION_COM1_ONLY 1 +#define OPTION_COM2_ONLY 2 +#define OPTION_BOTH 3 + +#define MODULE_NAME "uart16550" +#define SOLUTION_NAME "solution" + +#define PAD_CHARS 60 + +#define UART0 "/dev/uart0" +#define UART1 "/dev/uart1" +#define UART10 "/dev/uart10" + +#define INFILE "testfile.in" +#define OUTFILE "testfile.out" + +#define fail(s) do { \ + printf("%s:%d: ", __func__, __LINE__); \ + fflush(stdout); \ + perror(s); \ + exit(EXIT_FAILURE); \ + } while (0) + + +#define test(d, v, e, p) do_test((d), (v), (e), 0, 0, (p)) +#define not_test(d, v, e, p) do_test((d), (v), (e), 1, 0, (p)) +#define fatal_test(d, v, e,p) do_test((d), (v), (e), 0, 1, (p)) + +#define GENERIC_TEST_TIMEOUT 3 +const int total = 92; + +void sig_handler(int signum) { + fprintf(stderr, "Child process pid=%d of checker (that issues read/write syscalls to the driver) got killed after TIMEOUT=%ds\n", getpid(), GENERIC_TEST_TIMEOUT); + fprintf(stderr, "\tThis might be because you didn't implement read/write or there is a bug in the implementation\n"); + exit(EXIT_FAILURE); +} + +/* + * if the test passes it will return 0 + * if it fails it returns the number of points given as argument + */ +static float +do_test(const char *description, int value, int expected, int negate, int fatal, float points) +{ + int num_chars; + + num_chars = printf("%s", description); + for (; num_chars < PAD_CHARS - strlen("passed"); num_chars++) + putchar('.'); + fflush(stdout); + if (!negate) { + if (value == expected) { + printf("passed [%.1f/%d]\n", points, total); + fflush(stdout); + return 0; + } else { + printf("failed [0/%d]\n", total); + fflush(stdout); + if (fatal) + exit(EXIT_FAILURE); + } + } else { + if (value != expected) { + printf("passed [%.1f/%d]\n", points, total); + fflush(stdout); + return 0; + } else { + printf("failed [0/%d]\n", total); + fflush(stdout); + if (fatal) + exit(EXIT_FAILURE); + } + } + return points; +} + +static void +test_title(const char *title) +{ + int len = strlen(title); + int pad = (PAD_CHARS - len) / 2 - 1; + int mod = (PAD_CHARS - len) % 2; + int i; + + assert(pad >= 1); + putchar('\n'); + for (i = 0; i < pad; i++) + putchar('='); + printf(" %s ", title); + for (i = 0; i < pad + mod; i++) + putchar('='); + putchar('\n'); +} + +static void +make_nodes(void) +{ + mknod(UART0, S_IFCHR, COM1_MAJOR<<8); + mknod(UART1, S_IFCHR, (COM2_MAJOR<<8) + 1); + mknod(UART10, S_IFCHR, (UART16550_MAJOR<<8)+10); +} + +static void +remove_nodes(void) +{ + unlink(UART0); + unlink(UART1); + unlink(UART10); +} + +static float +test1(void) +{ + float total = 16; + + test_title("Test 1. Module insertion and removal"); + + /* Insert module with default params and test. */ + total -= fatal_test("insmod " MODULE_NAME ", default options", + system("insmod " MODULE_NAME ".ko"), 0, 1); + total -= test("major", + system("cat /proc/devices | grep '" XSTR(COM1_MAJOR) " " MODULE_NAME "' >/dev/null 2>&1"), + 0, 1); + total -= test("ioports COM1", + system("cat /proc/ioports | grep '03f8-03ff : " MODULE_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("ioports COM2", + system("cat /proc/ioports | grep '02f8-02ff : " MODULE_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("interrupts COM1", + system("cat /proc/interrupts | grep '4:.*" MODULE_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("interrupts COM2", + system("cat /proc/interrupts | grep '3:.*" MODULE_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("rmmod", system("rmmod " MODULE_NAME), 0, 0.5); + + /* Insert module with different major. */ + total -= fatal_test("insmod " MODULE_NAME ", major=" XSTR(COM2_MAJOR), + system("insmod " MODULE_NAME ".ko major=" XSTR(COM2_MAJOR)), 0, 1); + total -= test("major", + system("cat /proc/devices | grep '" XSTR(COM2_MAJOR) " " MODULE_NAME "' >/dev/null 2>&1"), + 0, 1); + total -= test("rmmod", system("rmmod " MODULE_NAME), 0, 0.5); + + /* Insert module only for COM2, check that it works side by side + * with solution. + */ + total -= fatal_test("insmod " MODULE_NAME ", COM2 only", + system("insmod " MODULE_NAME ".ko option=" XSTR(OPTION_COM2_ONLY)), + 0, 1); + total -= fatal_test("insmod " SOLUTION_NAME ", COM1 only", + system("insmod " SOLUTION_NAME ".ko option=" XSTR(OPTION_COM1_ONLY)), + 0, 1); + total -= test("ioports COM1", + system("cat /proc/ioports | grep '03f8-03ff : " SOLUTION_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("ioports COM2", + system("cat /proc/ioports | grep '02f8-02ff : " MODULE_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("interrupts COM1", + system("cat /proc/interrupts | grep '4:.*" SOLUTION_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("interrupts COM2", + system("cat /proc/interrupts | grep '3:.*" MODULE_NAME "' > /dev/null 2>&1"), + 0, 1); + total -= test("rmmod " MODULE_NAME, system("rmmod " MODULE_NAME), 0, 0.5); + total -= test("rmmod " SOLUTION_NAME, system("rmmod " SOLUTION_NAME), 0, 0.5); + + return total; +} + +static float +test2(void) +{ + float total = 5.5; + int fd; + + test_title("Test 2. Invalid parameters"); + + /* Check ioctl sanity. */ + total -= fatal_test("insmod", system("insmod " MODULE_NAME ".ko"), 0, 1); + fd = open(UART0, O_RDWR); + if (fd == -1) + fail("open " UART0); +#define ioctl_test(n) test("invalid ioctl " XSTR((n)), \ + ioctl(fd, UART16550_IOCTL_SET_LINE, (n)), -1, 1) + total -= ioctl_test(0xdeadbeef); + total -= ioctl_test(0x1337cafe); +#undef ioctl_test + total -= test("invalid ioctl wrong operation", ioctl(fd, 0xffff), -1, 1); + close(fd); + total -= test("rmmod", system("rmmod " MODULE_NAME), 0, 0.5); + + /* Check invalid module parameters. */ + total -= not_test("insmod " MODULE_NAME ", option=0xdeadbabe", + system("insmod " MODULE_NAME ".ko option=0xdeadbabe"), + 0, 1); + + return total; +} + +/* Speed sets: + * 0 -> 1200, 2400, 4800 + * 1 -> 9600, 19200, 38400, 56000 + * 2 -> 115200 + */ +static const struct { + int num; + unsigned char speed[4]; + int bufsizes[2]; /* min and max */ +} speed_sets[3] = { + { + .num = 3, + .speed = { UART16550_BAUD_1200, + UART16550_BAUD_2400, + UART16550_BAUD_4800, -1 }, + .bufsizes = { 128, 256 }, + }, + { + .num = 4, + .speed = { UART16550_BAUD_9600, + UART16550_BAUD_19200, + UART16550_BAUD_38400, + UART16550_BAUD_56000 }, + .bufsizes = { 256, 1024 }, + }, + { + .num = 1, + .speed = { UART16550_BAUD_115200, -1, -1, -1 }, + .bufsizes = { 2048, 2048 }, + }, +}; + +static void +gen_params(struct uart16550_line_info *line, int speed_set) +{ + int r; + + line->baud = speed_sets[speed_set].speed[rand() % + speed_sets[speed_set].num]; + line->len = UART16550_LEN_8; + line->stop = rand() % 2 * 4; + r = rand() % 4; + line->par = r < 2 ? r*8 : 0x18 + (r-2) * 8; +} + +int do_read(int fd, unsigned char *buffer, int size) +{ + int n, from = 0; + + while (1) { + n = read(fd, &buffer[from], size - from); + if (n <= 0) + return -1; + if (n + from == size) + return 0; + from += n; + } +} + +int do_write(int fd, unsigned char *buffer, unsigned int size) +{ + int n, from = 0; + + while (1) { + n = write(fd, &buffer[from], size - from); + if (n <= 0) { + perror("write"); + return -1; + } + if (n + from == size) + return 0; + from += n; + } +} + +static int +gen_test_file(char *fname, int speed_set) +{ + int size, min, max; + char comm[1024]; + + min = speed_sets[speed_set].bufsizes[0]; + max = speed_sets[speed_set].bufsizes[1]; + size = (min == max) ? min : rand() % (min - max) + min; + sprintf(comm, + "dd if=/dev/urandom of=%s bs=1 count=%d >/dev/null 2>/dev/null", + fname, + size); + if (system(comm)) + fprintf(stderr, "failed to generate random file (%s)\n", comm); + return size; +} + +static void +copy_file(int fdr, int fdw, int len) +{ +#define COPY_BUF_SIZE 128 + unsigned char buf[COPY_BUF_SIZE]; + + do { + int partial, rc; + + partial = len < COPY_BUF_SIZE ? len : COPY_BUF_SIZE; + if (partial == 0) + break; + rc = read(fdr, buf, partial); + if (rc == 0) + break; + if (rc == -1) + fail("read"); + len -= rc; + rc = do_write(fdw, buf, rc); + if (rc < 0) + fail("write"); + } while (1); +} + +static int +copy_test(int fd0, int fd1, int speed_set) +{ + pid_t rpid, wpid, kpid; + int len, status, fd; + int rc1, rc2, rc3, exit_status1, exit_status2, exit_status3; + int i; + + len = gen_test_file(INFILE, speed_set); + rpid = fork(); + switch (rpid) { + case 0: + fd = open(INFILE, O_RDONLY); + if (fd < 0) + fail("open " INFILE); + copy_file(fd, fd0, len); + close(fd); + exit(EXIT_SUCCESS); + break; + default: + break; + } + + wpid = fork(); + switch (wpid) { + case 0: + fd = open(OUTFILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + fail("open " OUTFILE); + copy_file(fd1, fd, len); + close(fd); + exit(EXIT_SUCCESS); + break; + default: + break; + } + + kpid = fork(); + switch (kpid) { + case 0: + for (i = 0; i < GENERIC_TEST_TIMEOUT; i++) { + /* + * check if procs still exist. kill with arg 0 + * will succed (ret 0) if the pid exists + */ + if (!kill(rpid, 0)) { + sleep(1); + continue; + } else if (!kill(wpid, 0)) { + sleep(1); + continue; + } else + break; + + } + kill(rpid, SIGTERM); + kill(wpid, SIGTERM); + exit(EXIT_SUCCESS); + break; + default: + break; + + } + + rc1 = waitpid(rpid, &status, 0); + exit_status1 = WEXITSTATUS(status); + + + rc2 = waitpid(wpid, &status, 0); + exit_status2 = WEXITSTATUS(status); + + rc3 = waitpid(kpid, &status, 0); + exit_status3 = WEXITSTATUS(status); + + if (rc1 < 0 || rc2 < 0 || rc3 < 0 || + exit_status1 || exit_status2 || exit_status3) + return -1; + + return system("diff " INFILE " " OUTFILE "> /dev/null 2> /dev/null"); +} + +static float +generic_test(const char *reader, const char *writer, int speed_set, + int num_tests) +{ + int fd0, fd1, i; + float total = num_tests * 1.5 + (reader != writer ? 6 : 4) * 0.5; + char dbuf[1024], cbuf[1024]; + struct uart16550_line_info uli; + + if (reader != writer) { + sprintf(dbuf, "insmod %s", reader); + sprintf(cbuf, "insmod %s.ko option=%d", + reader, OPTION_COM2_ONLY); + total -= fatal_test(dbuf, system(cbuf), 0, 0.5); + sprintf(dbuf, "insmod %s", writer); + sprintf(cbuf, "insmod %s.ko option=%d", + writer, OPTION_COM1_ONLY); + total -= fatal_test(dbuf, system(cbuf), 0, 0.5); + } else { + sprintf(dbuf, "insmod %s", reader); + sprintf(cbuf, "insmod %s.ko", reader); + total -= fatal_test(dbuf, system(cbuf), 0, 0.5); + } + + gen_params(&uli, speed_set); + fd0 = open(UART0, O_WRONLY); + if (fd0 == -1) + fail("open " UART0); + fd1 = open(UART1, O_RDONLY); + if (fd1 == -1) + fail("open " UART1); + total -= test("ioctl reader", + ioctl(fd1, UART16550_IOCTL_SET_LINE, &uli), 0, 0.5); + total -= test("ioctl writer", + ioctl(fd0, UART16550_IOCTL_SET_LINE, &uli), 0, 0.5); + + for (i = 0; i < num_tests; i++) { + sprintf(dbuf, "test %02d", i + 1); + total -= test(dbuf, copy_test(fd0, fd1, speed_set), 0, 1.5); + } + + close(fd0); + close(fd1); + + if (reader != writer) { + sprintf(dbuf, "rmmod %s", reader); + sprintf(cbuf, "rmmod %s.ko", reader); + total -= fatal_test(dbuf, system(cbuf), 0, 0.5); + sprintf(dbuf, "rmmod %s", writer); + sprintf(cbuf, "rmmod %s.ko", writer); + total -= fatal_test(dbuf, system(cbuf), 0, 0.5); + } else { + sprintf(dbuf, "rmmod %s", reader); + sprintf(cbuf, "rmmod %s.ko", reader); + total -= fatal_test(dbuf, system(cbuf), 0, 0.5); + } + + return total; +} + +#define choose_one(rd, wr) do { \ + int r = rand() % 2; \ + if (r == 0) { \ + rd = MODULE_NAME; \ + wr = SOLUTION_NAME; \ + } else { \ + rd = SOLUTION_NAME; \ + wr = MODULE_NAME; \ + } \ + } while (0) + +static float +test3(void) +{ + const char *rd, *wr; + + rd = MODULE_NAME; + wr = SOLUTION_NAME; + test_title("Test 3. Read, small speed"); + return generic_test(rd, wr, 0, 5); +} + +static float +test4(void) +{ + const char *rd, *wr; + + rd = SOLUTION_NAME; + wr = MODULE_NAME; + test_title("Test 4. Write, small speed"); + return generic_test(rd, wr, 0, 5); +} + +static float +test5(void) +{ + const char *rd, *wr; + + rd = wr = MODULE_NAME; + test_title("Test 5. Back-to-back, small speed"); + return generic_test(rd, wr, 0, 5); +} + +static float +test6(void) +{ + const char *rd, *wr; + + choose_one(rd, wr); + test_title("Test 6. Read/Write, medium speed"); + return generic_test(rd, wr, 1, 5); +} + +static float +test7(void) +{ + const char *rd, *wr; + + rd = wr = MODULE_NAME; + test_title("Test 7. Back-to-back, medium speed"); + return generic_test(rd, wr, 1, 5); +} + +static float +test8(void) +{ + const char *rd, *wr; + + choose_one(rd, wr); + test_title("Test 8. Read/Write, high speed"); + return generic_test(rd, wr, 2, 5); +} + +static float +test9(void) +{ + const char *rd, *wr; + + rd = wr = MODULE_NAME; + test_title("Test 9. Back-to-back, high speed"); + return generic_test(rd, wr, 2, 5); +} + +int +main(void) +{ + float num_passed = 0; + + signal(SIGTERM, sig_handler); + srand(time(NULL)); + make_nodes(); + + num_passed += test1(); + num_passed += test2(); + num_passed += test3(); + num_passed += test4(); + num_passed += test5(); + num_passed += test6(); + num_passed += test7(); + num_passed += test8(); + num_passed += test9(); + + remove_nodes(); + unlink(INFILE); + unlink(OUTFILE); + printf("\nTotal: [%.1f/%d]\n", num_passed, total); + + return 0; +} + +/* Extra 2 lines so the file is the proper size. */ diff --git a/checker/2-uart-checker/_test/uart16550.h b/checker/2-uart-checker/_test/uart16550.h new file mode 100644 index 0000000000000000000000000000000000000000..73008921925769a4081b10d563f84a8bf5ead49b --- /dev/null +++ b/checker/2-uart-checker/_test/uart16550.h @@ -0,0 +1,46 @@ +#ifndef UART16550_H +#define UART16550_H + +#define OPTION_COM1 1 +#define OPTION_COM2 2 +#define OPTION_BOTH 3 + +#define UART16550_COM1_SELECTED 0x01 +#define UART16550_COM2_SELECTED 0x02 + +#define MAX_NUMBER_DEVICES 2 + +#ifndef _UART16550_REGS_H + +#define UART16550_BAUD_1200 96 +#define UART16550_BAUD_2400 48 +#define UART16550_BAUD_4800 24 +#define UART16550_BAUD_9600 12 +#define UART16550_BAUD_19200 6 +#define UART16550_BAUD_38400 3 +#define UART16550_BAUD_56000 2 +#define UART16550_BAUD_115200 1 + +#define UART16550_LEN_5 0x00 +#define UART16550_LEN_6 0x01 +#define UART16550_LEN_7 0x02 +#define UART16550_LEN_8 0x03 + +#define UART16550_STOP_1 0x00 +#define UART16550_STOP_2 0x04 + +#define UART16550_PAR_NONE 0x00 +#define UART16550_PAR_ODD 0x08 +#define UART16550_PAR_EVEN 0x18 +#define UART16550_PAR_STICK 0x20 + +#endif + +#define UART16550_IOCTL_SET_LINE 1 + +struct uart16550_line_info { + unsigned char baud, len, par, stop; +}; + +#endif + diff --git a/checker/3-raid-checker/Makefile b/checker/3-raid-checker/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9eea19fbf00cbb82d1959e828adf44b886735199 --- /dev/null +++ b/checker/3-raid-checker/Makefile @@ -0,0 +1,14 @@ +CFLAGS = -Wall -Wextra -g -m32 +LDFLAGS = -static -m32 + +.PHONY: all clean + +all: + +test: + make -C _test/ + ln -sf _test/run-test run-test + +clean: + -make -C _test/ clean + rm -rf run-test diff --git a/checker/3-raid-checker/README b/checker/3-raid-checker/README new file mode 100644 index 0000000000000000000000000000000000000000..8fb41fd9b25275c7971aee7ea79c37006c0b7b00 --- /dev/null +++ b/checker/3-raid-checker/README @@ -0,0 +1,40 @@ += SOFTWARE RAID TEST SUITE == + +Test suite for software RAID + +== FILES == + +README + * this file + +Makefile + * Makefile for automating the build process + +_checker + * script to run all tests defined in _test/test.c + +_test/test.c + * test suite for software RAID + +== RUNNING == + +In order to run the test suite you can either use the _checker +script or run the run-test executable. + +The kernel module must be named ssr.ko and must be in the current folder. + +The run-test executable has to be in the current folder. You can create +a link using: + + ln -sf _test/run-test run-test + +The _checker script runs all tests and computes assignment grade. You +can use any of the two commands below. + + make test + ./_checker + +In order to run a specific test, pass the test number (1 .. 78) to the +run-test executable. + + ./run-test 5 diff --git a/checker/3-raid-checker/_checker b/checker/3-raid-checker/_checker new file mode 100755 index 0000000000000000000000000000000000000000..da4e4058d736be4eecbd54f4db21695ab5648100 --- /dev/null +++ b/checker/3-raid-checker/_checker @@ -0,0 +1,4 @@ +#!/bin/sh + +/bin/dmesg -c > /dev/null 2>&1 +./run-test diff --git a/checker/3-raid-checker/_test/Makefile b/checker/3-raid-checker/_test/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a8a98a2cb2220d974e4d31ec76af85a70c2a5c8d --- /dev/null +++ b/checker/3-raid-checker/_test/Makefile @@ -0,0 +1,15 @@ +CFLAGS = -Wall -Wextra -Wno-unused-function -g -m32 +LDFLAGS = -static -m32 + +.PHONY: all clean + +all: run-test + +run-test: run-test.o test.o + +run-test.o: run-test.c run-test.h + +test.o: test.c run-test.h + +clean: + -rm -f *~ test.o run-test.o run-test test diff --git a/checker/3-raid-checker/_test/run-test.c b/checker/3-raid-checker/_test/run-test.c new file mode 100644 index 0000000000000000000000000000000000000000..d8f15caf33ee0eb18e842c6d910ba87dfddf7fa0 --- /dev/null +++ b/checker/3-raid-checker/_test/run-test.c @@ -0,0 +1,106 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <time.h> + +#include "run-test.h" + +/* Enable/disable exiting when program fails. */ +//#define EXIT_IF_FAIL + +static size_t test_index; +static size_t total_points = 0; + +static void test_do_fail(size_t points) +{ + printf("failed [ 0/%3zu]\n", max_points); + fflush(stdout); +#ifdef EXIT_IF_FAIL + exit(EXIT_FAILURE); +#endif +} + +static void test_do_pass(size_t points) +{ + total_points += points; + printf("passed [%3zu/%3zu]\n", points, max_points); + fflush(stdout); +} + +void basic_test(int condition) +{ + size_t i; + char *description = test_array[test_index].description; + size_t desc_len = strlen(description); + size_t points = test_array[test_index].points; + + printf("(%3zu) %s", test_index + 1, description); + for (i = 0; i < 56 - desc_len; i++) + printf("."); + if (condition) + test_do_pass(points); + else + test_do_fail(points); +} + +static void print_test_total(void) +{ + size_t i; + + for (i = 0; i < 62; i++) + printf(" "); + printf("Total: [%3zu/%3zu]\n", total_points, max_points); +} + +static void run_test(void) +{ + test_array[test_index].function(); +} + +int main(int argc, char **argv) +{ + size_t num_tests = get_num_tests(); + + if (argc > 2) { + fprintf(stderr, "Usage: %s [test_number]\n", argv[0]); + fprintf(stderr, " 1 <= test_number <= %zu\n", num_tests); + exit(EXIT_FAILURE); + } + + /* Randomize time quantums. */ + srand(time(NULL)); + + /* In case of no arguments run all tests. */ + if (argc == 1) { + init_world(); + for (test_index = 0; test_index < num_tests; test_index++) + run_test(); + print_test_total(); + cleanup_world(); + return 0; + } + + /* If provided, argument is test index. */ + test_index = strtoul(argv[1], NULL, 10); + if (errno == EINVAL || errno == ERANGE) { + fprintf(stderr, "%s is not a number\n", argv[1]); + exit(EXIT_FAILURE); + } + + if (test_index == 0 || test_index > num_tests) { + fprintf(stderr, "Error: Test index is out of range " + "(1 <= test_index <= %zu).\n", num_tests); + exit(EXIT_FAILURE); + } + + /* test_index is one less than what the user provides. */ + test_index--; + + /* Run test_index test. */ + init_world(); + run_test(); + cleanup_world(); + + return 0; +} diff --git a/checker/3-raid-checker/_test/run-test.h b/checker/3-raid-checker/_test/run-test.h new file mode 100644 index 0000000000000000000000000000000000000000..e4d64f6aa1b375c097e045d59d740e755ce5b9d6 --- /dev/null +++ b/checker/3-raid-checker/_test/run-test.h @@ -0,0 +1,25 @@ +#ifndef _RUN_TEST_H_ +#define _RUN_TEST_H_ + +/* functions exported by the framework */ +void basic_test(int condition); + +/* function exported by the test */ +void init_world(void); +void cleanup_world(void); +size_t get_num_tests(void); + +/* test function prototype */ +typedef void (test_f)(void); + +struct run_test_t { + test_f *function; /* test/evaluation function */ + char *description; /* test description */ + size_t points; /* points for each test */ +}; + +/* Use test_index to pass through test_array. */ +extern struct run_test_t test_array[]; +extern size_t max_points; + +#endif /* _RUN_TEST_H_ */ diff --git a/checker/3-raid-checker/_test/ssr.h b/checker/3-raid-checker/_test/ssr.h new file mode 100644 index 0000000000000000000000000000000000000000..5aa4107fb15825ec455a6e68c6884a2e545a5861 --- /dev/null +++ b/checker/3-raid-checker/_test/ssr.h @@ -0,0 +1,26 @@ +/* + * Simple Software Raid - Linux header file + */ + +#ifndef SSR_H_ +#define SSR_H_ 1 + +#define SSR_MAJOR 240 +#define SSR_FIRST_MINOR 0 +#define SSR_NUM_MINORS 1 + +#define PHYSICAL_DISK1_NAME "/dev/vdb" +#define PHYSICAL_DISK2_NAME "/dev/vdc" + +/* sector size */ +#define KERNEL_SECTOR_SIZE 512 + +/* physical partition size - 95 MB (more than this results in error) */ +#define LOGICAL_DISK_NAME "/dev/ssr" +#define LOGICAL_DISK_SIZE (95 * 1024 * 1024) +#define LOGICAL_DISK_SECTORS ((LOGICAL_DISK_SIZE) / (KERNEL_SECTOR_SIZE)) + +/* sync data */ +#define SSR_IOCTL_SYNC 1 + +#endif diff --git a/checker/3-raid-checker/_test/test.c b/checker/3-raid-checker/_test/test.c new file mode 100644 index 0000000000000000000000000000000000000000..b6a36ec618aee137458c48289d667c82d909353a --- /dev/null +++ b/checker/3-raid-checker/_test/test.c @@ -0,0 +1,1769 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <assert.h> + +#include "run-test.h" +#include "ssr.h" + +#define SSR_BASE_NAME "ssr" +#define SSR_LIN_EXT ".ko" +#define SSR_MOD_NAME SSR_BASE_NAME SSR_LIN_EXT + +#define CRC_SIZE 4 + +#define ONE_SECTOR KERNEL_SECTOR_SIZE +#define ONE_PAGE 4096 +#define TWO_PAGES 8192 +#define TEN_PAGES 40960 +#define ONE_MEG 1048576 + +/* Read/write buffers. */ +static unsigned char *log_rd_buf, *log_wr_buf; +static unsigned char *phys1_rd_buf, *phys1_wr_buf; +static unsigned char *phys2_rd_buf, *phys2_wr_buf; +static unsigned char *log_rd_crc, *log_wr_crc; +static unsigned char *phys1_rd_crc, *phys1_wr_crc; +static unsigned char *phys2_rd_crc, *phys2_wr_crc; + +/* File descriptors. */ +static int log_fd, phys1_fd, phys2_fd; + +enum { + START = 0, + MIDDLE, + END +}; + +enum { + PHYS_FILL_DATA = 'P', + LOG_FILL_DATA = 'L', + CORRUPT_DATA = 'C', + PHYS1_DISK_DIRTY_DATA = 'a', + PHYS1_BUF_DIRTY_DATA = 'A', + PHYS2_DISK_DIRTY_DATA = 'b', + PHYS2_BUF_DIRTY_DATA = 'B', + LOG_DISK_DIRTY_DATA = 'd', + LOG_BUF_DIRTY_DATA = 'D', +}; + +/* + * "upgraded" read routine + */ + +static ssize_t xread(int fd, void *buffer, size_t len) +{ + ssize_t ret; + ssize_t n; + + n = 0; + while (n < (ssize_t) len) { + ret = read(fd, (char *) buffer + n, len - n); + if (ret < 0) + return -1; + if (ret == 0) + break; + n += ret; + } + + return n; +} + +/* + * "upgraded" write routine + */ + +static ssize_t xwrite(int fd, const void *buffer, size_t len) +{ + ssize_t ret; + ssize_t n; + + n = 0; + while (n < (ssize_t) len) { + ret = write(fd, (const char *) buffer + n, len - n); + if (ret < 0) + return -1; + if (ret == 0) + break; + n += ret; + } + + return n; +} + +/* + * Compute CRC32. + */ + +static unsigned int crc32(unsigned int seed, + const unsigned char *p, unsigned int len) +{ + size_t i; + unsigned int crc = seed; + + while (len--) { + crc ^= *p++; + for (i = 0; i < 8; i++) + crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0); + } + + return crc; +} + +static void compute_crc(const void *data_buffer, void *crc_buffer, size_t len) +{ + size_t i; + unsigned int crc; + + for (i = 0; i < len; i += ONE_SECTOR) { + crc = crc32(0, (const unsigned char *) data_buffer + i, ONE_SECTOR); + memcpy((char *) crc_buffer + i / ONE_SECTOR * CRC_SIZE, + &crc, CRC_SIZE); + } +} + +static off_t data_offset_from_whence(int whence, size_t len) +{ + switch (whence) { + case START: + return 0; + case MIDDLE: + return LOGICAL_DISK_SIZE / 2 - len; + case END: + return LOGICAL_DISK_SIZE - len; + default: + return -1; + } +} + +static off_t crc_offset_from_whence(int whence, size_t len) +{ + off_t data_offset = data_offset_from_whence(whence, len); + + return LOGICAL_DISK_SIZE + data_offset / ONE_SECTOR * CRC_SIZE; +} + +static void fill_buffer(void *buffer, int c, size_t len) +{ + memset(buffer, c, len); +} + +static void log_fill_buffer(size_t len) +{ + fill_buffer(log_wr_buf, LOG_FILL_DATA, len); +} + +static void phys_fill_buffer(size_t len) +{ + fill_buffer(phys1_wr_buf, PHYS_FILL_DATA, len); + fill_buffer(phys2_wr_buf, PHYS_FILL_DATA, len); +} + +static ssize_t read_whence_data(int fd, void *buffer, size_t len, int whence) +{ + off_t offset = data_offset_from_whence(whence, len); + + lseek(fd, offset, SEEK_SET); + return xread(fd, buffer, len); +} + +static ssize_t read_whence_crc(int fd, void *crc_buffer, size_t data_len, + int whence) +{ + off_t offset = crc_offset_from_whence(whence, data_len); + + lseek(fd, offset, SEEK_SET); + return xread(fd, crc_buffer, data_len / ONE_SECTOR * CRC_SIZE); +} + +static ssize_t write_whence_data(int fd, const void *buffer, + size_t len, int whence) +{ + off_t offset = data_offset_from_whence(whence, len); + + lseek(fd, offset, SEEK_SET); + return xwrite(fd, buffer, len); +} + +static ssize_t write_whence_crc(int fd, void *crc_buffer, size_t data_len, + int whence) +{ + off_t offset = crc_offset_from_whence(whence, data_len); + + lseek(fd, offset, SEEK_SET); + return xwrite(fd, crc_buffer, data_len / ONE_SECTOR * CRC_SIZE); +} + +static ssize_t log_read_whence(size_t len, int whence) +{ + ssize_t n; + + n = read_whence_data(log_fd, log_rd_buf, len, whence); + if (n < 0) + return -1; + compute_crc(log_rd_buf, log_rd_crc, len); + return n; +} + +static ssize_t log_write_whence(size_t len, int whence) +{ + compute_crc(log_wr_buf, log_wr_crc, len); + return write_whence_data(log_fd, log_wr_buf, len, whence); +} + +static ssize_t phys_read_whence(size_t id, size_t len, int whence) +{ + ssize_t n_data, n_crc; + int fd = ((id == 1) ? phys1_fd : phys2_fd); + + unsigned char *data_buf = ((id == 1) ? phys1_rd_buf : phys2_rd_buf); + unsigned char *crc_buf = ((id == 1) ? phys1_rd_crc : phys2_rd_crc); + + n_data = read_whence_data(fd, data_buf, len, whence); + if (n_data < 0) + return -1; + n_crc = read_whence_crc(fd, crc_buf, len, whence); + if (n_crc < 0) + return -1; + return n_data; +} + +static ssize_t phys_write_whence(size_t id, size_t len, int whence) +{ + ssize_t n_data, n_crc; + int fd = ((id == 1) ? phys1_fd : phys2_fd); + unsigned char *data_buf = ((id == 1) ? phys1_wr_buf : phys2_wr_buf); + unsigned char *crc_buf = ((id == 1) ? phys1_wr_crc : phys2_wr_crc); + + compute_crc(data_buf, crc_buf, len); + n_data = write_whence_data(fd, data_buf, len, whence); + if (n_data < 0) + return -1; + n_crc = write_whence_crc(fd, crc_buf, len, whence); + if (n_crc < 0) + return -1; + return n_data; +} + +static void corrupt_buffer(void *buffer, size_t sectors) +{ + size_t i; + + for (i = 0; i < sectors; i++) + ((unsigned char *) buffer)[i * ONE_SECTOR] = CORRUPT_DATA; +} + +static ssize_t phys_corrupt_and_write_whence(size_t id, size_t len, + size_t sectors, int whence) +{ + ssize_t n_data, n_crc; + int fd = ((id == 1) ? phys1_fd : phys2_fd); + unsigned char *data_buf = ((id == 1) ? phys1_wr_buf : phys2_wr_buf); + unsigned char *crc_buf = ((id == 1) ? phys1_wr_crc : phys2_wr_crc); + + compute_crc(data_buf, crc_buf, len); + corrupt_buffer(data_buf, sectors); + n_data = write_whence_data(fd, data_buf, len, whence); + if (n_data < 0) + return -1; + n_crc = write_whence_crc(fd, crc_buf, len, whence); + if (n_crc < 0) + return -1; + return n_data; +} + +static ssize_t log_read_start(size_t len) +{ + return log_read_whence(len, START); +} + +static ssize_t log_read_middle(size_t len) +{ + return log_read_whence(len, MIDDLE); +} + +static ssize_t log_read_end(size_t len) +{ + return log_read_whence(len, END); +} + +static ssize_t log_write_start(size_t len) +{ + return log_write_whence(len, START); +} + +static ssize_t log_write_middle(size_t len) +{ + return log_write_whence(len, MIDDLE); +} + +static ssize_t log_write_end(size_t len) +{ + return log_write_whence(len, END); +} + +static ssize_t phys1_read_start(size_t len) +{ + return phys_read_whence(1, len, START); +} + +#if 0 +static ssize_t phys1_read_middle(size_t len) +{ + return phys_read_whence(1, len, MIDDLE); +} + +static ssize_t phys1_read_end(size_t len) +{ + return phys_read_whence(1, len, END); +} +#endif + +static ssize_t phys1_write_start(size_t len) +{ + return phys_write_whence(1, len, START); +} + +static ssize_t phys1_corrupt_and_write_start(size_t len, size_t sectors) +{ + return phys_corrupt_and_write_whence(1, len, sectors, START); +} + +#if 0 +static ssize_t phys1_write_middle(size_t len) +{ + return phys_write_whence(1, len, MIDDLE); +} + +static ssize_t phys1_write_end(size_t len) +{ + return phys_write_whence(1, len, END); +} +#endif + +static ssize_t phys2_read_start(size_t len) +{ + return phys_read_whence(2, len, START); +} + +#if 0 +static ssize_t phys2_read_middle(size_t len) +{ + return phys_read_whence(2, len, MIDDLE); +} + +static ssize_t phys2_read_end(size_t len) +{ + return phys_read_whence(2, len, END); +} +#endif + +static ssize_t phys2_write_start(size_t len) +{ + return phys_write_whence(2, len, START); +} + +static ssize_t phys2_corrupt_and_write_start(size_t len, size_t sectors) +{ + return phys_corrupt_and_write_whence(2, len, sectors, START); +} + +#if 0 +static ssize_t phys2_write_middle(size_t len) +{ + return phys_write_whence(2, len, MIDDLE); +} + +static ssize_t phys2_write_end(size_t len) +{ + return phys_write_whence(2, len, END); +} +#endif + +static int cmp_data_log_rd_phys1_wr(size_t len) +{ + return memcmp(log_rd_buf, phys1_wr_buf, len); +} + +static int cmp_data_log_rd_phys2_wr(size_t len) +{ + return memcmp(log_rd_buf, phys2_wr_buf, len); +} + +static int cmp_data_log_rd_phys1_rd(size_t len) +{ + return memcmp(log_rd_buf, phys1_rd_buf, len); +} + +static int cmp_data_log_rd_phys2_rd(size_t len) +{ + return memcmp(log_rd_buf, phys2_rd_buf, len); +} + +static int cmp_data_log_wr_phys1_rd(size_t len) +{ + return memcmp(log_wr_buf, phys1_rd_buf, len); +} + +static int cmp_data_log_wr_phys2_rd(size_t len) +{ + return memcmp(log_wr_buf, phys2_rd_buf, len); +} + +static int cmp_crc_log_rd_phys1_wr(size_t data_len) +{ + return memcmp(log_rd_crc, phys1_wr_crc, data_len / ONE_SECTOR * CRC_SIZE); +} + +static int cmp_crc_log_rd_phys2_wr(size_t data_len) +{ + return memcmp(log_rd_crc, phys2_wr_crc, data_len / ONE_SECTOR * CRC_SIZE); +} + +static int cmp_crc_log_rd_phys1_rd(size_t data_len) +{ + return memcmp(log_rd_crc, phys1_rd_crc, data_len / ONE_SECTOR * CRC_SIZE); +} + +static int cmp_crc_log_rd_phys2_rd(size_t data_len) +{ + return memcmp(log_rd_crc, phys2_rd_crc, data_len / ONE_SECTOR * CRC_SIZE); +} + +static int cmp_crc_log_wr_phys1_rd(size_t data_len) +{ + return memcmp(log_wr_crc, phys1_rd_crc, data_len / ONE_SECTOR * CRC_SIZE); +} + +static int cmp_crc_log_wr_phys2_rd(size_t data_len) +{ + return memcmp(log_wr_crc, phys2_rd_crc, data_len / ONE_SECTOR * CRC_SIZE); +} + +static void drop_caches(void) +{ + int fd; + char buf[] = "1\n"; + + fd = open("/proc/sys/vm/drop_caches", O_WRONLY); + assert(fd >= 0); + write(fd, buf, strlen(buf)); + close(fd); +} + +static void flush_disk_buffers(void) +{ + sync(); + //system("/bin/echo 1 > /proc/sys/vm/drop_caches"); + drop_caches(); +} + +static void dump_data(const void *buf, size_t len, const char *header) +{ + size_t i; + + printf("%s:", header); + for (i = 0; i < len / sizeof(unsigned int); i++) { + if (i % 4 == 0) + printf("\n\t"); + printf(" %08x", ((unsigned int *) buf)[i]); + } + printf("\n\n"); +} + +void init_world(void) +{ + /* Cleanup if required. */ + flush_disk_buffers(); + system("/sbin/rmmod " SSR_BASE_NAME " > /dev/null 2>&1"); + system("/bin/cat /proc/devices | /bin/grep " SSR_BASE_NAME " > /dev/null"); + system("/bin/rm -f " LOGICAL_DISK_NAME); + + assert(system("/sbin/insmod " SSR_MOD_NAME) == 0); + assert(system("/bin/cat /proc/devices | /bin/grep " SSR_BASE_NAME + " > /dev/null") == 0); + assert(access(PHYSICAL_DISK1_NAME, F_OK) == 0); + assert(access(PHYSICAL_DISK2_NAME, F_OK) == 0); + assert(access(LOGICAL_DISK_NAME, F_OK) == 0); + + log_rd_buf = calloc(1024 * 1024, 1); + assert(log_rd_buf != NULL); + log_wr_buf = calloc(1024 * 1024, 1); + assert(log_rd_buf != NULL); + phys1_rd_buf = calloc(1024 * 1024, 1); + assert(phys1_rd_buf != NULL); + phys1_wr_buf = calloc(1024 * 1024, 1); + assert(phys1_wr_buf != NULL); + phys2_rd_buf = calloc(1024 * 1024, 1); + assert(phys2_rd_buf != NULL); + phys2_wr_buf = calloc(1024 * 1024, 1); + assert(phys2_wr_buf != NULL); + log_rd_crc = calloc(8 * 1024, 1); + assert(log_rd_crc != NULL); + log_wr_crc = calloc(8 * 1024, 1); + assert(log_rd_crc != NULL); + phys1_rd_crc = calloc(8 * 1024, 1); + assert(phys1_rd_crc != NULL); + phys1_wr_crc = calloc(8 * 1024, 1); + assert(phys1_wr_crc != NULL); + phys2_rd_crc = calloc(8 * 1024, 1); + assert(phys2_rd_crc != NULL); + phys2_wr_crc = calloc(8 * 1024, 1); + assert(phys2_wr_crc != NULL); +} + +void cleanup_world(void) +{ + flush_disk_buffers(); + system("/sbin/rmmod " SSR_BASE_NAME); + system("/bin/cat /proc/devices | /bin/grep " SSR_BASE_NAME " > /dev/null"); + system("/bin/rm -f " LOGICAL_DISK_NAME); + free(log_rd_buf); free(log_wr_buf); + free(phys1_rd_buf); free(phys1_wr_buf); + free(phys2_rd_buf); free(phys2_wr_buf); + free(log_rd_crc); free(log_wr_crc); + free(phys1_rd_crc); free(phys1_wr_crc); + free(phys2_rd_crc); free(phys2_wr_crc); +} + +static void make_disks_dirty(void) +{ + fill_buffer(phys1_wr_buf, PHYS1_DISK_DIRTY_DATA, ONE_MEG); + fill_buffer(phys1_wr_crc, PHYS1_DISK_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); + fill_buffer(phys2_wr_buf, PHYS2_DISK_DIRTY_DATA, ONE_MEG); + fill_buffer(phys2_wr_crc, PHYS2_DISK_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); + phys1_write_start(ONE_MEG); + phys2_write_start(ONE_MEG); +} + +static void make_buffers_dirty(void) +{ + fill_buffer(phys1_wr_buf, PHYS1_BUF_DIRTY_DATA, ONE_MEG); + fill_buffer(phys1_wr_crc, PHYS1_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); + fill_buffer(phys1_rd_buf, PHYS1_BUF_DIRTY_DATA, ONE_MEG); + fill_buffer(phys1_rd_crc, PHYS1_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); + fill_buffer(phys2_wr_buf, PHYS2_BUF_DIRTY_DATA, ONE_MEG); + fill_buffer(phys2_wr_crc, PHYS2_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); + fill_buffer(phys2_rd_buf, PHYS2_BUF_DIRTY_DATA, ONE_MEG); + fill_buffer(phys2_rd_crc, PHYS2_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); + fill_buffer(log_wr_buf, LOG_BUF_DIRTY_DATA, ONE_MEG); + fill_buffer(log_wr_crc, LOG_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); + fill_buffer(log_rd_buf, LOG_BUF_DIRTY_DATA, ONE_MEG); + fill_buffer(log_rd_crc, LOG_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE); +} + +static void init_test(void) +{ + flush_disk_buffers(); + log_fd = open(LOGICAL_DISK_NAME, O_RDWR); + assert(log_fd >= 0); + phys1_fd = open(PHYSICAL_DISK1_NAME, O_RDWR); + assert(phys1_fd >= 0); + phys2_fd = open(PHYSICAL_DISK2_NAME, O_RDWR); + assert(phys2_fd >= 0); + make_disks_dirty(); + make_buffers_dirty(); + flush_disk_buffers(); +} + +static void cleanup_test(void) +{ + close(log_fd); + close(phys1_fd); + close(phys2_fd); +} + +static void open_logical(void) +{ + int fd; + + fd = open(LOGICAL_DISK_NAME, O_RDWR); + basic_test(fd >= 0); + close(fd); +} + +static void close_logical(void) +{ + int fd, rc; + + fd = open(LOGICAL_DISK_NAME, O_RDWR); + rc = close(fd); + basic_test(rc == 0); +} + +static void use_after_close_invalid(void) +{ + int fd, val; + ssize_t n; + + fd = open(LOGICAL_DISK_NAME, O_RDWR); + close(fd); + n = read(fd, &val, sizeof(val)); + basic_test(n < 0); +} + +static void lseek_logical(void) +{ + off_t offset; + + init_test(); + offset = lseek(log_fd, LOGICAL_DISK_SIZE / 2, SEEK_SET); + basic_test(offset == LOGICAL_DISK_SIZE / 2); + cleanup_test(); +} + +static void read_one_sector_start(void) +{ + ssize_t n; + + init_test(); + n = log_read_start(ONE_SECTOR); + basic_test(n == ONE_SECTOR); + cleanup_test(); +} + +static void read_one_sector_middle(void) +{ + ssize_t n; + + init_test(); + n = log_read_middle(ONE_SECTOR); + basic_test(n == ONE_SECTOR); + cleanup_test(); +} + +static void read_one_sector_end(void) +{ + ssize_t n; + + init_test(); + n = log_read_end(ONE_SECTOR); + basic_test(n == ONE_SECTOR); + cleanup_test(); +} + +static void write_one_sector_start(void) +{ + ssize_t n; + + init_test(); + n = log_write_start(ONE_SECTOR); + basic_test(n == ONE_SECTOR); + cleanup_test(); +} + +static void write_one_sector_middle(void) +{ + ssize_t n; + + init_test(); + n = log_write_middle(ONE_SECTOR); + basic_test(n == ONE_SECTOR); + cleanup_test(); +} + +static void write_one_sector_end(void) +{ + ssize_t n; + + init_test(); + n = log_write_end(ONE_SECTOR); + basic_test(n == ONE_SECTOR); + cleanup_test(); +} + +static void read_one_page_start(void) +{ + ssize_t n; + + init_test(); + n = log_read_start(ONE_PAGE); + basic_test(n == ONE_PAGE); + cleanup_test(); +} + +static void read_one_page_middle(void) +{ + ssize_t n; + + init_test(); + n = log_read_middle(ONE_PAGE); + basic_test(n == ONE_PAGE); + cleanup_test(); +} + +static void read_one_page_end(void) +{ + ssize_t n; + + init_test(); + n = log_read_end(ONE_PAGE); + basic_test(n == ONE_PAGE); + cleanup_test(); +} + +static void write_one_page_start(void) +{ + ssize_t n; + + init_test(); + n = log_write_start(ONE_PAGE); + basic_test(n == ONE_PAGE); + cleanup_test(); +} + +static void write_one_page_middle(void) +{ + ssize_t n; + + init_test(); + n = log_write_middle(ONE_PAGE); + basic_test(n == ONE_PAGE); + cleanup_test(); +} + +static void write_one_page_end(void) +{ + ssize_t n; + + init_test(); + n = log_write_end(ONE_PAGE); + basic_test(n == ONE_PAGE); + cleanup_test(); +} + +static void read_two_pages_start(void) +{ + ssize_t n; + + init_test(); + n = log_read_start(TWO_PAGES); + basic_test(n == TWO_PAGES); + cleanup_test(); +} + +static void read_two_pages_middle(void) +{ + ssize_t n; + + init_test(); + n = log_read_middle(TWO_PAGES); + basic_test(n == TWO_PAGES); + cleanup_test(); +} + +static void read_two_pages_end(void) +{ + ssize_t n; + + init_test(); + n = log_read_end(TWO_PAGES); + basic_test(n == TWO_PAGES); + cleanup_test(); +} + +static void write_two_pages_start(void) +{ + ssize_t n; + + init_test(); + n = log_write_start(TWO_PAGES); + basic_test(n == TWO_PAGES); + cleanup_test(); +} + +static void write_two_pages_middle(void) +{ + ssize_t n; + + init_test(); + n = log_write_middle(TWO_PAGES); + basic_test(n == TWO_PAGES); + cleanup_test(); +} + +static void write_two_pages_end(void) +{ + ssize_t n; + + init_test(); + n = log_write_end(TWO_PAGES); + basic_test(n == TWO_PAGES); + cleanup_test(); +} + +static void read_one_meg_start(void) +{ + ssize_t n; + + init_test(); + n = log_read_start(ONE_MEG); + basic_test(n == ONE_MEG); + cleanup_test(); +} + +static void read_one_meg_middle(void) +{ + ssize_t n; + + init_test(); + n = log_read_middle(ONE_MEG); + basic_test(n == ONE_MEG); + cleanup_test(); +} + +static void read_one_meg_end(void) +{ + ssize_t n; + + init_test(); + n = log_read_end(ONE_MEG); + basic_test(n == ONE_MEG); + cleanup_test(); +} + +static void write_one_meg_start(void) +{ + ssize_t n; + + init_test(); + n = log_write_start(ONE_MEG); + basic_test(n == ONE_MEG); + cleanup_test(); +} + +static void write_one_meg_middle(void) +{ + ssize_t n; + + init_test(); + n = log_write_middle(ONE_MEG); + basic_test(n == ONE_MEG); + cleanup_test(); +} + +static void write_one_meg_end(void) +{ + ssize_t n; + + init_test(); + n = log_write_end(ONE_MEG); + basic_test(n == ONE_MEG); + cleanup_test(); +} + +static void read_boundary_one_sector(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xread(log_fd, log_rd_buf, ONE_SECTOR); + basic_test(n == 0); + cleanup_test(); +} + +static void read_boundary_one_page(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xread(log_fd, log_rd_buf, ONE_PAGE); + basic_test(n == 0); + cleanup_test(); +} + +static void read_boundary_two_pages(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xread(log_fd, log_rd_buf, TWO_PAGES); + basic_test(n == 0); + cleanup_test(); +} + +static void read_boundary_one_meg(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xread(log_fd, log_rd_buf, ONE_MEG); + basic_test(n == 0); + cleanup_test(); +} + +static void write_boundary_one_sector(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xwrite(log_fd, log_rd_buf, ONE_SECTOR); + basic_test(n < 0); + cleanup_test(); +} + +static void write_boundary_one_page(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xwrite(log_fd, log_wr_buf, ONE_PAGE); + basic_test(n < 0); + cleanup_test(); +} + +static void write_boundary_two_pages(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xwrite(log_fd, log_wr_buf, TWO_PAGES); + basic_test(n < 0); + cleanup_test(); +} + +static void write_boundary_one_meg(void) +{ + ssize_t n; + + init_test(); + lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET); + n = xwrite(log_fd, log_wr_buf, ONE_MEG); + basic_test(n < 0); + cleanup_test(); +} + +static size_t get_free_memory(void) +{ + FILE *f; + size_t i; + char buf[256]; + char *p; + + f = fopen("/proc/meminfo", "rt"); + assert(f != NULL); + /* Second line is 'MemFree: ...' */ + fgets(buf, 256, f); + fgets(buf, 256, f); + fclose(f); + + p = NULL; + for (i = 0; i < 256; i++) + if (buf[i] == ':') { + p = buf+i+1; + break; + } + + return strtoul(p, NULL, 10); +} + +static void memory_is_freed(void) +{ + size_t mem_used_before, mem_used_after; + size_t i; + + init_test(); + mem_used_before = get_free_memory(); + for (i = 0; i < 5; i++) + log_write_start(ONE_MEG); + mem_used_after = get_free_memory(); + + /* We assume 3MB (3072KB) is a reasonable memory usage in writes. */ + basic_test(mem_used_after < mem_used_before + 3072 && + mem_used_before < mem_used_after + 3072); + cleanup_test(); +} + +static void write_one_sector_check_phys1(void) +{ + int rc; + size_t len = ONE_SECTOR; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_data_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_one_page_check_phys1(void) +{ + int rc; + size_t len = ONE_PAGE; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_data_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_two_pages_check_phys1(void) +{ + int rc; + size_t len = TWO_PAGES; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_data_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_one_meg_check_phys1(void) +{ + int rc; + size_t len = ONE_MEG; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_data_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_one_sector_check_phys(void) +{ + int rc1, rc2; + size_t len = ONE_SECTOR; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_data_log_wr_phys1_rd(len); + rc2 = cmp_data_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void write_one_page_check_phys(void) +{ + int rc1, rc2; + size_t len = ONE_PAGE; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_data_log_wr_phys1_rd(len); + rc2 = cmp_data_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void write_two_pages_check_phys(void) +{ + int rc1, rc2; + size_t len = TWO_PAGES; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_data_log_wr_phys1_rd(len); + rc2 = cmp_data_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void write_one_meg_check_phys(void) +{ + int rc1, rc2; + size_t len = ONE_MEG; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_data_log_wr_phys1_rd(len); + rc2 = cmp_data_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void read_one_sector_after_write(void) +{ + int rc; + size_t len = ONE_SECTOR; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc = cmp_data_log_rd_phys1_wr(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void read_one_page_after_write(void) +{ + int rc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc = cmp_data_log_rd_phys1_wr(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void read_two_pages_after_write(void) +{ + int rc; + size_t len = TWO_PAGES; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc = cmp_data_log_rd_phys1_wr(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void read_one_meg_after_write(void) +{ + int rc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc = cmp_data_log_rd_phys1_wr(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_one_sector_check_phys1_crc(void) +{ + int rc; + size_t len = ONE_SECTOR; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_crc_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_one_page_check_phys1_crc(void) +{ + int rc; + size_t len = ONE_PAGE; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_crc_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_two_pages_check_phys1_crc(void) +{ + int rc; + size_t len = TWO_PAGES; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_crc_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_one_meg_check_phys1_crc(void) +{ + int rc; + size_t len = ONE_MEG; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc = cmp_crc_log_wr_phys1_rd(len); + basic_test(rc == 0); + cleanup_test(); +} + +static void write_one_sector_check_phys_crc(void) +{ + int rc1, rc2; + size_t len = ONE_SECTOR; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_crc_log_wr_phys1_rd(len); + rc2 = cmp_crc_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void write_one_page_check_phys_crc(void) +{ + int rc1, rc2; + size_t len = ONE_PAGE; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_crc_log_wr_phys1_rd(len); + rc2 = cmp_crc_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void write_two_pages_check_phys_crc(void) +{ + int rc1, rc2; + size_t len = TWO_PAGES; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_crc_log_wr_phys1_rd(len); + rc2 = cmp_crc_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void write_one_meg_check_phys_crc(void) +{ + int rc1, rc2; + size_t len = ONE_MEG; + + init_test(); + log_fill_buffer(len); + log_write_start(len); + flush_disk_buffers(); + phys1_read_start(len); + phys2_read_start(len); + rc1 = cmp_crc_log_wr_phys1_rd(len); + rc2 = cmp_crc_log_wr_phys2_rd(len); + basic_test(rc1 == 0 && rc2 == 0); + cleanup_test(); +} + +static void corrupt_read_correct_one_sector_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_SECTOR; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, 1); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys2_wr(len); + rc_crc = cmp_crc_log_rd_phys2_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_one_sector_in_page_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, 1); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys2_wr(len); + rc_crc = cmp_crc_log_rd_phys2_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_one_page_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys2_wr(len); + rc_crc = cmp_crc_log_rd_phys2_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_ten_pages_in_one_meg_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys2_wr(len); + rc_crc = cmp_crc_log_rd_phys2_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_one_meg_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys2_wr(len); + rc_crc = cmp_crc_log_rd_phys2_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_sector_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_SECTOR; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, 1); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc_data = cmp_data_log_rd_phys1_rd(len); + rc_crc = cmp_crc_log_rd_phys1_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_sector_in_page_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, 1); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc_data = cmp_data_log_rd_phys1_rd(len); + rc_crc = cmp_crc_log_rd_phys1_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_page_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc_data = cmp_data_log_rd_phys1_rd(len); + rc_crc = cmp_crc_log_rd_phys1_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_ten_page_in_one_meg_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc_data = cmp_data_log_rd_phys1_rd(len); + rc_crc = cmp_crc_log_rd_phys1_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_meg_disk1(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR); + phys2_write_start(len); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys1_read_start(len); + rc_data = cmp_data_log_rd_phys1_rd(len); + rc_crc = cmp_crc_log_rd_phys1_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_one_sector_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_SECTOR; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, 1); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys1_wr(len); + rc_crc = cmp_crc_log_rd_phys1_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_one_sector_in_page_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, 1); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys1_wr(len); + rc_crc = cmp_crc_log_rd_phys1_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_one_page_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys1_wr(len); + rc_crc = cmp_crc_log_rd_phys1_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void corrupt_read_correct_ten_pages_in_one_meg_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys1_wr(len); + rc_crc = cmp_crc_log_rd_phys1_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + flush_disk_buffers(); + cleanup_test(); +} + +static void corrupt_read_correct_one_meg_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR); + flush_disk_buffers(); + log_read_start(len); + rc_data = cmp_data_log_rd_phys1_wr(len); + rc_crc = cmp_crc_log_rd_phys1_wr(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_sector_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_SECTOR; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, 1); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys2_read_start(len); + rc_data = cmp_data_log_rd_phys2_rd(len); + rc_crc = cmp_crc_log_rd_phys2_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_sector_in_page_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, 1); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys2_read_start(len); + rc_data = cmp_data_log_rd_phys2_rd(len); + rc_crc = cmp_crc_log_rd_phys2_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_page_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_PAGE; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys2_read_start(len); + rc_data = cmp_data_log_rd_phys2_rd(len); + rc_crc = cmp_crc_log_rd_phys2_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_ten_page_in_one_meg_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys2_read_start(len); + rc_data = cmp_data_log_rd_phys2_rd(len); + rc_crc = cmp_crc_log_rd_phys2_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void recover_one_meg_disk2(void) +{ + int rc_data, rc_crc; + size_t len = ONE_MEG; + + init_test(); + phys_fill_buffer(len); + phys1_write_start(len); + phys2_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR); + flush_disk_buffers(); + log_read_start(len); + flush_disk_buffers(); + phys2_read_start(len); + rc_data = cmp_data_log_rd_phys2_rd(len); + rc_crc = cmp_crc_log_rd_phys2_rd(len); + basic_test(rc_data == 0 && rc_crc == 0); + cleanup_test(); +} + +static void dual_error(void) +{ + ssize_t n; + size_t len = ONE_SECTOR; + + init_test(); + phys_fill_buffer(len); + phys1_corrupt_and_write_start(len, 1); + phys2_corrupt_and_write_start(len, 1); + flush_disk_buffers(); + n = log_read_start(len); + basic_test(n <= 0); + cleanup_test(); +} + +struct run_test_t test_array[] = { + { open_logical, "open(" LOGICAL_DISK_NAME ")", 4 }, + { close_logical, "close(" LOGICAL_DISK_NAME ")", 4 }, + { use_after_close_invalid, "use after close is invalid", 4 }, + { lseek_logical, "lseek(" LOGICAL_DISK_NAME ")", 4 }, + { read_one_sector_start, "read one sector from the start", 5 }, + { read_one_sector_middle, "read one sector from the middle", 5 }, + { read_one_sector_end, "read one sector from the end", 5 }, + { write_one_sector_start, "write one sector from the start", 5 }, + { write_one_sector_middle, "write one sector from the middle", 5 }, + { write_one_sector_end, "write one sector from the end", 5 }, + { read_one_page_start, "read one page from the start", 5 }, + { read_one_page_middle, "read one page from the middle", 5 }, + { read_one_page_end, "read one page from the end", 5 }, + { write_one_page_start, "write one page from the start", 5 }, + { write_one_page_middle, "write one page from the middle", 5 }, + { write_one_page_end, "write one page from the end", 5 }, + { read_two_pages_start, "read two pages from the start", 5 }, + { read_two_pages_middle, "read two pages from the middle", 5 }, + { read_two_pages_end, "read two pages from the end", 5 }, + { write_two_pages_start, "write two pages from the start", 5 }, + { write_two_pages_middle, "write two pages from the middle", 5 }, + { write_two_pages_end, "write two pages from the end", 5 }, + { read_one_meg_start, "read 1MB from the start", 5 }, + { read_one_meg_middle, "read 1MB from the middle", 5 }, + { read_one_meg_end, "read 1MB from the end", 5 }, + { write_one_meg_start, "write 1MB from the start", 5 }, + { write_one_meg_middle, "write 1MB from the middle", 5 }, + { write_one_meg_end, "write 1MB from the end", 5 }, + { read_boundary_one_sector, "read one sector outside boundary", 7 }, + { read_boundary_one_page, "read one page with contents outside boundary", 7 }, + { read_boundary_two_pages, "read two pages with contents outside boundary", 7 }, + { read_boundary_one_meg, "read 1MB with contents outside boundary", 7 }, + { write_boundary_one_sector, "write one sector outside boundary", 7 }, + { write_boundary_one_page, "write one page with contents outside boundary", 7 }, + { write_boundary_two_pages, "write two pages with contents outside boundary", 7 }, + { write_boundary_one_meg, "write 1MB with contents outside boundary", 7 }, + { memory_is_freed, "check memory is freed", 24 }, + { write_one_sector_check_phys1, "write one sector and check disk1 (no CRC check)", 15 }, + { write_one_page_check_phys1, "write one page and check disk1 (no CRC check)", 15 }, + { write_two_pages_check_phys1, "write two pages and check disk1 (no CRC check)", 15 }, + { write_one_meg_check_phys1, "write 1MB and check disk1 (no CRC check)", 15 }, + { write_one_sector_check_phys, "write one sector and check disks (no CRC check)", 15 }, + { write_one_page_check_phys, "write one page and check disks (no CRC check)", 15 }, + { write_two_pages_check_phys, "write two pages and check disks (no CRC check)", 15 }, + { write_one_meg_check_phys, "write 1MB and check disks (no CRC check)", 15 }, + { read_one_sector_after_write, "read one sector after physical write (correct CRC)", 16 }, + { read_one_page_after_write, "read one page after physical write (correct CRC)", 16 }, + { read_two_pages_after_write, "read two pages after physical write (correct CRC)", 16 }, + { read_one_meg_after_write, "read 1MB after physical write (correct CRC)", 16 }, + { write_one_sector_check_phys1_crc, "write one sector and check disk1 (do CRC check)", 16 }, + { write_one_page_check_phys1_crc, "write one page and check disk1 (do CRC check)", 16 }, + { write_two_pages_check_phys1_crc, "write two pages and check disk1 (do CRC check)", 16 }, + { write_one_meg_check_phys1_crc, "write 1MB and check disk1 (do CRC check)", 16 }, + { write_one_sector_check_phys_crc, "write one sector and check disks (do CRC check)", 16 }, + { write_one_page_check_phys_crc, "write one page and check disks (do CRC check)", 16 }, + { write_two_pages_check_phys_crc, "write two pages and check disks (do CRC check)", 16 }, + { write_one_meg_check_phys_crc, "write 1MB and check disks (do CRC check)", 16 }, + { corrupt_read_correct_one_sector_disk1, "read corrected one sector error from disk1", 18 }, + { corrupt_read_correct_one_sector_in_page_disk1, "read corrected one sector in page error from disk1", 18 }, + { corrupt_read_correct_one_page_disk1, "read corrected one page error from disk1", 18 }, + { corrupt_read_correct_ten_pages_in_one_meg_disk1, "read corrected ten pages error in one meg from disk1", 18 }, + { corrupt_read_correct_one_meg_disk1, "read corrected one meg error from disk1", 18 }, + { recover_one_sector_disk1, "recover one sector error from disk1", 18 }, + { recover_one_sector_in_page_disk1, "recover one sector error in one page from disk1", 18 }, + { recover_one_page_disk1, "recover one page filled with errors from disk1", 18 }, + { recover_ten_page_in_one_meg_disk1, "recover ten pages error in 1MB from disk1", 18 }, + { recover_one_meg_disk1, "recover 1MB filled with errors from disk1", 18 }, + { corrupt_read_correct_one_sector_disk2, "read corrected one sector error from disk2", 18 }, + { corrupt_read_correct_one_sector_in_page_disk2, "read corrected one sector in page error from disk2", 18 }, + { corrupt_read_correct_one_page_disk2, "read corrected one page error from disk2", 18 }, + { corrupt_read_correct_ten_pages_in_one_meg_disk2, "read corrected ten pages error in one meg from disk2", 18 }, + { corrupt_read_correct_one_meg_disk2, "read corrected one meg error from disk2", 18 }, + { recover_one_sector_disk2, "recover one sector error from disk2", 18 }, + { recover_one_sector_in_page_disk2, "recover one sector error in one page from disk2", 18 }, + { recover_one_page_disk2, "recover one page filled with errors from disk2", 18 }, + { recover_ten_page_in_one_meg_disk2, "recover ten pages error in 1MB from disk2", 18 }, + { recover_one_meg_disk2, "recover 1MB filled with errors from disk2", 18 }, + { dual_error, "signal error when both physical disks are corrupted", 12 }, +}; +size_t max_points = 900; + +/* Return number of tests in test_array. */ +size_t get_num_tests(void) +{ + return sizeof(test_array) / sizeof(test_array[0]); +} diff --git a/checker/4-stp-checker/Makefile b/checker/4-stp-checker/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e0c2b42807f22f2a94bf684002d7fa6a86aa8891 --- /dev/null +++ b/checker/4-stp-checker/Makefile @@ -0,0 +1,17 @@ +objects = _test/stp_test.o + +.PHONY: all clean _test_subdir_all _test_subdir_clean + +all: stp_test + +stp_test: _test_subdir_all $(objects) + $(CC) -Wall -g -m32 -static $(objects) -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -o $@ + +_test_subdir_all: + make -C _test + +clean: _test_subdir_clean + -rm -f stp_test *~ + +_test_subdir_clean: + make -C _test clean diff --git a/checker/4-stp-checker/README b/checker/4-stp-checker/README new file mode 100644 index 0000000000000000000000000000000000000000..a1d04d84f8653e98d3812f0a9ecbfa26d64c5956 --- /dev/null +++ b/checker/4-stp-checker/README @@ -0,0 +1,87 @@ += STP TEST SUITE == + +Test suite for SO2 Tranport Protocol + +== FILES == + +README + * this file + +Makefile + +_checker + * script to run all tests defined in _test/stp_test.c + +_test/Makefile + * test suite internal Makefile (creates necessary object files) + +_test/stp_test.c + * test suite for SO2 Transport Protocol + +_test/stp_test.h + * test suite header file + +_test/stp.h + * SO2 Transport Protocol header file (macros and structures) + +_test/test.h + * useful macros for testing + +_test/debug.h + * debugging macros + +_test/util.h + * useful macros for generic use (error processing) + +== BUILDING == + + +== RUNNING == + +Copy your af_stp.ko module and _checker and stp_test +to fsimg/root directory on your QEMU/KVM virtual machine. + +In order to run the test suite you can either use the _checker +script or run the stp_test executable. + +The _checker script runs all tests and computes assignment grade: + + ./_checker + +In order to run a specific test pass the test number (1 .. 32) to the +stp_test executable. + + ./stp_test 5 + +== TESTS == + +Tests are basically unit tests. A single function in the test_fun_array (see +stp_test.c) is called each time the stp_test executable is invoked, +testing a single functionality (and assuming previous tests have been run and +passed). + +The EXIT_IF_FAIL macro (see test.h) is unnecessary since after each test, the +program completes. + +Each test function follows the unit test pattern: initialization, action, +evaluation. The test macro (see test.h) is invoked at the end of each test +for evaluating and grading the test. + +== DEBUGGING == + +The debug.h header file consists of several macros useful for debugging +(dprintf, dlog). There are multiple uses of these macros throughout the above +files. + +In order to turn debug messages on, you must define the DEBUG macro, either in +a header file, or, I suggest, in the Makefile. The LOG_LEVEL macro limits the +log message types that are to be printed, by default LOG_WARNING (see enum in +debug.h). You may redefine it in a header file or in the Makefile. + +Rapid enabling of debug messages is achieved by commenting out the CPPFLAGS +line in the Makefile. It turns on debugging and enables all log messages +(LOG_DEBUG). + +== OTHER == + +srand48() and drand48() are used for generating random numbers. diff --git a/checker/4-stp-checker/_checker b/checker/4-stp-checker/_checker new file mode 100755 index 0000000000000000000000000000000000000000..810e77f5e98e5337f30ad6e54d707baacaca1f4f --- /dev/null +++ b/checker/4-stp-checker/_checker @@ -0,0 +1,24 @@ +#!/bin/sh + +first_test=1 +last_test=32 +executable=stp_test + +for i in $(seq $first_test $last_test); do + ./"$executable" $i +done | tee results.txt + +cat results.txt | grep '\[.*\]$' | awk -F '[] /[]+' ' +BEGIN { + sum=0 +} + +{ + sum += $2; +} + +END { + printf "\n%66s [%d/100]\n", "Total:", sum; +}' + +rm -f results.txt diff --git a/checker/4-stp-checker/_test/Makefile b/checker/4-stp-checker/_test/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d5074dd464a5f5fa80cf42324e336ebdb975f529 --- /dev/null +++ b/checker/4-stp-checker/_test/Makefile @@ -0,0 +1,11 @@ +#CPPFLAGS = -DDEBUG -DLOG_LEVEL=LOG_DEBUG +CFLAGS = -Wall -g -m32 + +.PHONY: all clean + +all: stp_test.o + +stp_test.o: stp_test.c stp_test.h stp.h test.h util.h debug.h + +clean: + -rm -f *~ *.o diff --git a/checker/4-stp-checker/_test/debug.h b/checker/4-stp-checker/_test/debug.h new file mode 100644 index 0000000000000000000000000000000000000000..a54e96226471814a6f8595bc0a3c59cce37c5210 --- /dev/null +++ b/checker/4-stp-checker/_test/debug.h @@ -0,0 +1,77 @@ +/* + * debugging macros + * heavily inspired by previous work and Internet resources + * + * uses C99 variadic macros + * uses non-standard usage of the token-paste operator (##) for + * removing the comma symbol (,) when not followed by a token + * uses non-standard __FUNCTION__ macro (MSVC doesn't support __func__) + * tested on gcc 4.4.5 and Visual Studio 2008 (9.0), compiler version 15.00 + * + * Razvan Deaconescu, razvan.deaconescu@cs.pub.ro + */ + +#ifndef DEBUG_H_ +#define DEBUG_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> + +/* log levels */ +enum { + LOG_EMERG = 1, + LOG_ALERT, + LOG_CRIT, + LOG_ERR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFO, + LOG_DEBUG +}; + +/* + * initialize default loglevel (for dlog) + * may be redefined in the including code + */ + +#ifndef LOG_LEVEL +#define LOG_LEVEL LOG_WARNING +#endif + +/* + * define DEBUG macro as a compiler option: + * -DDEBUG for GCC + * /DDEBUG for MSVC + */ + +#if defined DEBUG +#define dprintf(format, ...) \ + fprintf(stderr, " [%s(), %s:%u] " format, \ + __FUNCTION__, __FILE__, __LINE__, \ + ##__VA_ARGS__) +#else +#define dprintf(format, ...) \ + do { \ + } while (0) +#endif + +#if defined DEBUG +#define dlog(level, format, ...) \ + do { \ + if (level <= LOG_LEVEL) \ + dprintf(format, ##__VA_ARGS__); \ + } while (0) +#else +#define dlog(level, format, ...) \ + do { \ + } while (0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/checker/4-stp-checker/_test/stp.h b/checker/4-stp-checker/_test/stp.h new file mode 100644 index 0000000000000000000000000000000000000000..838f9936bf55c535d5e6ed640bb5e617effd1a26 --- /dev/null +++ b/checker/4-stp-checker/_test/stp.h @@ -0,0 +1,51 @@ +/* + * SO2 Transport Protocol + */ + +#ifndef STP_H_ +#define STP_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include <linux/types.h> + +/* STP reuses the defines of ancient protocols like Econet and Xerox PUP + * because adding a new protocol would involve patching the kernel, which we + * don't want to do and besides that, they are probably not used anymore. + */ +#define AF_STP 19 +#define PF_STP AF_STP +#define ETH_P_STP 0x0a00 + +struct stp_hdr { + __be16 dst; /* Destination port */ + __be16 src; /* Source port */ + __be16 len; /* Total length, including header */ + __u8 flags; /* */ + __u8 csum; /* xor of all bytes, including header */ +}; + +struct sockaddr_stp { + unsigned short sas_family; /* Always AF_STP */ + int sas_ifindex; /* Interface index */ + __be16 sas_port; /* Port */ + __u8 sas_addr[6]; /* MAC address */ +}; + +/* STP protocol name; used as identifier in /proc/net/protocols */ +#define STP_PROTO_NAME "STP" + +/* + * STP uses proc interface to communicate statistical information to + * user space (in /proc/net/). + */ +#define STP_PROC_NET_FILENAME "stp_stats" +#define STP_PROC_FULL_FILENAME "/proc/net/" STP_PROC_NET_FILENAME + +#ifdef __cplusplus +} +#endif + +#endif /* STP_H_ */ diff --git a/checker/4-stp-checker/_test/stp_test.c b/checker/4-stp-checker/_test/stp_test.c new file mode 100644 index 0000000000000000000000000000000000000000..d6c729e344e634a0a90eced63918d7953cb8faf6 --- /dev/null +++ b/checker/4-stp-checker/_test/stp_test.c @@ -0,0 +1,1331 @@ +/* + * SO2 Transport Protocol - test suite + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <assert.h> +#include <time.h> +#include <signal.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ether.h> +#include <net/if.h> +#include <assert.h> +#include <sys/wait.h> +#include <semaphore.h> +#include <fcntl.h> + +#include "test.h" +#include "debug.h" +#include "util.h" + +#include "stp.h" +#include "stp_test.h" + +#define SSA struct sockaddr +#define BUFLEN 32 + +/* declared in test.h; used for printing information in test macro */ +int max_points = 100; + +/* values read from STP_PROC_FULL_FILENAME */ +static int rx_pkts, hdr_err, csum_err, no_sock, no_buffs, tx_pkts; + +enum socket_action { + ACTION_SENDTO, + ACTION_SENDMSG, + ACTION_SEND, + ACTION_SENDTO_PING_PONG, + ACTION_SENDMSG_PING_PONG, + ACTION_SEND_PING_PONG, +}; + +/* + * Do initialization for STP test functions. + */ + +static void init_test(void) +{ + system("insmod " MODULE_FILENAME); +} + +/* + * Do cleanup for STP test functions. + */ + +static void cleanup_test(void) +{ + system("rmmod " MODULE_NAME); +} + +/* + * Check for successful module insertion and removal from the kernel. + */ + +static void test_insmod_rmmod(void) +{ + int rc; + + rc = system("insmod " MODULE_FILENAME); + test("test_insmod", rc == 0, 1); + + rc = system("rmmod " MODULE_NAME); + test("test_rmmod", rc == 0, 1); + + rc = system("insmod " MODULE_FILENAME); + test(__FUNCTION__, rc == 0, 1); + + system("rmmod " MODULE_NAME); +} + +/* + * Check /proc/net/protocols for STP protocol. Grep for line starting with + * the string identified by STP_PROTO_NAME. + */ + +static void test_proto_name_exists_after_insmod(void) +{ + int rc; + + init_test(); + + rc = system("grep '^" STP_PROTO_NAME "' /proc/net/protocols > /dev/null 2>&1"); + test(__FUNCTION__, rc == 0, 2); + + cleanup_test(); +} + +/* + * STP entry in /proc/net/protocols is deleted when module is removed. + */ + +static void test_proto_name_inexistent_after_rmmod(void) +{ + int rc; + + init_test(); + cleanup_test(); + + rc = system("grep '^" STP_PROTO_NAME "' /proc/net/protocols > /dev/null 2>&1"); + test(__FUNCTION__, rc != 0, 2); +} + +/* + * Check for proc entry for STP statistics. + */ + +static void test_proc_entry_exists_after_insmod(void) +{ + int rc; + + init_test(); + + rc = access(STP_PROC_FULL_FILENAME, F_OK); + test(__FUNCTION__, rc == 0, 2); + + cleanup_test(); +} + +/* + * STP statistics file in /proc/net/ is deleted when module is removed. + */ + +static void test_proc_entry_inexistent_after_rmmod(void) +{ + int rc; + + init_test(); + cleanup_test(); + + rc = system("file " STP_PROC_FULL_FILENAME " > /dev/null 2>&1"); + test(__FUNCTION__, rc != 0, 2); +} + +/* + * Call socket(2) with proper arguments for creating an AF_STP socket. + */ + +static void test_socket(void) +{ + int s; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + test(__FUNCTION__, s > 0, 5); + + close(s); + cleanup_test(); +} + +/* + * Create two AF_STP sockets using socket(2). + */ + +static void test_two_sockets(void) +{ + int s1, s2; + + init_test(); + + s1 = socket(AF_STP, SOCK_DGRAM, 0); + s2 = socket(AF_STP, SOCK_DGRAM, 0); + test(__FUNCTION__, s1 > 0 && s2 > 0 && s1 != s2, 2); + + close(s1); + close(s2); + cleanup_test(); +} + +/* + * Pass bad socket type argument to socket(2) (second argument). + * Call should fail. + */ + +static void test_socket_bad_socket_type(void) +{ + int s; + + init_test(); + + s = socket(AF_STP, SOCK_STREAM, 0); + test(__FUNCTION__, s < 0, 1); + + close(s); + cleanup_test(); +} + +/* + * Pass bad protocol argument to socket(2) (third argument). + * Call should fail. + */ + +static void test_socket_bad_protocol(void) +{ + int s; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, IPPROTO_TCP); + test(__FUNCTION__, s < 0, 1); + + close(s); + cleanup_test(); +} + +/* + * Close open socket using close(2). + */ + +static void test_close(void) +{ + int s; + int rc; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + rc = close(s); + test(__FUNCTION__, rc == 0, 2); + + cleanup_test(); +} + +/* + * Pass closed socket descriptor to close(2). Call should fail. + */ + +static void test_close_closed_socket(void) +{ + int s; + int rc; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + close(s); + rc = close(s); + + test(__FUNCTION__, rc < 0, 2); + + cleanup_test(); +} + +/* + * Bind socket to proper address. Use "all" interface. + */ + +static void test_bind(void) +{ + int s; + int rc; + struct sockaddr_stp sas; + const unsigned short port = 12345; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = 0; + rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc == 0, 5); + + close(s); + cleanup_test(); +} + +/* + * Bind socket to proper address. Use "eth0" interface. + */ + +static void test_bind_eth0(void) +{ + int s; + int rc; + struct sockaddr_stp sas; + const unsigned short port = 12345; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("eth0"); + rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc == 0, 2); + + close(s); + cleanup_test(); +} + +/* + * Use bind(2) on two AF_STP sockets. + */ + +static void test_two_binds(void) +{ + int s1, s2; + int rc1, rc2; + struct sockaddr_stp sas1, sas2; + const unsigned short port1 = 12345, port2 = 54321; + + init_test(); + + s1 = socket(AF_STP, SOCK_DGRAM, 0); + + sas1.sas_family = AF_STP; + sas1.sas_port = htons(port1); + sas1.sas_ifindex = 0; + rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp)); + + s2 = socket(AF_STP, SOCK_DGRAM, 0); + + sas2.sas_family = AF_STP; + sas2.sas_port = htons(port2); + sas2.sas_ifindex = 0; + rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc1 == 0 && rc2 == 0, 2); + + close(s1); close(s2); + cleanup_test(); +} + +/* + * Pass bad address to bind(2) (second argument). + * Call should fail. + */ + +static void test_bind_bad_address(void) +{ + int s; + int rc; + struct sockaddr_stp sas; + const unsigned short port = 12345; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_INET; /* invalid */ + sas.sas_port = htons(port); + sas.sas_ifindex = 0; + rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc != 0, 1); + + close(s); + cleanup_test(); +} + +/* + * Use bind(2) on two AF_STP sockets using same port and "all" interface. + * Call should fail. + */ + +static void test_two_binds_same_if(void) +{ + int s1, s2; + int rc1, rc2; + struct sockaddr_stp sas1, sas2; + const unsigned short port = 12345; + + init_test(); + + s1 = socket(AF_STP, SOCK_DGRAM, 0); + + sas1.sas_family = AF_STP; + sas1.sas_port = htons(port); + sas1.sas_ifindex = 0; + rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp)); + + s2 = socket(AF_STP, SOCK_DGRAM, 0); + + sas2.sas_family = AF_STP; + sas2.sas_port = htons(port); + sas2.sas_ifindex = 0; + rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2); + + close(s1); close(s2); + cleanup_test(); +} + +/* + * Use bind(2) on two AF_STP sockets using same port and same interface. + * Call should fail. + */ + +static void test_two_binds_same_if_eth0(void) +{ + int s1, s2; + int rc1, rc2; + struct sockaddr_stp sas1, sas2; + const unsigned short port = 12345; + + init_test(); + + s1 = socket(AF_STP, SOCK_DGRAM, 0); + + sas1.sas_family = AF_STP; + sas1.sas_port = htons(port); + sas1.sas_ifindex = if_nametoindex("eth0"); + rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp)); + + s2 = socket(AF_STP, SOCK_DGRAM, 0); + + sas2.sas_family = AF_STP; + sas2.sas_port = htons(port); + sas2.sas_ifindex = if_nametoindex("eth0"); + rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2); + + close(s1); close(s2); + cleanup_test(); +} + +/* + * Use bind(2) on two AF_STP sockets using same port and "all" interface and + * "eth0". + * Call should fail. + */ + +static void test_two_binds_same_if_all_eth0(void) +{ + int s1, s2; + int rc1, rc2; + struct sockaddr_stp sas1, sas2; + const unsigned short port = 12345; + + init_test(); + + s1 = socket(AF_STP, SOCK_DGRAM, 0); + + sas1.sas_family = AF_STP; + sas1.sas_port = htons(port); + sas1.sas_ifindex = 0; + rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp)); + + s2 = socket(AF_STP, SOCK_DGRAM, 0); + + sas2.sas_family = AF_STP; + sas2.sas_port = htons(port); + sas2.sas_ifindex = if_nametoindex("eth0"); + rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2); + + close(s1); close(s2); + cleanup_test(); +} + +/* + * Use bind(2) on two AF_STP sockets using same port and "eth0" interface and + * "all". + * Call should fail. + */ + +static void test_two_binds_same_if_eth0_all(void) +{ + int s1, s2; + int rc1, rc2; + struct sockaddr_stp sas1, sas2; + const unsigned short port = 12345; + + init_test(); + + s1 = socket(AF_STP, SOCK_DGRAM, 0); + + sas1.sas_family = AF_STP; + sas1.sas_port = htons(port); + sas1.sas_ifindex = if_nametoindex("eth0"); + rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp)); + + s2 = socket(AF_STP, SOCK_DGRAM, 0); + + sas2.sas_family = AF_STP; + sas2.sas_port = htons(port); + sas2.sas_ifindex = 0; + rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp)); + + test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2); + + close(s1); close(s2); + cleanup_test(); +} + +static ssize_t sendto_message(int sockfd, struct sockaddr_stp *sas, + char *buf, size_t len) +{ + return sendto(sockfd, buf, len, 0, (SSA *) sas, sizeof(*sas)); +} + +static ssize_t sendmsg_message(int sockfd, struct sockaddr_stp *sas, + char *buf, size_t len) +{ + struct iovec iov; + struct msghdr msg; + + iov.iov_base = buf; + iov.iov_len = len; + msg.msg_name = sas; + msg.msg_namelen = sizeof(*sas); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + return sendmsg(sockfd, &msg, 0); +} + +static ssize_t send_message(int sockfd, char *buf, size_t len) +{ + return send(sockfd, buf, len, 0); +} + +/* + * Use recvfrom(2) to receive message. We don't care what is the source + * address of the message. + */ + +static ssize_t recvfrom_message(int sockfd, char *buf, size_t len) +{ + dprintf("ready to receive using recvfrom\n"); + return recvfrom(sockfd, buf, len, 0, NULL, NULL); +} + +/* + * Use recvmsg(2) to receive message. We don't care what is the source + * address of the message. + */ + +static ssize_t recvmsg_message(int sockfd, char *buf, size_t len) +{ + struct iovec iov; + struct msghdr msg; + + iov.iov_base = buf; + iov.iov_len = len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + return recvmsg(sockfd, &msg, 0); +} + +/* + * Can not use recv(2) on datagram sockets. call recvfrom_message(). + */ + +static ssize_t recv_message(int sockfd, char *buf, size_t len) +{ + dprintf("ready to receive using recv\n"); + return recv(sockfd, buf, len, 0); +} + +/* + * Use sendto(2) on a socket. + */ + +static void test_sendto(void) +{ + int s; + int rc; + struct sockaddr_stp sas, remote_sas; + const unsigned short port = 12345, remote_port = 54321; + char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("lo"); + bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + remote_sas.sas_family = AF_STP; + remote_sas.sas_port = htons(remote_port); + remote_sas.sas_ifindex = 0; + memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"), + sizeof(remote_sas.sas_addr)); + + rc = sendto_message(s, &remote_sas, bufout, BUFLEN); + + test(__FUNCTION__, rc >= 0, 5); + + close(s); + cleanup_test(); +} + +/* + * Use sendmsg(2) on a socket. + */ + +static void test_sendmsg(void) +{ + int s; + int rc; + struct sockaddr_stp sas, remote_sas; + const unsigned short port = 12345, remote_port = 54321; + char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("lo"); + bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + remote_sas.sas_family = AF_STP; + remote_sas.sas_port = htons(remote_port); + remote_sas.sas_ifindex = 0; + memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"), + sizeof(remote_sas.sas_addr)); + + rc = sendmsg_message(s, &remote_sas, bufout, BUFLEN); + + test(__FUNCTION__, rc >= 0, 3); + + close(s); + cleanup_test(); +} + +/* + * Connect local socket to remote AF_STP socket. + */ + +static void test_connect(void) +{ + int s; + int rc; + struct sockaddr_stp sas, remote_sas; + const unsigned short port = 12345, remote_port = 54321; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("lo"); + bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + remote_sas.sas_family = AF_STP; + remote_sas.sas_port = htons(remote_port); + remote_sas.sas_ifindex = 0; + memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"), + sizeof(remote_sas.sas_addr)); + + rc = connect(s, (struct sockaddr *) &remote_sas, sizeof(remote_sas)); + + test(__FUNCTION__, rc >= 0, 5); + + close(s); + cleanup_test(); +} + +/* + * Use send(2) on a connected socket. + */ + +static void test_send(void) +{ + int s; + int rc; + struct sockaddr_stp sas, remote_sas; + const unsigned short port = 12345, remote_port = 54321; + char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("lo"); + bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + remote_sas.sas_family = AF_STP; + remote_sas.sas_port = htons(remote_port); + remote_sas.sas_ifindex = 0; + memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"), + sizeof(remote_sas.sas_addr)); + + rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas)); + assert(rc == 0); + + rc = send_message(s, bufout, BUFLEN); + + test(__FUNCTION__, rc >= 0, 5); + + close(s); + cleanup_test(); +} + +/* + * Read values from STP_PROC_FULL_FILENAME. + */ + +static int stp_proc_read_values(void) +{ + char buffer[256]; + FILE *f; + + f = fopen(STP_PROC_FULL_FILENAME, "rt"); + if (f == NULL) + return -1; + + /* read column line */ + fgets(buffer, 256, f); + + /* read values line */ + fscanf(f, "%d %d %d %d %d %d", + &rx_pkts, &hdr_err, &csum_err, &no_sock, &no_buffs, &tx_pkts); + dprintf("read: %d %d %d %d %d %d\n", + rx_pkts, hdr_err, csum_err, no_sock, no_buffs, tx_pkts); + + fclose(f); + + return 0; +} + +/* + * Send packet updates RxPkts column in STP_PROC_FULL_FILENAME. + * Expected values are 1, 1. + */ + +static void test_stat_tx(void) +{ + int s; + int rc; + struct sockaddr_stp sas, remote_sas; + const unsigned short port = 12345, remote_port = 54321; + char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE; + + init_test(); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("lo"); + bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + remote_sas.sas_family = AF_STP; + remote_sas.sas_port = htons(remote_port); + remote_sas.sas_ifindex = 0; + memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"), + sizeof(remote_sas.sas_addr)); + + rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas)); + assert(rc == 0); + + send_message(s, bufout, BUFLEN); + + close(s); + + stp_proc_read_values(); + + test(__FUNCTION__, tx_pkts == 1, 3); + + cleanup_test(); +} + +/* + * Start sender process. + * + * action switches between sendto(2), sendmsg(2), send(2) and whether + * to do ping_pong or not. + */ + +static pid_t start_sender(enum socket_action action) +{ + pid_t pid; + int s; + struct sockaddr_stp sas, remote_sas; + const unsigned short port = 12345, remote_port = 54321; + char bufin[BUFLEN]; + char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE; + ssize_t bytes_recv = 0, bytes_sent = 0; + sem_t *sem; + + /* set bufin to 0 for testing purposes (it should be overwritten) */ + memset(bufin, 0, BUFLEN); + + pid = fork(); + DIE(pid < 0, "fork"); + + switch (pid) { + case 0: /* child process */ + break; + + default: /* parent process */ + return pid; + } + + /* only child process (sender) is running */ + + sem = sem_open(SEM_NAME_SENDER, 0); + if (sem == SEM_FAILED) + exit(EXIT_FAILURE); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("lo"); + bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + remote_sas.sas_family = AF_STP; + remote_sas.sas_port = htons(remote_port); + remote_sas.sas_ifindex = 0; + memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"), + sizeof(remote_sas.sas_addr)); + if (action == ACTION_SEND || action == ACTION_SEND_PING_PONG) { + int rc; + + rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas)); + assert(rc == 0); + } + + switch (action) { + case ACTION_SENDTO: + case ACTION_SENDTO_PING_PONG: + bytes_sent = sendto_message(s, &remote_sas, bufout, BUFLEN); + dprintf("sent %s\n", bufout); + break; + + case ACTION_SENDMSG: + case ACTION_SENDMSG_PING_PONG: + bytes_sent = sendmsg_message(s, &remote_sas, bufout, BUFLEN); + dprintf("sent %s\n", bufout); + break; + + case ACTION_SEND: + case ACTION_SEND_PING_PONG: + bytes_sent = send_message(s, bufout, BUFLEN); + dprintf("sent %s\n", bufout); + break; + + default: + break; + } + + switch (action) { + case ACTION_SENDTO_PING_PONG: + bytes_recv = recvfrom_message(s, bufin, BUFLEN); + dprintf("received %s\n", bufin); + break; + case ACTION_SENDMSG_PING_PONG: + bytes_recv = recvmsg_message(s, bufin, BUFLEN); + dprintf("received %s\n", bufin); + break; + case ACTION_SEND_PING_PONG: + bytes_recv = recv_message(s, bufin, BUFLEN); + dprintf("received %s\n", bufin); + break; + default: + break; + } + + /* Let the parent know we're done. */ + sem_post(sem); + + /* exit with EXIT_SUCCESS in case of successful communication */ + switch (action) { + case ACTION_SENDTO: + case ACTION_SEND: + case ACTION_SENDMSG: + if (bytes_sent > 0) + exit(EXIT_SUCCESS); + break; + + case ACTION_SENDMSG_PING_PONG: + case ACTION_SENDTO_PING_PONG: + case ACTION_SEND_PING_PONG: + dprintf("(ping_pong) bytes_sent: %d, bytes_recv: %d, strcmp: %d\n", + bytes_sent, bytes_recv, strcmp(bufin, bufout)); + dprintf("bufin: #%s#, bufout: #%s#\n", bufin, bufout); + if (bytes_sent > 0 && bytes_recv > 0 && + strcmp(bufin, DEFAULT_RECEIVER_MESSAGE) == 0) + exit(EXIT_SUCCESS); + break; + } + + exit(EXIT_FAILURE); + + /* is not reached */ + return 0; +} + +/* + * Start receiver process. + * + * action switches between sendto(2), sendmsg(2), send(2) and whether + * to do ping_pong or not. + */ + +static pid_t start_receiver(enum socket_action action) +{ + pid_t pid; + int s; + struct sockaddr_stp sas, remote_sas; + const unsigned short port = 54321, remote_port = 12345; + char bufin[BUFLEN]; + char bufout[BUFLEN] = DEFAULT_RECEIVER_MESSAGE; + ssize_t bytes_recv = 0, bytes_sent = 0; + sem_t *sem; + + /* set bufin to 0 for testing purposes (it should be overwritten) */ + memset(bufin, 0, BUFLEN); + + pid = fork(); + DIE(pid < 0, "fork"); + + switch (pid) { + case 0: /* child process */ + break; + + default: /* parent process */ + return pid; + } + + /* only child process (receiver) is running */ + + sem = sem_open(SEM_NAME_RECEIVER, 0); + if (sem == SEM_FAILED) + exit(EXIT_FAILURE); + + s = socket(AF_STP, SOCK_DGRAM, 0); + + sas.sas_family = AF_STP; + sas.sas_port = htons(port); + sas.sas_ifindex = if_nametoindex("lo"); + bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp)); + + remote_sas.sas_family = AF_STP; + remote_sas.sas_port = htons(remote_port); + remote_sas.sas_ifindex = 0; + memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"), + sizeof(remote_sas.sas_addr)); + + if (action == ACTION_SEND || action == ACTION_SEND_PING_PONG) { + int rc; + + rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas)); + assert(rc == 0); + dprintf("connected\n"); + } + + /* We're set up, let the parent know. */ + sem_post(sem); + + switch (action) { + case ACTION_SENDTO: + case ACTION_SENDTO_PING_PONG: + bytes_recv = recvfrom_message(s, bufin, BUFLEN); + dprintf("received %s\n", bufin); + break; + + case ACTION_SENDMSG: + case ACTION_SENDMSG_PING_PONG: + bytes_recv = recvmsg_message(s, bufin, BUFLEN); + dprintf("received %s\n", bufin); + break; + + case ACTION_SEND: + case ACTION_SEND_PING_PONG: + bytes_recv = recv_message(s, bufin, BUFLEN); + dprintf("received %s\n", bufin); + break; + + default: + break; + } + + switch (action) { + case ACTION_SENDTO_PING_PONG: + bytes_sent = sendto_message(s, &remote_sas, bufout, BUFLEN); + dprintf("sent %s\n", bufout); + break; + case ACTION_SENDMSG_PING_PONG: + bytes_sent = sendmsg_message(s, &remote_sas, bufout, BUFLEN); + dprintf("sent %s\n", bufout); + break; + case ACTION_SEND_PING_PONG: + bytes_sent = send_message(s, bufout, BUFLEN); + dprintf("sent %s\n", bufout); + break; + default: + break; + } + + /* Let the parent know we're done. */ + sem_post(sem); + + /* exit with EXIT_SUCCESS in case of successful communication */ + switch (action) { + case ACTION_SENDTO: + case ACTION_SEND: + case ACTION_SENDMSG: + if (bytes_recv > 0) + exit(EXIT_SUCCESS); + break; + + case ACTION_SENDMSG_PING_PONG: + case ACTION_SENDTO_PING_PONG: + case ACTION_SEND_PING_PONG: + dprintf("(ping_pong) bytes_sent: %d, bytes_recv: %d\n", + bytes_sent, bytes_recv); + dprintf("bufin: #%s#, bufout: #%s#\n", bufin, bufout); + if (bytes_recv > 0 && bytes_sent > 0 && + strcmp(bufin, DEFAULT_SENDER_MESSAGE) == 0) + exit(EXIT_SUCCESS); + break; + } + + exit(EXIT_FAILURE); + + /* is not reached */ + return 0; +} + +int wait_for_semaphore(sem_t *sem, unsigned int secs) +{ + struct timespec ts; + int ret; + + ret = clock_gettime(CLOCK_REALTIME, &ts); + assert(ret == 0); + + ts.tv_sec += secs; + + ret = sem_timedwait(sem, &ts); + return ret; +} + +/* + * Wrapper call for running a sender and a receiver process. + * + * action switches between sendto(2), sendmsg(2), send(2) and whether + * to do ping_pong or not. + * + * Returns boolean value: 1 in case of successful run, 0 otherwise. + */ + +static int run_sender_receiver(enum socket_action action) +{ + pid_t pid_r = 0, pid_s = 0; + int rc1, rc2, ret; + int status1, status2; + sem_t *sem_r, *sem_s; + + /* Create two named semaphores used to communicate + * with the child processes + */ + sem_r = sem_open(SEM_NAME_RECEIVER, O_CREAT, (mode_t)0644, 0); + assert(sem_r != SEM_FAILED); + sem_s = sem_open(SEM_NAME_SENDER, O_CREAT, (mode_t)0644, 0); + assert(sem_s != SEM_FAILED); + + /* start the receiver */ + pid_r = start_receiver(action); + assert(pid_r > 0); + /* wait for it to bind */ + wait_for_semaphore(sem_r, RECV_TIMEOUT); + + /* Receiver is set up, start the sender now. */ + pid_s = start_sender(action); + assert(pid_s > 0); + + /* Wait for both to finish. */ + rc1 = wait_for_semaphore(sem_r, SENDRECV_TIMEOUT); + ret = waitpid(pid_r, &status1, rc1 ? WNOHANG : 0); + assert(ret >= 0); + kill(pid_r, SIGTERM); kill(pid_r, SIGKILL); + + rc2 = wait_for_semaphore(sem_s, SENDRECV_TIMEOUT); + ret = waitpid(pid_s, &status2, rc2 ? WNOHANG : 0); + assert(ret >= 0); + kill(pid_s, SIGTERM); kill(pid_s, SIGKILL); + + sem_close(sem_r); sem_unlink(SEM_NAME_RECEIVER); + sem_close(sem_s); sem_unlink(SEM_NAME_SENDER); + + return !rc1 && !rc2 && + WIFEXITED(status1) && WEXITSTATUS(status1) == EXIT_SUCCESS && + WIFEXITED(status2) && WEXITSTATUS(status2) == EXIT_SUCCESS; +} + +/* + * Send a datagram on one end and receive it on the other end. + * Use sendto(2) and recvfrom(2). + */ + +static void test_sendto_recvfrom(void) +{ + int rc; + + init_test(); + + rc = run_sender_receiver(ACTION_SENDTO); + + test(__FUNCTION__, rc != 0, 10); + + cleanup_test(); +} + +/* + * Send and receive packet updates RxPkts and TxPkts columns in + * STP_PROC_FULL_FILENAME. Expected values are 1, 1. + */ + +static void test_stat_tx_rx(void) +{ + init_test(); + + run_sender_receiver(ACTION_SENDTO); + + stp_proc_read_values(); + + test(__FUNCTION__, tx_pkts == 1 && rx_pkts == 1, 3); + + cleanup_test(); +} + +/* + * Send a packet and then wait for a reply. + */ + +static void test_sendto_recvfrom_ping_pong(void) +{ + int rc; + + init_test(); + + rc = run_sender_receiver(ACTION_SENDTO_PING_PONG); + + test(__FUNCTION__, rc != 0, 5); + + cleanup_test(); +} + +/* + * Send and receive ping pong updates RxPkts and TxPkts column in + * STP_PROC_FULL_FILENAME. Expected values are 2, 2. + */ + +static void test_stat_tx_rx_ping_pong(void) +{ + init_test(); + + run_sender_receiver(ACTION_SENDTO_PING_PONG); + + stp_proc_read_values(); + stp_proc_read_values(); + + test(__FUNCTION__, tx_pkts == 2 && rx_pkts == 2, 3); + + cleanup_test(); +} + +/* + * Send a datagram on one end and receive it on the other end. + * Use sendmsg(2) and recvmsg(2). + */ + +static void test_sendmsg_recvmsg(void) +{ + int rc; + + init_test(); + + rc = run_sender_receiver(ACTION_SENDMSG); + + test(__FUNCTION__, rc != 0, 5); + + cleanup_test(); +} + +/* + * Send a packet and then wait for a reply. + */ + +static void test_sendmsg_recvmsg_ping_pong(void) +{ + int rc; + + init_test(); + + rc = run_sender_receiver(ACTION_SENDMSG_PING_PONG); + + test(__FUNCTION__, rc != 0, 3); + + cleanup_test(); +} + +/* + * Send a packet on one end and receive it on the other end. + * Use send(2) and recv(2). + */ + +static void test_send_receive(void) +{ + int rc; + + init_test(); + + rc = run_sender_receiver(ACTION_SEND); + + test(__FUNCTION__, rc != 0, 5); + + cleanup_test(); +} + +/* + * Send a packet and then wait for a reply. + */ + +static void test_send_receive_ping_pong(void) +{ + int rc; + + init_test(); + + rc = run_sender_receiver(ACTION_SEND_PING_PONG); + + test(__FUNCTION__, rc != 0, 3); + + cleanup_test(); +} + +static void (*test_fun_array[])(void) = { + NULL, + test_insmod_rmmod, + test_proto_name_exists_after_insmod, + test_proto_name_inexistent_after_rmmod, + test_proc_entry_exists_after_insmod, + test_proc_entry_inexistent_after_rmmod, + test_socket, + test_two_sockets, + test_socket_bad_socket_type, + test_socket_bad_protocol, + test_close, + test_close_closed_socket, + test_bind, + test_bind_eth0, + test_two_binds, + test_bind_bad_address, + test_two_binds_same_if, + test_two_binds_same_if_eth0, + test_two_binds_same_if_all_eth0, + test_two_binds_same_if_eth0_all, + test_sendto, + test_sendmsg, + test_connect, + test_send, + test_stat_tx, + test_sendto_recvfrom, + test_stat_tx_rx, + test_sendto_recvfrom_ping_pong, + test_stat_tx_rx_ping_pong, + test_sendmsg_recvmsg, + test_sendmsg_recvmsg_ping_pong, + test_send_receive, + test_send_receive_ping_pong, +}; + +/* + * Usage message for invalid executable call. + */ + +static void usage(const char *argv0) +{ + fprintf(stderr, "Usage: %s test_no\n\n", argv0); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + int test_idx; + + if (argc != 2) + usage(argv[0]); + + test_idx = atoi(argv[1]); + + if (test_idx < 1 || + test_idx >= sizeof(test_fun_array)/sizeof(test_fun_array[0])) { + fprintf(stderr, "Error: test index %d is out of bounds\n", + test_idx); + exit(EXIT_FAILURE); + } + + srand(time(NULL)); + srand48(time(NULL)); + test_fun_array[test_idx](); + + return 0; +} diff --git a/checker/4-stp-checker/_test/stp_test.h b/checker/4-stp-checker/_test/stp_test.h new file mode 100644 index 0000000000000000000000000000000000000000..fb708433c0269f38454ccb3d6903e2c8c410468a --- /dev/null +++ b/checker/4-stp-checker/_test/stp_test.h @@ -0,0 +1,31 @@ +/* + * SO2 Transport Protocol - test suite specific header + */ + +#ifndef STP_TEST_H_ +#define STP_TEST_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* STP test suite macros and structures */ +#define MODULE_NAME "af_stp" +#define MODULE_FILENAME MODULE_NAME ".ko" + +#define SEM_NAME_RECEIVER "/receiver_sem" +#define SEM_NAME_SENDER "/sender_sem" + +/* timeouts waiting for receiver/sender */ +#define RECV_TIMEOUT 1 +#define SENDRECV_TIMEOUT 3 + +/* messages used for "ping-pong" between sender and receiver */ +#define DEFAULT_SENDER_MESSAGE "You called down the thunder" +#define DEFAULT_RECEIVER_MESSAGE "now reap the whirlwind" + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/checker/4-stp-checker/_test/test.h b/checker/4-stp-checker/_test/test.h new file mode 100644 index 0000000000000000000000000000000000000000..4bcafad9c7d0f5b241a0241071301954c3bf9601 --- /dev/null +++ b/checker/4-stp-checker/_test/test.h @@ -0,0 +1,63 @@ +/* + * generic test suite + * + * test macros and headers + */ + +#ifndef TEST_H_ +#define TEST_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> + +/* to be defined by calling program */ +extern int max_points; + +/* + * uncommend EXIT_IF_FAIL macro in order to stop test execution + * at first failed test + */ + +/*#define EXIT_IF_FAIL 1*/ + +#if defined(EXIT_IF_FAIL) +#define test_do_fail(points) \ + do { \ + printf("failed\n"); \ + exit(EXIT_FAILURE); \ + } while (0) +#else +#define test_do_fail(points) \ + printf("failed [ 0/%3d]\n", max_points) +#endif + +#define test_do_pass(points) \ + printf("passed [%3d/%3d]\n", points, max_points) + +#define test(message, test, points) \ + do { \ + size_t i; \ + int t = (test); \ + \ + printf("%s", message); \ + fflush(stdout); \ + \ + for (i = 0; i < 60 - strlen(message); i++) \ + putchar('.'); \ + \ + if (!t) \ + test_do_fail(points); \ + else \ + test_do_pass(points); \ + \ + fflush(stdout); \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/checker/4-stp-checker/_test/util.h b/checker/4-stp-checker/_test/util.h new file mode 100644 index 0000000000000000000000000000000000000000..f06cb833b99635e27c494498fd6b981e50564dbc --- /dev/null +++ b/checker/4-stp-checker/_test/util.h @@ -0,0 +1,69 @@ +/* + * useful structures/macros + */ + +#ifndef UTIL_H_ +#define UTIL_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) + +#include <windows.h> + +static VOID PrintLastError(const PCHAR message) +{ + CHAR errBuff[1024]; + + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, + GetLastError(), + 0, + errBuff, + sizeof(errBuff) - 1, + NULL); + + fprintf(stderr, "%s: %s\n", message, errBuff); +} + +#define ERR(call_description) \ + do { \ + fprintf(stderr, "(%s, %d): ", \ + __FILE__, __LINE__); \ + PrintLastError(call_description); \ + } while (0) + +#elif defined(__linux__) + +/* error printing macro */ +#define ERR(call_description) \ + do { \ + fprintf(stderr, "(%s, %d): ", \ + __FILE__, __LINE__); \ + perror(call_description); \ + } while (0) + +#else + #error "Unknown platform" +#endif + +/* print error (call ERR) and exit */ +#define DIE(assertion, call_description) \ + do { \ + if (assertion) { \ + ERR(call_description); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/checker/checker.sh b/checker/checker.sh index 3e1d1a630cbeddab6f15eedbd5010c17a2ede37e..424e3dcd7ae3e5e8fbacb3d71dc7b0f1cfa5f932 100755 --- a/checker/checker.sh +++ b/checker/checker.sh @@ -1,34 +1,104 @@ #!/bin/bash -TIMEOUT=300 # 5 min SO2_WORKSPACE=/linux/tools/labs +SO2_VM_LOG=/tmp/so2_vm_log.txt + +ASSIGNMENT0_TIMEOUT=300 # 5 min ASSIGNMENT0_MOD=list.ko ASSIGNMENT0_DIR=${SO2_WORKSPACE}/skels/assignments/0-list +ASSIGNMENT0_CHECKER_LOCAL_DIR=checker/0-list-checker ASSIGNMENT0_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/0-list-checker ASSIGNMENT0_OUTPUT=${SO2_WORKSPACE}/skels/0-list-output ASSIGNMENT0_FINISHED=${SO2_WORKSPACE}/skels/0-list-finished +ASSIGNMENT1_TIMEOUT=300 # 5 min ASSIGNMENT1_MOD=tracer.ko ASSIGNMENT1_DIR=${SO2_WORKSPACE}/skels/assignments/1-tracer +ASSIGNMENT1_CHECKER_LOCAL_DIR=checker/1-tracer-checker ASSIGNMENT1_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/1-tracer-checker ASSIGNMENT1_OUTPUT=${SO2_WORKSPACE}/skels/1-tracer-output ASSIGNMENT1_FINISHED=${SO2_WORKSPACE}/skels/1-tracer-finished ASSIGNMENT1_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/1-tracer/tracer.h ASSIGNMENT1_CHECKER_AUX_LIST="${ASSIGNMENT1_CHECKER_DIR}/_helper/tracer_helper.ko" +ASSIGNMENT2_TIMEOUT=300 # 5 min +ASSIGNMENT2_MOD=uart16550.ko +ASSIGNMENT2_DIR=${SO2_WORKSPACE}/skels/assignments/2-uart +ASSIGNMENT2_CHECKER_LOCAL_DIR=checker/2-uart-checker +ASSIGNMENT2_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/2-uart-checker +ASSIGNMENT2_OUTPUT=${SO2_WORKSPACE}/skels/2-uart-output +ASSIGNMENT2_FINISHED=${SO2_WORKSPACE}/skels/2-uart-finished +ASSIGNMENT2_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/2-uart/uart16550.h +ASSIGNMENT2_CHECKER_AUX_LIST="${ASSIGNMENT2_CHECKER_DIR}/_test/solution.ko" + +ASSIGNMENT3_TIMEOUT=360 # 6 min +ASSIGNMENT3_MOD=ssr.ko +ASSIGNMENT3_DIR=${SO2_WORKSPACE}/skels/assignments/3-raid +ASSIGNMENT3_CHECKER_LOCAL_DIR=checker/3-raid-checker +ASSIGNMENT3_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/3-raid-checker +ASSIGNMENT3_OUTPUT=${SO2_WORKSPACE}/skels/3-raid-output +ASSIGNMENT3_FINISHED=${SO2_WORKSPACE}/skels/3-raid-finished +ASSIGNMENT3_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/3-raid/ssr.h +ASSIGNMENT3_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test" + +ASSIGNMENT4_TIMEOUT=300 # 5 min +ASSIGNMENT4_MOD=af_stp.ko +ASSIGNMENT4_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp +ASSIGNMENT4_CHECKER_LOCAL_DIR=checker/4-stp-checker +ASSIGNMENT4_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp-checker +ASSIGNMENT4_OUTPUT=${SO2_WORKSPACE}/skels/4-stp-output +ASSIGNMENT4_FINISHED=${SO2_WORKSPACE}/skels/4-stp-finished +ASSIGNMENT4_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/4-stp/stp.h +#ASSIGNMENT4_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test" + + usage() { echo "Usage: $0 <assignment>" exit 1 } + + +recover_grade_from_timeout() +{ + local output=$1 + if [ ! -f $output ]; then + echo "$output not available" + else + points_total=$(echo $(cat $output | grep "....passed" | egrep -o "/.*[0-9]+\.*[0-9]*.*\]" | egrep -o "[0-9]+\.*[0-9]*" | head -n 1)) + list=$(echo $(cat $output | grep "....passed" | egrep -o "\[.*[0-9]+\.*[0-9]*.*\/" | egrep -o "[0-9]+\.*[0-9]*") | sed -e 's/\s\+/,/g') + recovered_points=$(python3 -c "print(sum([$list]))") + echo "Recovered from timeout => Total: [$recovered_points/$points_total]" + echo "Please note that this is not a DIRECT checker output! Other penalties may be applied!" + echo "Please contact a teaching assistant" + python3 -c "print('Total: ' + str(int ($recovered_points * 100 / $points_total)) + '/' + '100')" + fi +} + timeout_exceeded() { - echo TIMEOUT EXCEEDED !!! killing the process - echo "<VMCK_NEXT_END>" + local output=$1 pkill -SIGKILL qemu - exit 1 + echo "" + echo "TIMEOUT EXCEEDED !!! killing the process" + if [[ $RECOVER_GRADE_TIMEOUT == 0 ]]; then + if [ -f $output ]; then + echo "$output not available" + else + cat $output + fi + echo "dumping SO2_VM_LOG=${SO2_VM_LOG} output" + cat $SO2_VM_LOG + + echo "The Recover Grade Timeout option is not set! Please contact a teaching assistant!" + else + recover_grade_from_timeout $output + fi + echo "<VMCK_NEXT_END>" + # exit successfully for vmchecker-next to process output + exit 0 # TODO: fixme } compute_total() @@ -46,8 +116,10 @@ compute_total() dump_output() { local output=$1 + local timeout=$2 echo "<VMCK_NEXT_BEGIN>" cat $output + echo "Running time $timeout/$TIMEOUT" } @@ -67,18 +139,22 @@ run_checker() { local assignment_mod=$1 local assignment_dir=$2 - local checker_dir=$3 - local output=$4 - local finished=$5 - local assignment=$6 - local header_overwrite=$7 - local aux_modules=$8 + local local_checker_dir=$3 + local checker_dir=$4 + local output=$5 + local finished=$6 + local assignment=$7 + local header_overwrite=$8 + local aux_modules=$9 local module_path="${assignment_dir}/${assignment_mod}" echo "Copying the contents of src/ into $assignment_dir" cp src/* $assignment_dir + echo "Copying the contents of $local_checker_dir into $checker_dir" + cp -r $local_checker_dir/* $checker_dir + echo "Checking if $assignment_mod exists before build" if [ -f $module_path ]; then echo "$assignment_mod shouldn't exists. Removing ${module_path}" @@ -117,7 +193,7 @@ run_checker() if [ ! -f $module_path ]; then error_message $assignment_mod # exit successfully for vmchecker-next to process output - exit 0 # TODO: changeme + exit 0 # TODO: fixme fi # copy *.ko in checker @@ -133,8 +209,9 @@ run_checker() done fi - LINUX_ADD_CMDLINE="so2=$assignment" ./qemu/run-qemu.sh &> /dev/null & - + LINUX_ADD_CMDLINE="so2=$assignment" make checker &> ${SO2_VM_LOG} & + + timeout=0 echo -n "CHECKER IS RUNNING" while [ ! -f $finished ] do @@ -144,27 +221,45 @@ run_checker() dump_output $output compute_total $output fi - timeout_exceeded + timeout_exceeded $output fi sleep 2 (( timeout += 2 )) echo -n . done echo "" - dump_output $output + dump_output $output $timeout compute_total $output popd &> /dev/null } case $1 in 0-list) - run_checker $ASSIGNMENT0_MOD $ASSIGNMENT0_DIR $ASSIGNMENT0_CHECKER_DIR $ASSIGNMENT0_OUTPUT $ASSIGNMENT0_FINISHED $1 + TIMEOUT=$ASSIGNMENT0_TIMEOUT + RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory + run_checker $ASSIGNMENT0_MOD $ASSIGNMENT0_DIR $ASSIGNMENT0_CHECKER_LOCAL_DIR $ASSIGNMENT0_CHECKER_DIR $ASSIGNMENT0_OUTPUT $ASSIGNMENT0_FINISHED $1 ;; 1-tracer) - run_checker $ASSIGNMENT1_MOD $ASSIGNMENT1_DIR $ASSIGNMENT1_CHECKER_DIR $ASSIGNMENT1_OUTPUT $ASSIGNMENT1_FINISHED $1 $ASSIGNMENT1_HEADER_OVERWRITE $ASSIGNMENT1_CHECKER_AUX_LIST - - + TIMEOUT=$ASSIGNMENT1_TIMEOUT + RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory + run_checker $ASSIGNMENT1_MOD $ASSIGNMENT1_DIR $ASSIGNMENT1_CHECKER_LOCAL_DIR $ASSIGNMENT1_CHECKER_DIR $ASSIGNMENT1_OUTPUT $ASSIGNMENT1_FINISHED $1 $ASSIGNMENT1_HEADER_OVERWRITE $ASSIGNMENT1_CHECKER_AUX_LIST ;; + 2-uart) + TIMEOUT=$ASSIGNMENT2_TIMEOUT + RECOVER_GRADE_TIMEOUT=1 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory + run_checker $ASSIGNMENT2_MOD $ASSIGNMENT2_DIR $ASSIGNMENT2_CHECKER_LOCAL_DIR $ASSIGNMENT2_CHECKER_DIR $ASSIGNMENT2_OUTPUT $ASSIGNMENT2_FINISHED $1 $ASSIGNMENT2_HEADER_OVERWRITE $ASSIGNMENT2_CHECKER_AUX_LIST + ;; + 3-raid) + TIMEOUT=$ASSIGNMENT3_TIMEOUT + RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory + run_checker $ASSIGNMENT3_MOD $ASSIGNMENT3_DIR $ASSIGNMENT3_CHECKER_LOCAL_DIR $ASSIGNMENT3_CHECKER_DIR $ASSIGNMENT3_OUTPUT $ASSIGNMENT3_FINISHED $1 $ASSIGNMENT3_HEADER_OVERWRITE $ASSIGNMENT3_CHECKER_AUX_LIST + ;; + 4-stp) + TIMEOUT=$ASSIGNMENT4_TIMEOUT + RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output file + run_checker $ASSIGNMENT4_MOD $ASSIGNMENT4_DIR $ASSIGNMENT4_CHECKER_LOCAL_DIR $ASSIGNMENT4_CHECKER_DIR $ASSIGNMENT4_OUTPUT $ASSIGNMENT4_FINISHED $1 $ASSIGNMENT4_HEADER_OVERWRITE + ;; + *) usage ;; diff --git a/checker/checker_daemons/so2_vm_checker_daemon.sh b/checker/checker_daemons/so2_vm_checker_daemon.sh index afc38b65f3a33a39cbb20e707d6dd1cd1b3790c1..546f236afdea29a8851f21106f3b317387856ee9 100644 --- a/checker/checker_daemons/so2_vm_checker_daemon.sh +++ b/checker/checker_daemons/so2_vm_checker_daemon.sh @@ -10,22 +10,58 @@ ASSIGNMENT1_CHECKER=/home/root/skels/assignments/1-tracer-checker ASSIGNMENT1_OUTPUT=/home/root/skels/1-tracer-output ASSIGNMENT1_FINISHED=/home/root/skels/1-tracer-finished +ASSIGNMENT2_CHECKER=/home/root/skels/assignments/2-uart-checker +ASSIGNMENT2_OUTPUT=/home/root/skels/2-uart-output +ASSIGNMENT2_FINISHED=/home/root/skels/2-uart-finished + +ASSIGNMENT3_CHECKER=/home/root/skels/assignments/3-raid-checker +ASSIGNMENT3_OUTPUT=/home/root/skels/3-raid-output +ASSIGNMENT3_FINISHED=/home/root/skels/3-raid-finished + +ASSIGNMENT4_CHECKER=/home/root/skels/assignments/4-stp-checker +ASSIGNMENT4_OUTPUT=/home/root/skels/4-stp-output +ASSIGNMENT4_FINISHED=/home/root/skels/4-stp-finished + + assign0_list() { - cd $ASSIGNMENT0_CHECKER - sh _checker &> $ASSIGNMENT0_OUTPUT - echo FINISHED &> $ASSIGNMENT0_FINISHED - cd - + cd $ASSIGNMENT0_CHECKER + sh _checker &> $ASSIGNMENT0_OUTPUT + echo FINISHED &> $ASSIGNMENT0_FINISHED + cd - } assign1_tracer() { - cd $ASSIGNMENT1_CHECKER - sh _checker &> $ASSIGNMENT1_OUTPUT - echo FINISHED &> $ASSIGNMENT1_FINISHED - cd - + cd $ASSIGNMENT1_CHECKER + sh _checker &> $ASSIGNMENT1_OUTPUT + echo FINISHED &> $ASSIGNMENT1_FINISHED + cd - } +assign2_uart() +{ + cd $ASSIGNMENT2_CHECKER + sh _checker &> $ASSIGNMENT2_OUTPUT + echo FINISHED &> $ASSIGNMENT2_FINISHED + cd - +} + +assign3_raid() +{ + cd $ASSIGNMENT3_CHECKER + sh _checker &> $ASSIGNMENT3_OUTPUT + echo FINISHED &> $ASSIGNMENT3_FINISHED + cd - +} + +assign4_stp() +{ + cd $ASSIGNMENT4_CHECKER + sh _checker &> $ASSIGNMENT4_OUTPUT + echo FINISHED &> $ASSIGNMENT4_FINISHED + cd - +} start() { @@ -37,6 +73,15 @@ start() 1-tracer) assign1_tracer ;; + 2-uart) + assign2_uart + ;; + 3-raid) + assign3_raid + ;; + 4-stp) + assign4_stp + ;; *) echo "Unknown option" exit 0 diff --git a/local.sh b/local.sh index 6c0cc4986334fd091ed306122458c9a2123ae44a..adfa8a9d00fb4faa192937edb5b975dc94716c8a 100755 --- a/local.sh +++ b/local.sh @@ -153,7 +153,7 @@ docker_push() { [ -z "$token" ] && LOG_FATAL "No token provided. Push operation will be aborted..." LOG_INFO "Pushing Docker image..." - +set -x docker login "${registry}" -u "${user}" -p "${token}" docker push "${registry}/${image_name}:${tag}" } @@ -198,7 +198,7 @@ docker_interactive() { set -x cp -R ${ASSIGNMENT_CHECKER_DIR}/* "$tmpdir" - docker run $privileged --rm -it \ + docker run $privileged --rm -it --cap-add=NET_ADMIN --device /dev/net/tun:/dev/net/tun \ --mount type=bind,source="$SRC_DIR",target="$ASSIGNMENT_MOUNT_DIR" \ --mount type=bind,source="$tmpdir",target="$ASSIGNMENT_CHECKER_MOUNT_DIR" \ --workdir "$SO2_WORKSPACE" \ @@ -265,7 +265,7 @@ checker_main() { # In your checker script if you must use absolute paths please use $CI_PROJECT_DIR to reference the location of your directory, # otherwise stick to relative paths. # It is guaranteed that the current working directory in which checker.sh will run is $CI_PROJECT_DIR/checker. - docker run $privileged --rm \ + docker run $privileged --rm --cap-add=NET_ADMIN --device /dev/net/tun:/dev/net/tun \ --mount type=bind,source="$tmpdir",target="$MOUNT_PROJECT_DIRECTORY" \ "$image_name" /bin/bash -c "rm -rf /usr/local/bin/bash; cd \"$MOUNT_PROJECT_DIRECTORY\"; \"$MOUNT_PROJECT_DIRECTORY/checker/checker.sh\" \"${script_args[@]}\"" # remove bash middleware script