From e8b90abe1981c91852af6041f80d4574d5ca433b Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Mon, 9 Jul 2018 17:34:00 +0200 Subject: [PATCH 01/53] Various corrections --- student/CTester/CTester.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index f4a85fa..a6af3cc 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -73,8 +73,10 @@ void push_info_msg(char *msg) } struct info_msg *item = malloc(sizeof(struct info_msg)); - if (item == NULL) + if (item == NULL) { test_metadata.err = ENOMEM; + return; + } item->next = NULL; item->msg = malloc(strlen(msg) + 1); @@ -197,11 +199,11 @@ int clean_suite1(void) void start_test() { - bzero(&test_metadata,sizeof(test_metadata)); - bzero(&stats,sizeof(stats)); - bzero(&failures,sizeof(failures)); - bzero(&monitored,sizeof(monitored)); - bzero(&logs,sizeof(logs)); + memset(&test_metadata, 0, sizeof(test_metadata)); + memset(&stats, 0, sizeof(stats)); + memset(&failures, 0, sizeof(failures)); + memset(&monitored, 0, sizeof(monitored)); + memset(&logs, 0, sizeof(logs)); } int __real_exit(int status); From 4bf0af097d888fce54b4d838d2a23d4748eb29f2 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Thu, 12 Jul 2018 17:52:00 +0200 Subject: [PATCH 02/53] Some corrections --- student/CTester/CTester.c | 5 ++++- student/CTester/wrap_file.h | 20 ++++++++++---------- student/CTester/wrap_malloc.h | 6 +++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index a6af3cc..3bcaef1 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -80,8 +80,10 @@ void push_info_msg(char *msg) item->next = NULL; item->msg = malloc(strlen(msg) + 1); - if (item->msg == NULL) + if (item->msg == NULL) { test_metadata.err = ENOMEM; + return; + } strcpy(item->msg, msg); if (test_metadata.fifo_in == NULL && test_metadata.fifo_out == NULL) { @@ -170,6 +172,7 @@ void sandbox_end() while ((n = read(pipe_stderr[0], buf, BUFSIZ)) > 0) { if (strstr(buf, "double free or corruption") != NULL) { + // TODO n'y a-t-il pas un risque que le message se fasse couper en plusieurs morceaux à cause de read ? CU_FAIL("Double free or corruption"); push_info_msg(_("Your code produced a double free.")); set_tag("double_free"); diff --git a/student/CTester/wrap_file.h b/student/CTester/wrap_file.h index 7cae21f..a816aed 100644 --- a/student/CTester/wrap_file.h +++ b/student/CTester/wrap_file.h @@ -29,9 +29,9 @@ struct params_creat_t { // basic statistics for the utilisation of the creat system call struct stats_creat_t { - int called; // number of times the open system call has been issued + int called; // number of times the creat system call has been issued struct params_creat_t last_params; // parameters for the last call issued - int last_return; // return value of the last open call issued + int last_return; // return value of the last creat call issued }; @@ -42,9 +42,9 @@ struct params_close_t { // basic statistics for the utilisation of the close system call struct stats_close_t { - int called; // number of times the open system call has been issued + int called; // number of times the close system call has been issued struct params_close_t last_params; // parameters for the last call issued - int last_return; // return value of the last open call issued + int last_return; // return value of the last close call issued }; struct params_read_t { @@ -72,7 +72,7 @@ struct params_write_t { struct stats_write_t { int called; // number of times the write system call has been issued struct params_read_t last_params; // parameters for the last call issued - int last_return; // return value of the last read call issued + int last_return; // return value of the last write call issued }; struct params_stat_t { @@ -83,9 +83,9 @@ struct params_stat_t { // basic statistics for the utilisation of the stat system call struct stats_stat_t { - int called; // number of times the write system call has been issued + int called; // number of times the stat system call has been issued struct params_stat_t last_params; // parameters for the last call issued - int last_return; // return value of the last read call issued + int last_return; // return value of the last stat call issued struct stat returned_stat; // last returned stat structure }; @@ -98,9 +98,9 @@ struct params_fstat_t { // basic statistics for the utilisation of the fstat system call struct stats_fstat_t { - int called; // number of times the write system call has been issued + int called; // number of times the fstat system call has been issued struct params_fstat_t last_params; // parameters for the last call issued - int last_return; // return value of the last read call issued + int last_return; // return value of the last fstat call issued struct stat returned_stat; // last returned stat structure }; @@ -111,7 +111,7 @@ struct params_lseek_t { int whence; }; -// basic statistics for the utilisation of the fstat system call +// basic statistics for the utilisation of the lseek system call struct stats_lseek_t { int called; // number of times the lseek system call has been issued diff --git a/student/CTester/wrap_malloc.h b/student/CTester/wrap_malloc.h index dcc7a8c..8cb866c 100644 --- a/student/CTester/wrap_malloc.h +++ b/student/CTester/wrap_malloc.h @@ -83,7 +83,7 @@ struct params_realloc_t { // basic statistics for the utilisation of the realloc call struct stats_realloc_t { - int called; // number of times the malloc call has been issued + int called; // number of times the realloc call has been issued struct params_realloc_t last_params; // parameters for the last call issued void *last_return; // return value of the last realloc call issued }; @@ -94,7 +94,7 @@ struct stats_realloc_t { //void malloc_log_init(struct malloc_t *l); void malloc_log(void *ptr, size_t size); -// true if memory was allocated by malloc, false otherwise +// true if memory was allocated by malloc/realloc etc, false otherwise int malloced(void *addr); // total amount of memory allocated by malloc -int malloc_allocated(); \ No newline at end of file +int malloc_allocated(); From 47b55fdccb3a04548f468fdb2a95dfb51ceb3505 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Fri, 13 Jul 2018 15:37:35 +0200 Subject: [PATCH 03/53] Adding (hopefully sufficient) .gitignore --- .gitignore | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e82bf44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled headers +*.gch +*.pch + +# Libraries +*.a +*.lib +*.la +*.lo + +# Shared objects +*.so +*.dll +*.so.* +*.dylib + +# Executables +*.exe +*.com +*.app + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Emacs +*~ +\#*\# +.\#* + +# vi +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# macOS/Windows +*.DS_Store +._* +Thumbs.db +*.lnk + +# Files specific to this project +student/results.txt +student/tests + From ca308e686ee110576756f79f0c5ee6e035791de1 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Fri, 13 Jul 2018 15:04:08 +0200 Subject: [PATCH 04/53] Replacing getpagesize() by getconf(_SC_PAGESIZE), which is recommanded by POSIX standard --- student/CTester/trap.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/student/CTester/trap.c b/student/CTester/trap.c index 2b36c07..4df5371 100644 --- a/student/CTester/trap.c +++ b/student/CTester/trap.c @@ -6,19 +6,20 @@ void *trap_buffer(size_t size, int type, int flags, void *data) { - int nb_pages = (size / getpagesize()) + 2; - void *ptr = mmap(NULL, getpagesize() * nb_pages, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + size_t pagesize = sysconf(_SC_PAGESIZE); + int nb_pages = (size / pagesize) + 2; + void *ptr = mmap(NULL, pagesize * nb_pages, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (ptr == MAP_FAILED) { return NULL; } void *buf_start; if (type == TRAP_LEFT) { - buf_start = ptr + getpagesize(); - mprotect(ptr, getpagesize(), PROT_NONE); + buf_start = ptr + pagesize; + mprotect(ptr, pagesize, PROT_NONE); } else if (type == TRAP_RIGHT) { - buf_start = ptr + (nb_pages - 1) * getpagesize() - size; - mprotect(ptr + (nb_pages - 1)*getpagesize(), getpagesize(), PROT_NONE); + buf_start = ptr + (nb_pages - 1) * pagesize - size; + mprotect(ptr + (nb_pages - 1)*pagesize, pagesize, PROT_NONE); } else { return NULL; } From 39dbe6e1af1c28f9c742a2b8fcd0042b5122dcc8 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Fri, 13 Jul 2018 15:18:58 +0200 Subject: [PATCH 05/53] Adding include guards to prevent multiple inclusions --- student/CTester/wrap.h | 5 +++++ student/CTester/wrap_file.h | 5 +++++ student/CTester/wrap_getpid.h | 10 ++++++++-- student/CTester/wrap_malloc.h | 4 ++++ student/CTester/wrap_mutex.h | 12 +++++++++--- student/CTester/wrap_sleep.h | 10 ++++++++-- 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/student/CTester/wrap.h b/student/CTester/wrap.h index e62430a..9cce09a 100644 --- a/student/CTester/wrap.h +++ b/student/CTester/wrap.h @@ -1,3 +1,6 @@ +#ifndef __WRAP_H_ +#define __WRAP_H_ + #include #include #include @@ -161,3 +164,5 @@ struct wrap_stats_t { struct stats_pthread_mutex_unlock_t pthread_mutex_destroy; struct stats_sleep_t sleep; }; + +#endif // __WRAP_H_ diff --git a/student/CTester/wrap_file.h b/student/CTester/wrap_file.h index a816aed..64b6887 100644 --- a/student/CTester/wrap_file.h +++ b/student/CTester/wrap_file.h @@ -1,3 +1,6 @@ +#ifndef __WRAP_FILE_H_ +#define __WRAP_FILE_H_ + #include #include #include @@ -118,3 +121,5 @@ struct stats_lseek_t { struct params_lseek_t last_params; // parameters for the last call issued int last_return; // return value of the last lseek call issued }; + +#endif // __WRAP_FILE_H_ diff --git a/student/CTester/wrap_getpid.h b/student/CTester/wrap_getpid.h index 2c1905c..d326cca 100644 --- a/student/CTester/wrap_getpid.h +++ b/student/CTester/wrap_getpid.h @@ -1,8 +1,12 @@ +#ifndef __WRAP_GETPID_H_ +#define __WRAP_GETPID_H_ + +#include +#include + // never remove statistics from this structure, they could be // used by existing exercices. You might add some additional information // if it can help to validate some exercices -#include -#include struct stats_getpid_t { int called; // number of times the system call has been called @@ -14,3 +18,5 @@ void init_getpid(); void clean_getpid(); void resetstats_getpid(); +#endif // __WRAP_GETPID_H_ + diff --git a/student/CTester/wrap_malloc.h b/student/CTester/wrap_malloc.h index 8cb866c..e2898e3 100644 --- a/student/CTester/wrap_malloc.h +++ b/student/CTester/wrap_malloc.h @@ -13,6 +13,8 @@ * along with this program. If not, see . */ +#ifndef __WRAP_MALLOC_H_ +#define __WRAP_MALLOC_H_ #include #include @@ -98,3 +100,5 @@ void malloc_log(void *ptr, size_t size); int malloced(void *addr); // total amount of memory allocated by malloc int malloc_allocated(); + +#endif // __WRAP_MALLOC_H_ diff --git a/student/CTester/wrap_mutex.h b/student/CTester/wrap_mutex.h index 9a1e5eb..15f2f72 100644 --- a/student/CTester/wrap_mutex.h +++ b/student/CTester/wrap_mutex.h @@ -1,10 +1,14 @@ -// never remove statistics from this structure, they could be -// used by existing exercices. You might add some additional information -// if it can help to validate some exercices +#ifndef __WRAP_MUTEX_H_ +#define __WRAP_MUTEX_H_ + #include #include #include +// never remove statistics from this structure, they could be +// used by existing exercices. You might add some additional information +// if it can help to validate some exercices + struct stats_pthread_mutex_lock_t { int called; // number of times the system call has been called pid_t last_return; // last return value @@ -60,3 +64,5 @@ void init_pthread_mutex_destroy(); void clean_pthread_mutex_destroy(); void resetstats_pthread_mutex_destroy(); +#endif // __WRAP_MUTEX_H_ + diff --git a/student/CTester/wrap_sleep.h b/student/CTester/wrap_sleep.h index 6d0a488..a0fa6c9 100644 --- a/student/CTester/wrap_sleep.h +++ b/student/CTester/wrap_sleep.h @@ -1,8 +1,12 @@ +#ifndef __WRAP_SLEEP_H_ +#define __WRAP_SLEEP_H_ + +#include +#include + // never remove statistics from this structure, they could be // used by existing exercices. You might add some additional information // if it can help to validate some exercices -#include -#include struct stats_sleep_t { int called; // number of times the system call has been called @@ -14,3 +18,5 @@ void init_sleep(); void clean_sleep(); void resetstats_sleep(); +#endif // __WRAP_SLEEP_H_ + From 65736a7091f2742118e5ba5f8da28dab30e8e424 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Fri, 13 Jul 2018 15:40:11 +0200 Subject: [PATCH 06/53] Updating Makefile to include all C source in CTester/ (Including wrap_getpid.c which was absent) Minor changes to student_code.h --- student/Makefile | 11 ++++++++--- student/student_code.h | 4 ++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/student/Makefile b/student/Makefile index 4cc66a2..820d7ae 100644 --- a/student/Makefile +++ b/student/Makefile @@ -1,10 +1,15 @@ CC=gcc EXEC=tests LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic -SRC=$(wildcard *.c) CTester/wrap_mutex.c CTester/wrap_malloc.c CTester/wrap_file.c CTester/wrap_sleep.c CTester/CTester.c CTester/trap.c +SRC=$(wildcard *.c) $(wildcard CTester/*.c) OBJ=$(SRC:.c=.o) -CFLAGS=-Wall -Werror -DC99 -std=gnu99 -ICTester -WRAP=-Wl,-wrap=pthread_mutex_lock -Wl,-wrap=pthread_mutex_unlock -Wl,-wrap=pthread_mutex_trylock -Wl,-wrap=pthread_mutex_init -Wl,-wrap=pthread_mutex_destroy -Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=realloc -Wl,-wrap=calloc -Wl,-wrap=open -Wl,-wrap=creat -Wl,-wrap=close -Wl,-wrap=read -Wl,-wrap=write -Wl,-wrap=stat -Wl,-wrap=fstat -Wl,-wrap=lseek -Wl,-wrap=exit -Wl,-wrap=sleep +CFLAGS=-Wall -Werror -DC99 -std=gnu99 -ICTester -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE +WRAP += -Wl,-wrap=exit +WRAP += -Wl,-wrap=open -Wl,-wrap=creat -Wl,-wrap=close -Wl,-wrap=read -Wl,-wrap=write -Wl,-wrap=stat -Wl,-wrap=fstat -Wl,-wrap=lseek +WRAP += -Wl,-wrap=getpid +WRAP += -Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=realloc -Wl,-wrap=calloc +WRAP += -Wl,-wrap=pthread_mutex_lock -Wl,-wrap=pthread_mutex_unlock -Wl,-wrap=pthread_mutex_trylock -Wl,-wrap=pthread_mutex_init -Wl,-wrap=pthread_mutex_destroy +WRAP += -Wl,-wrap=sleep all: $(EXEC) diff --git a/student/student_code.h b/student/student_code.h index 3ccead1..9d10929 100644 --- a/student/student_code.h +++ b/student/student_code.h @@ -1 +1,5 @@ +/* + * This function should return 0 in all cases. + */ + int myfunc(int myargs); From 4c2abc5a9188d1be485c092011d13fe78f6c6149 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 18 Jul 2018 18:32:44 +0200 Subject: [PATCH 07/53] Prevent SIGFPE from breaking the sandbox and the tests --- student/CTester/CTester.c | 29 +++++++++++++++++++++++++++-- student/CTester/CTester.h | 3 +++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index 3bcaef1..6ec7ea1 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -31,8 +31,19 @@ extern struct wrap_log_t logs; extern sigjmp_buf segv_jmp; +/** + * Copies of the real standard output and error output + * (that is, the actual files refered to by STDOUT_FILENO and STDERR_FILENO + * before we change them using dup2(2)). + * This allows us to restore the correct stdout/stderr of the program after the sandbox. + */ int true_stderr; int true_stdout; +/** + * pipe_std(err|out)[2] : when in the sandbox, when writing on STD(ERR|OUT)_FILENO, the program will write on [1] too. At the end of the sandbox, its content (on [0]) will be writtent to usr_pip_std(err|out)[1]. + * usr_pipe_std(err|out)[2] : when quitting the sandbox, + * the content of pipe_std(err|out)[0] will be written on [1], and will be accessible on std(err|out)_cpy too. + */ int pipe_stderr[2], usr_pipe_stderr[2]; int pipe_stdout[2], usr_pipe_stdout[2]; extern int stdout_cpy, stderr_cpy; @@ -108,7 +119,8 @@ void set_tag(char *tag) strncpy(test_metadata.tags[test_metadata.nb_tags++], tag, TAGS_LEN_MAX); } -void segv_handler(int sig, siginfo_t *unused, void *unused2) { +void segv_handler(int sig, siginfo_t *unused, void *unused2) +{ wrap_monitoring = false; push_info_msg(_("Your code produced a segfault.")); set_tag("sigsegv"); @@ -116,6 +128,15 @@ void segv_handler(int sig, siginfo_t *unused, void *unused2) { siglongjmp(segv_jmp, 1); } +void fpe_handler(int sig, siginfo_t *unused, void *unused2) +{ + wrap_monitoring = false; + push_info_msg(_("Your code produced an arithmetic exception.")); + set_tag("sigfpe"); + wrap_monitoring = true; + siglongjmp(segv_jmp, 1); +} + void alarm_handler(int sig, siginfo_t *unused, void *unused2) { wrap_monitoring = false; @@ -157,7 +178,7 @@ void sandbox_end() { wrap_monitoring = false; - // Remapping stderr to the orignal one ... + // Remapping stdout and stderr to the orignal one ... dup2(true_stdout, STDOUT_FILENO); // TODO dup2(true_stderr, STDERR_FILENO); @@ -258,6 +279,10 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { sigaltstack(&ss, 0); sigfillset(&sa.sa_mask); int ret = sigaction(SIGSEGV, &sa, NULL); + if (ret) + return ret; + sa.sa_sigaction = fpe_handler; + ret = sigaction(SIGFPE, &sa, NULL); if (ret) return ret; sa.sa_sigaction = alarm_handler; diff --git a/student/CTester/CTester.h b/student/CTester/CTester.h index 4448ade..2ac4f1c 100644 --- a/student/CTester/CTester.h +++ b/student/CTester/CTester.h @@ -38,6 +38,9 @@ struct wrap_monitor_t monitored; struct wrap_fail_t failures; struct wrap_log_t logs; +/** + * Readable file descriptors that contain the student's code outputs. + */ int stdout_cpy, stderr_cpy; sigjmp_buf segv_jmp; From 01ecaba3d6d85551bc42c63d7f47518ea0965572 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 18 Jul 2018 19:13:04 +0200 Subject: [PATCH 08/53] Applying non-blocking write to pipe_stdout/pipe_stderr The original code only made the reading end of the pipe non-blocking, as it needs to empty the pipe after the sandbox. But, if the student's program actually writes a lot on the writing end of the pipe, the pipe will block and cause the program to timeout. It is better to actually report this error (too much output) rather than waiting for the task to end. In both cases, the output will be truncated and it will not pass the tests, as we may expect the problem author to not require the student to output more than 65536 bytes or so of data on stdout or on stderr. --- student/CTester/CTester.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index 6ec7ea1..6d24b7e 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -255,8 +255,11 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { int *pipes[] = {pipe_stderr, pipe_stdout, usr_pipe_stdout, usr_pipe_stderr}; for(int i=0; i < 4; i++) { // Configuring pipes to be non-blocking pipe(pipes[i]); - int flags = fcntl(pipes[i][0], F_GETFL, 0); + int flags; + flags = fcntl(pipes[i][0], F_GETFL, 0); fcntl(pipes[i][0], F_SETFL, flags | O_NONBLOCK); + flags = fcntl(pipes[i][1], F_GETFL, 0); + fcntl(pipes[i][1], F_SETFL, flags | O_NONBLOCK); } stdout_cpy = usr_pipe_stdout[0]; stderr_cpy = usr_pipe_stderr[0]; From be4ef7a1da18ebcaa6808a0bcb971e729082fd35 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sun, 22 Jul 2018 18:38:37 +0200 Subject: [PATCH 09/53] Removes temporarily the possibility for the student to call exit and bypass the tests. Also, makes sure to have a less undefined behaviour if the student actually calls exit: it will predictably raise a SIGSEGV instead of randomly not crashing instantly. --- student/CTester/CTester.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index 6d24b7e..f8c6e0a 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -230,9 +230,26 @@ void start_test() memset(&logs, 0, sizeof(logs)); } -int __real_exit(int status); -int __wrap_exit(int status){ - return status; +/** + * CTester functions should *never* call exit directly, but rather __real_exit; + * this prevents completely-undefined behaviour from arising in the correcter's program. + */ +void __real_exit(int status); + +/** + * The exit call is considered special by glibc and gcc: + * it considers that after such a call, everything that comes after it + * will never be executed normally, and therefore it doesn't bother + * adding code to perform a clean function return: + * the program will have undefined behaviour if this function, + * __wrap_exit, actually returns to the callee, and this may or may not end + * in a catchable segfault. + * If we accept the possibility of a student calling exit, then it is better + * to explicitly raise the segfault in order to have a less-undefined behaviour. + */ +void __wrap_exit(int status) { + raise(SIGSEGV); // raise SIGSEGV if it comes from the student. + // TODO add code so that the student's code can actually exit } int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { @@ -277,7 +294,7 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { .ss_sp = stack, }; - sa.sa_flags = SA_NODEFER|SA_ONSTACK|SA_RESTART; + sa.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESTART | SA_SIGINFO; sa.sa_sigaction = segv_handler; sigaltstack(&ss, 0); sigfillset(&sa.sa_mask); From 12258c94c269533bbef9a5f1f1b050eefbabc01d Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 25 Jul 2018 13:30:57 +0200 Subject: [PATCH 10/53] Updating Makefile --- student/Makefile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/student/Makefile b/student/Makefile index 820d7ae..9e94e04 100644 --- a/student/Makefile +++ b/student/Makefile @@ -1,9 +1,13 @@ CC=gcc EXEC=tests LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic -SRC=$(wildcard *.c) $(wildcard CTester/*.c) +SRC=$(sort $(wildcard *.c)) $(sort $(wildcard CTester/*.c)) OBJ=$(SRC:.c=.o) -CFLAGS=-Wall -Werror -DC99 -std=gnu99 -ICTester -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE +CFLAGS = -Wall -Werror -Wextra -Wshadow -DC99 -std=gnu99 -ICTester -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE +DEBUGFLAGS = -g -Wno-unused-variable -Wno-unused-parameter +OTHERFLAGS = -fstack-protector-all -D_FORTIFY_SOURCE=2 -fno-omit-frame-pointer +#CFLAGS += $(DEBUGFLAGS) +#CFLAGS += $(OTHERFLAGS) WRAP += -Wl,-wrap=exit WRAP += -Wl,-wrap=open -Wl,-wrap=creat -Wl,-wrap=close -Wl,-wrap=read -Wl,-wrap=write -Wl,-wrap=stat -Wl,-wrap=fstat -Wl,-wrap=lseek WRAP += -Wl,-wrap=getpid @@ -35,6 +39,10 @@ compile-mo: clean: rm -f $(EXEC) $(OBJ) +#clr-aux: +# rm -f $(INC) $(ASM) +rebuild: clean all + .PHONY: tests From 43ade1196c92cea0fec16baaf02a7ce88a1ae3bc Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 25 Jul 2018 13:28:05 +0200 Subject: [PATCH 11/53] Fixes unused parameters and shadowed declaration of variable i --- student/CTester/CTester.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index f8c6e0a..854eef2 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -121,6 +121,9 @@ void set_tag(char *tag) void segv_handler(int sig, siginfo_t *unused, void *unused2) { + (void)sig; + (void)unused; + (void)unused2; wrap_monitoring = false; push_info_msg(_("Your code produced a segfault.")); set_tag("sigsegv"); @@ -130,6 +133,9 @@ void segv_handler(int sig, siginfo_t *unused, void *unused2) void fpe_handler(int sig, siginfo_t *unused, void *unused2) { + (void)sig; + (void)unused; + (void)unused2; wrap_monitoring = false; push_info_msg(_("Your code produced an arithmetic exception.")); set_tag("sigfpe"); @@ -139,6 +145,9 @@ void fpe_handler(int sig, siginfo_t *unused, void *unused2) void alarm_handler(int sig, siginfo_t *unused, void *unused2) { + (void)sig; + (void)unused; + (void)unused2; wrap_monitoring = false; push_info_msg(_("Your code exceeded the maximal allowed execution time.")); set_tag("timeout"); @@ -248,6 +257,7 @@ void __real_exit(int status); * to explicitly raise the segfault in order to have a less-undefined behaviour. */ void __wrap_exit(int status) { + (void)status; raise(SIGSEGV); // raise SIGSEGV if it comes from the student. // TODO add code so that the student's code can actually exit } @@ -360,12 +370,12 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { if (ret < 0) return ret; - for(int i=0; i < test_metadata.nb_tags; i++) { - ret = fprintf(f_out, "%s", test_metadata.tags[i]); + for(int j=0; j < test_metadata.nb_tags; j++) { + ret = fprintf(f_out, "%s", test_metadata.tags[j]); if (ret < 0) return ret; - if (i != test_metadata.nb_tags - 1) { + if (j != test_metadata.nb_tags - 1) { ret = fprintf(f_out, ","); if (ret < 0) return ret; From 482e2f50db8f778ebd9be97ed31772171fa9aa92 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Mon, 23 Jul 2018 15:31:44 +0200 Subject: [PATCH 12/53] First version of DNS-related function wrappers --- student/CTester/wrap.h | 24 ++++ student/CTester/wrap_network_dns.c | 223 +++++++++++++++++++++++++++++ student/CTester/wrap_network_dns.h | 160 +++++++++++++++++++++ 3 files changed, 407 insertions(+) create mode 100644 student/CTester/wrap_network_dns.c create mode 100644 student/CTester/wrap_network_dns.h diff --git a/student/CTester/wrap.h b/student/CTester/wrap.h index 9cce09a..e454ed9 100644 --- a/student/CTester/wrap.h +++ b/student/CTester/wrap.h @@ -12,6 +12,7 @@ #include "wrap_file.h" #include "wrap_malloc.h" #include "wrap_mutex.h" +#include "wrap_network_dns.h" #include "wrap_sleep.h" // Basic structures for system call wrapper @@ -40,6 +41,11 @@ struct wrap_monitor_t { bool pthread_mutex_init; bool pthread_mutex_destroy; bool sleep; + + bool getaddrinfo; + bool getnameinfo; + bool freeaddrinfo; + bool gai_strerror; }; #define MAX_LOG 1000 @@ -139,6 +145,19 @@ struct wrap_fail_t { uint32_t sleep; unsigned int sleep_ret; + uint32_t getaddrinfo; + int getaddrinfo_ret; + int getaddrinfo_errno; + + uint32_t getnameinfo; + int getnameinfo_ret; + int getnameinfo_errno; + + // freeaddrinfo - this call cannot fail, unless res is not a valid pointer. + + // gai_strerror - this call cannot fail; + // if its parameter is an unkown error code, it just returns "Unknown error code" + } ; @@ -163,6 +182,11 @@ struct wrap_stats_t { struct stats_pthread_mutex_unlock_t pthread_mutex_init; struct stats_pthread_mutex_unlock_t pthread_mutex_destroy; struct stats_sleep_t sleep; + + struct stats_getaddrinfo_t getaddrinfo; + struct stats_getnameinfo_t getnameinfo; + struct stats_freeaddrinfo_t freeaddrinfo; + struct stats_gai_strerror_t gai_strerror; }; #endif // __WRAP_H_ diff --git a/student/CTester/wrap_network_dns.c b/student/CTester/wrap_network_dns.c new file mode 100644 index 0000000..bc41dee --- /dev/null +++ b/student/CTester/wrap_network_dns.c @@ -0,0 +1,223 @@ +/* + * Wrapper for getaddrinfo, freeaddrinfo, gai_strerror and getnameinfo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "wrap.h" + +int __real_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +int __real_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); +void __real_freeaddrinfo(struct addrinfo *res); +const char *__real_gai_strerror(int errcode); + +extern bool wrap_monitoring; +extern struct wrap_stats_t stats; +extern struct wrap_monitor_t monitored; +extern struct wrap_fail_t failures; + +bool check_freeaddrinfo = false; +getaddrinfo_method_t getaddrinfo_method = NULL; +freeaddrinfo_method_t freeaddrinfo_method = NULL; +getnameinfo_method_t getnameinfo_method = NULL; +gai_strerror_method_t gai_strerror_method = NULL; +freeaddrinfo_badarg_report_t freeaddrinfo_badarg_reporter = NULL; + +// Used to record the addrinfo lists "returned" by getaddrinfo, in order to check for their deallocation via freeaddrinfo. +// TODO maybe it would be better placed in the .c file ? +struct addrinfo_node_t { + struct addrinfo *addr_list; + struct addrinfo_node_t *next; +}; + +void add_result(struct addrinfo *res) +{ + struct addrinfo_node_t *new_node = malloc(sizeof(*new_node)); + if (new_node == NULL) return; + new_node->addr_list = res; + new_node->next = stats.getaddrinfo.addrinfo_list; + stats.getaddrinfo.addrinfo_list = new_node; +} + +/** + * Returns 0 if res was successfully removed of the list, + * -1 if res was not present in the list, + * -2 if there was some other errors. + */ +int remove_result(struct addrinfo *res) +{ + if (stats.getaddrinfo.addrinfo_list != NULL) { + struct addrinfo_node_t *runner = stats.getaddrinfo.addrinfo_list; + if (runner->addr_list == res) { + stats.getaddrinfo.addrinfo_list = runner->next; + free(runner); + // runner->addr_list will be freed by freeaddrinfo + return 0; + } + for (; runner->next != NULL; runner = runner->next) { + if (runner->next->addr_list == res) { + struct addrinfo_node_t *old = runner->next; + runner->next = runner->next->next; + free(old); + // old->addr_list will be freed by freeaddrinfo when we return + return 0; + } + } + return -1; + } else { + return -1; + } +} + + +int __wrap_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) +{ + if (! (wrap_monitoring && monitored.getaddrinfo)) { + return __real_getaddrinfo(node, service, hints, res); + } + stats.getaddrinfo.called++; + stats.getaddrinfo.last_params = (struct params_getaddrinfo_t) { + .node = node, + .service = service, + .hints = hints, + .res = res + }; + if (FAIL(failures.getaddrinfo)) { + failures.getaddrinfo = NEXT(failures.getaddrinfo); + errno = failures.getaddrinfo_errno; + stats.getaddrinfo.last_return = failures.getaddrinfo_ret; + return failures.getaddrinfo_ret; + } + failures.getaddrinfo = NEXT(failures.getaddrinfo); + int ret; + if (getaddrinfo_method == NULL) { + ret = __real_getaddrinfo(node, service, hints, res); + } else { + ret = (*getaddrinfo_method)(node, service, hints, res); + } + stats.getaddrinfo.last_return = ret; + /* + * At this point, we may assume that *res is a valid memory location, + * as the program will segfault if it is not. + */ + if (check_freeaddrinfo) { + add_result(*res); + } + return ret; +} + +int __wrap_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, + char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) +{ + if (! (wrap_monitoring && monitored.getnameinfo)) { + return __real_getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags); + } + stats.getnameinfo.called++; + stats.getnameinfo.last_params = (struct params_getnameinfo_t) { + .addr = addr, + .addrlen = addrlen, + .host = host, + .hostlen = hostlen, + .serv = serv, + .servlen = servlen, + .flags = flags + }; + if (FAIL(failures.getnameinfo)) { + failures.getnameinfo = NEXT(failures.getnameinfo); + errno = failures.getnameinfo_errno; + stats.getnameinfo.last_return = failures.getnameinfo_ret; + return failures.getnameinfo_ret; + } + failures.getnameinfo = NEXT(failures.getnameinfo); + getnameinfo_method_t met = &__real_getnameinfo; + if (getnameinfo_method != NULL) { + met = getnameinfo_method; + } + int ret = (*met)(addr, addrlen, host, hostlen, serv, servlen, flags); + stats.getnameinfo.last_return = ret; + return ret; +} + +/** + * The student should call freeaddrinfo on all the result structures that have been initialized by getaddrinfo; failing to do so leads to memory leak. + * If the parameter check_freeaddrinfo is true, then this function will check if res was actually returned by getaddrinfo, and is not either an invalid pointer, or a non-first addrinfo in one of the lists. + */ +void __wrap_freeaddrinfo(struct addrinfo *res) +{ + if (! (wrap_monitoring && monitored.freeaddrinfo)) { + __real_freeaddrinfo(res); + return; + } + stats.freeaddrinfo.called++; + stats.freeaddrinfo.last_param = res; + if (check_freeaddrinfo) { + if (remove_result(res) == 0) { + // Okay, it is valid + stats.freeaddrinfo.status = 0; + } else { + // Not okay: it is invalid (e.g. this is the second item of the list instead of the first). + // TODO report the error, as it is still invalid + stats.freeaddrinfo.status = 1; + if (freeaddrinfo_badarg_reporter != NULL) { + (*freeaddrinfo_badarg_reporter)(); + } + } + } else { + stats.freeaddrinfo.status = 0; // will not check + } + if (freeaddrinfo_method == NULL) { + __real_freeaddrinfo(res); + } else { + freeaddrinfo_method(res); + } +} + +const char * __wrap_gai_strerror(int ecode) +{ + if (! (wrap_monitoring && monitored.getaddrinfo)) { + return __real_gai_strerror(ecode); + } + stats.gai_strerror.called++; + stats.gai_strerror.last_params = ecode; + gai_strerror_method_t met = (gai_strerror_method == NULL ? &__real_gai_strerror : gai_strerror_method); + const char *ret = (*met)(ecode); + stats.gai_strerror.last_return = ret; + return ret; +} + + +void set_getaddrinfo_method(getaddrinfo_method_t method) +{ + getaddrinfo_method = method; +} + +void set_gai_methods(getaddrinfo_method_t gai_method, freeaddrinfo_method_t fai_method) +{ + getaddrinfo_method = gai_method; + freeaddrinfo_method = fai_method; +} + +void set_getnameinfo_method(getnameinfo_method_t method) +{ + getnameinfo_method = method; +} + +void set_gai_strerror_method(gai_strerror_method_t method) +{ + gai_strerror_method = method; +} + diff --git a/student/CTester/wrap_network_dns.h b/student/CTester/wrap_network_dns.h new file mode 100644 index 0000000..d174805 --- /dev/null +++ b/student/CTester/wrap_network_dns.h @@ -0,0 +1,160 @@ +/* + * Wrapper for getaddrinfo, freeaddrinfo, gai_strerror and getnameinfo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __WRAP_NETWORK_DNS_H_ +#define __WRAP_NETWORK_DNS_H_ + +#include +#include + +// getaddrinfo parameters structures and statistics structure + +struct params_getaddrinfo_t { + const char *node; + const char *service; + const struct addrinfo *hints; + struct addrinfo **res; +}; + +struct addrinfo_node_t; + +struct stats_getaddrinfo_t { + int called; // number of times the getaddrinfo call has been issued + struct params_getaddrinfo_t last_params; // parameters for the last monitored call + struct addrinfo_node_t *addrinfo_list; // list of addrinfo lists "returned" by getaddrinfo + int last_return; // return value of the last monitored call issued +}; + +// getnameinfo parameters structures and statistics structure + +struct params_getnameinfo_t { + const struct sockaddr *addr; + socklen_t addrlen; + char *host; + socklen_t hostlen; + char *serv; + socklen_t servlen; + int flags; +}; + +struct stats_getnameinfo_t { + int called; // number of calls + struct params_getnameinfo_t last_params; // parameters of the last monitored call + int last_return; // return value of the last monitored call issued +}; + +// freeaddrinfo parameters structures and statistics structure + +struct stats_freeaddrinfo_t { + int called; // number of calls + struct addrinfo *last_param; // parameter of the last monitored call issued + /** status of the last monitored call: + * if check_freeaddrinfo is true + * and last_param was not returned by getaddrinfo during the monitoring, + * then status is 1; + * else, status is zero. + */ + int status; +}; + +// gai_strerror parameters structures and statistics structure + +struct stats_gai_strerror_t { + int called; // number of calls + int last_params; // parameter of the last monitored call + const char *last_return; // return value of the last monitored call +}; + +// TODO Maybe add some init, clean and resetstats methods to these calls ? + +/** + * When check is true, freeaddrinfo will check if its argument has been "returned" by an earlier call to getaddrinfo directly, and will report if it is not the case. + */ +void set_check_freeaddrinfo(bool check); + + +/** + * Functions of this type should return in *res a valid list of struct addrinfo, + * suitable to be freed by a freeaddrinfo_method_t. + */ +typedef int (*getaddrinfo_method_t)(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +/** + * Functions of this type should be able to free structures allocated + * by a method of type getaddrinfo_method_t. + */ +typedef void (*freeaddrinfo_method_t)(struct addrinfo *res); + +/** + * if method is not NULL, replaces __real_getaddrinfo by the function "method" + * as the real function executed by the wrapper. + * If method is NULL, resets the method to __real_getaddrinfo. + * This method only sets the substitute for the getaddrinfo function, + * and not the substitute for the freeaddrinfo function; + * incompatible definitions of these functions may result in memleaks + * or double free if care is not taken. + */ +void set_getaddrinfo_method(getaddrinfo_method_t method); + +/** + * Sets the getaddrinfo and freeaddrinfo methods to be used. + * Specifying NULL for a parameter means that the system will use + * the default, system-defined method (__real_getaddrinfo or + * __real_freeaddrinfo). + * This is especially needed as the POSIX standard doesn't specify the way + * the list of struct addrinfo in generated: glibc allocates a single block + * of memory for both the struct addrinfo and its struct sockaddr, + * as well as one block of memory for the canonical name. Therefore, + * calling __real_freeaddrinfo on a custom-built list may result in memleaks, + * and using a wrong freeaddrinfo_method_t to free a list generated by + * __real_getaddrinfo may cause a double free. + */ +void set_gai_methods(getaddrinfo_method_t gai_method, freeaddrinfo_method_t fai_method); + + +typedef int (*getnameinfo_method_t)(const struct sockaddr *addr, + socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); + +/** + * If method is not NULL, replaces __real_getnameinfo by the function "method" + * as the real function executed by the wrapper. + * If method is NULL, resets the method to __real_getnameinfo. + */ +void set_getnameinfo_method(getnameinfo_method_t method); + + +typedef const char *(*gai_strerror_method_t)(int); + +/** + * If method is not NULL, replaces __real_gai_strerror by the function "method" + * as the real function called by the wrapper. + * If method is NULL, resets the method to __real_gai_strerror. + * This allows specifying other error messages than the standard ones. + */ +void set_gai_strerror_method(gai_strerror_method_t method); + + +typedef void (*freeaddrinfo_badarg_report_t)(); + +/** + * Sets the method to be called if check_freeaddrinfo is true + * and the argument passed to freeaddrinfo was not returned by getaddrinfo. + * This is useful if you want to use the push_info_msg method + * for your feedback, for example. Default is NULL. + */ +void set_freeaddrinfo_badarg_report(freeaddrinfo_badarg_report_t reporter); + +#endif // __WRAP_NETWORK_DNS_H_ + From 633320dfaa4910598a334e2fa7f8c368f158e9f1 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Mon, 23 Jul 2018 18:54:27 +0200 Subject: [PATCH 13/53] Update to DNS functions; it seems to work --- student/CTester/wrap_network_dns.c | 97 ++++++++++++++++++++++++++++++ student/CTester/wrap_network_dns.h | 19 ++++++ 2 files changed, 116 insertions(+) diff --git a/student/CTester/wrap_network_dns.c b/student/CTester/wrap_network_dns.c index bc41dee..3cb74e3 100644 --- a/student/CTester/wrap_network_dns.c +++ b/student/CTester/wrap_network_dns.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "wrap.h" @@ -221,3 +222,99 @@ void set_gai_strerror_method(gai_strerror_method_t method) gai_strerror_method = method; } + +int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res) +{ + uint8_t buf[sizeof(struct in6_addr)]; + memset(buf, 0, sizeof(buf)); + int family = hints->ai_family; + if (node == NULL) { + if (family == AF_INET) { + node = "127.0.0.1"; + } else { + node = "::1"; + } + } + // Let's try IPv4 + int s = inet_pton(AF_INET, node, buf); + if (s == -1) { // error; should not happen + return EAI_FAMILY; + } else if (s == 1) { // IPVv4 + family = AF_INET; + } else { // Let's try IPv6 + memset(buf, 0, sizeof(buf)); + s = inet_pton(AF_INET6, node, buf); + if (s == 1) { + family = AF_INET6; + } else if (s == -1) { // error; should not happen + return EAI_FAMILY; + } else { // badly encoded then + return EAI_NONAME; + } + } + if (hints->ai_family != AF_UNSPEC && family != hints->ai_family) + return EAI_FAMILY; + // Converted, and family identified + *res = malloc(sizeof(struct addrinfo)); + struct addrinfo *rp = *res; + if (rp == NULL) + return EAI_MEMORY; + rp->ai_flags = 0; + rp->ai_family = family; + rp->ai_socktype = hints->ai_socktype; + rp->ai_protocol = hints->ai_protocol; + if ((hints->ai_flags & AI_CANONNAME) != 0) { + rp->ai_canonname = malloc(strlen(node) + 2); + if (rp->ai_canonname == NULL) { + free(rp); + *res = NULL; + return EAI_MEMORY; + } + strcpy((rp->ai_canonname) + 1, node); + rp->ai_canonname[0] = 'C'; + } else { + rp->ai_canonname = NULL; + } + rp->ai_next = NULL; + if (family == AF_INET) { + struct sockaddr_in *addr = malloc(sizeof(struct sockaddr_in)); + if (addr == NULL) { + free(rp->ai_canonname); + free(rp); + *res = NULL; + return EAI_MEMORY; + } + addr->sin_family = AF_INET; + addr->sin_port = htons((uint16_t)(strtol(serv, NULL, 10))); + memcpy(&(addr->sin_addr), buf, sizeof(struct in_addr)); + rp->ai_addr = (struct sockaddr*)addr; + rp->ai_addrlen = sizeof(struct sockaddr_in); + } else { + struct sockaddr_in6 *addr = malloc(sizeof(struct sockaddr_in6)); + if (addr == NULL) { + free(rp->ai_canonname); + free(rp); + *res = NULL; + return EAI_MEMORY; + } + memset(addr, 0, sizeof(*addr)); + addr->sin6_family = AF_INET6; + addr->sin6_port = htons((uint16_t)(strtol(serv, NULL, 10))); + memcpy(&(addr->sin6_addr), buf, sizeof(struct in6_addr)); + rp->ai_addr = (struct sockaddr*)addr; + rp->ai_addrlen = sizeof(struct sockaddr_in6); + } + return 0; +} + +void simple_freeaddrinfo(struct addrinfo *res) +{ + struct addrinfo *p = res; + while (p != NULL) { + struct addrinfo *old = p; + p = p->ai_next; + free(old->ai_canonname); + free(old->ai_addr); + free(old); + } +} diff --git a/student/CTester/wrap_network_dns.h b/student/CTester/wrap_network_dns.h index d174805..a2d3e0e 100644 --- a/student/CTester/wrap_network_dns.h +++ b/student/CTester/wrap_network_dns.h @@ -156,5 +156,24 @@ typedef void (*freeaddrinfo_badarg_report_t)(); */ void set_freeaddrinfo_badarg_report(freeaddrinfo_badarg_report_t reporter); + +/** + * Replaces getaddrinfo by a version which doesn't call DNS. + * node should be a numerical address, which will be converted using inet_pton + * and stored inside the answer. + * serv should be a positive short integer, representing the port. + * Returns only one addrinfo in *res, with appropriate values taken from + * node, serv and the fields from hints. Also, the canonical name is set, + * if specified, by 'C' followed by node. + */ +int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res); + +/** + * Necessary for cross-library compatibility: should be used in place + * of the default freeaddrinfo to free the list returned by simple_getaddrinfo; + * see set_gai_methods for more information. + */ +void simple_freeaddrinfo(struct addrinfo *res); + #endif // __WRAP_NETWORK_DNS_H_ From 29a4a7015c7a35c364f968852964d9498f837570 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 25 Jul 2018 13:33:57 +0200 Subject: [PATCH 14/53] Updating Makefile --- student/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/student/Makefile b/student/Makefile index 9e94e04..885a683 100644 --- a/student/Makefile +++ b/student/Makefile @@ -13,6 +13,7 @@ WRAP += -Wl,-wrap=open -Wl,-wrap=creat -Wl,-wrap=close -Wl,-wrap=read -Wl,-wrap= WRAP += -Wl,-wrap=getpid WRAP += -Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=realloc -Wl,-wrap=calloc WRAP += -Wl,-wrap=pthread_mutex_lock -Wl,-wrap=pthread_mutex_unlock -Wl,-wrap=pthread_mutex_trylock -Wl,-wrap=pthread_mutex_init -Wl,-wrap=pthread_mutex_destroy +WRAP += -Wl,-wrap=getaddrinfo -Wl,-wrap=getnameinfo -Wl,-wrap=freeaddrinfo -Wl,-wrap=gai_strerror WRAP += -Wl,-wrap=sleep all: $(EXEC) From a19e3029a6731e4dc230bddc1d6b71d70b2fd87c Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Tue, 31 Jul 2018 20:18:36 +0200 Subject: [PATCH 15/53] Wrapper functions for socket and related functions Functions: socket, bind, connect, accept, listen, recv (and al), send (and al). Currently only supports statistics Also included: Some unit tests --- ci/run_ci | 2 +- ci/test_network_dns/expected_results.txt | 1 + ci/test_network_dns/student_code.c | 215 +++++++++++++ ci/test_network_dns/student_code.h | 31 ++ ci/test_network_dns/tests.c | 184 ++++++++++++ student/CTester/wrap.h | 78 +++++ student/CTester/wrap_network_dns.c | 9 + student/CTester/wrap_network_dns.h | 3 + student/CTester/wrap_network_socket.c | 366 +++++++++++++++++++++++ student/CTester/wrap_network_socket.h | 216 +++++++++++++ student/Makefile | 1 + 11 files changed, 1105 insertions(+), 1 deletion(-) create mode 100644 ci/test_network_dns/expected_results.txt create mode 100644 ci/test_network_dns/student_code.c create mode 100644 ci/test_network_dns/student_code.h create mode 100644 ci/test_network_dns/tests.c create mode 100644 student/CTester/wrap_network_socket.c create mode 100644 student/CTester/wrap_network_socket.h diff --git a/ci/run_ci b/ci/run_ci index 0df5267..7326f87 100755 --- a/ci/run_ci +++ b/ci/run_ci @@ -1,6 +1,6 @@ #!/bin/bash -declare -a tests=("test-simple-success" "test-simple-fail") +declare -a tests=("test-simple-fail" "test-simple-success" "test_network_dns") cd "$(dirname "$0")" exec_test() { diff --git a/ci/test_network_dns/expected_results.txt b/ci/test_network_dns/expected_results.txt new file mode 100644 index 0000000..9637098 --- /dev/null +++ b/ci/test_network_dns/expected_results.txt @@ -0,0 +1 @@ +wrapper_stats#SUCCESS#Tests that the wrapper functions correctly remembers the stats#1# diff --git a/ci/test_network_dns/student_code.c b/ci/test_network_dns/student_code.c new file mode 100644 index 0000000..2a60980 --- /dev/null +++ b/ci/test_network_dns/student_code.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "student_code.h" + +/** + * This is the simple example from the BeeJ guide. + */ +void run_student_tests_wrapper_stats_1(struct f1_stats *stats) +{ + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + int s = getaddrinfo("inginious.info.ucl.ac.be", "443", &hints, &rep); + if (s != 0) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + stats->getaddrinfo_success = true; + int sfd = -1; + for (rp = rep; rp != NULL; rp = rp->ai_next) { + stats->nb_addr_tried++; + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) { + perror("socket"); + continue; + } + stats->nb_connect++; + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + perror("connect"); + close(sfd); + sfd = -1; + } + freeaddrinfo(rep); + if (sfd != -1) { + char coucou[] = "coucou"; + ssize_t nsent; + do { + nsent = send(sfd, coucou, sizeof(coucou), 0); + stats->nb_send++; + if (nsent != -1) { + stats->nb_bytes += nsent; + } else { + perror("send"); + } + if (nsent != sizeof(coucou)) { + break; + } + } while (nsent != -1 && stats->nb_bytes < 42); + } +} + +/* + * The following two codes are adapted from the Linux man pages + * for getaddrinfo + */ +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = 0; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("socket"); + continue; + } + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nconnect++; + break; + } + perror("connect"); + stats->nconnect++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + char *words[3]; + words[0] = "coucou"; + words[1] = "salut"; + words[2] = "STOP"; + for (int i = 0; i < 3; i++) { + // Send the three messages + int len = strlen(words[i]) + 1; + // Let's use a struct msghdr to see if it works + struct msghdr msg; + msg.msg_name = NULL; + msg.msg_namelen = 0; + struct iovec msg_iov[1]; + msg_iov[0].iov_base = words[i]; + msg_iov[0].iov_len = len; + msg.msg_iov = msg_iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ssize_t nsent = sendmsg(sfd, &msg, 0); + if (nsent == -1) + perror("send"); + stats->nsend++; + if (nsent != len) { + fprintf(stderr, "ACouldn't write\n"); + continue; + } + // Let's read the data again + memset(buf, 0, sizeof(buf)); + nread = recv(sfd, buf, 500, 0); + stats->nrecv++; + if (nread == -1) { + perror("Arecv"); + continue; + } + if (strcmp(buf, words[i]) != 0) { + fprintf(stderr, "AMessage don't match\n"); + continue; + } + } +} + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + struct sockaddr_storage peer_addr; + socklen_t peer_addrlen; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_PASSIVE; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("Bsocket"); + continue; + } + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nbind++; + break; + } + perror("Bbind"); + stats->nbind++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + for (int failures = 0; failures < 10;) { + memset(buf, 0, sizeof(buf)); + // Stops at the reception of "stop" + peer_addrlen = sizeof(peer_addr); + nread = recvfrom(sfd, buf, 500, 0, (struct sockaddr*) &peer_addr, &peer_addrlen); + stats->nrecvfrom++; + if (nread == -1) + perror("recvfrom"); + if (nread == -1) { + failures++; + continue; + } + // Let's skip the printing here + ssize_t nsent = sendto(sfd, buf, nread, 0, (struct sockaddr*) &peer_addr, peer_addrlen); + stats->nsendto++; + if (nsent != nread) { + failures++; + } + if (nsent == -1) { + perror("Bsent"); + } + if (strcmp(buf, "STOP") == 0) { + break; + } + } +} + diff --git a/ci/test_network_dns/student_code.h b/ci/test_network_dns/student_code.h new file mode 100644 index 0000000..342c2d1 --- /dev/null +++ b/ci/test_network_dns/student_code.h @@ -0,0 +1,31 @@ +#include +struct f1_stats { + bool getaddrinfo_success; + int nb_addr_tried; + int nb_connect; + int nb_send; + ssize_t nb_bytes; +}; +struct stats2_server { + int ngai; + int nsocket; + int nbind; + int nrecvfrom; + int nsendto; +}; +struct stats2_client { + int ngai; + int nsocket; + int nconnect; + int nsend; + int nrecv; +}; + +void run_student_tests_wrapper_stats_1(struct f1_stats *stats); + +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats); + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats); + +// void run_student_tests_wrapper_stats_3(); + diff --git a/ci/test_network_dns/tests.c b/ci/test_network_dns/tests.c new file mode 100644 index 0000000..814d98c --- /dev/null +++ b/ci/test_network_dns/tests.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include + +#include "CTester/CTester.h" +#include "student_code.h" + +void __real_exit(int status); // Needed as otherwise we'll get a segfault + +void test_wrapper_stats() +{ + // Let's enable the stats for all functions + set_test_metadata("wrapper_stats", _("Tests that the wrapper functions correctly remembers the stats"), 1); + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, 0); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, 0); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, 0); + CU_ASSERT_EQUAL(stats.socket.called, 0); + + CU_ASSERT_EQUAL(stats.accept.last_return, 0); + CU_ASSERT_EQUAL(stats.bind.last_return, 0); + CU_ASSERT_EQUAL(stats.connect.last_return, 0); + CU_ASSERT_EQUAL(stats.listen.last_return, 0); + CU_ASSERT_EQUAL(stats.recv.last_return, 0); + CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); + CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.send.last_return, 0); + CU_ASSERT_EQUAL(stats.sendto.last_return, 0); + CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.socket.last_return, 0); + + monitored.getaddrinfo = monitored.freeaddrinfo = true; + monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = true; + MONITOR_ALL_RECV(monitored, true); + MONITOR_ALL_SEND(monitored, true); + CU_ASSERT_EQUAL(monitored.recv, true); + CU_ASSERT_EQUAL(monitored.recvfrom, true); + CU_ASSERT_EQUAL(monitored.recvmsg, true); + CU_ASSERT_EQUAL(monitored.send, true); + CU_ASSERT_EQUAL(monitored.sendto, true); + CU_ASSERT_EQUAL(monitored.sendmsg, true); + + // wrap_monitoring is set to true inside the sandbox, and set to false at the end of it. + struct f1_stats stats1; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_1(&stats1); + SANDBOX_END; + + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, stats1.nb_connect); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, stats1.nb_send); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, stats1.nb_send); + CU_ASSERT_EQUAL(stats.socket.called, stats1.nb_addr_tried); + + reinit_stats_network_dns(); + reinit_network_socket_stats(); // Simpler for the next calls + + int pid; + pid = fork(); + if (pid == 0) { + struct stats2_server statss; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_server(&statss); + SANDBOX_END; + // I must exit to let the other process terminate. No test here ! + __real_exit(0); + } else if (pid != -1) { + struct stats2_client statss; + memset(&statss, 0, sizeof(statss)); + sleep(1); // Strangely extremely important FIXME + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_client(&statss); + SANDBOX_END; + int status = 0; + waitpid(pid, &status, 0); + // Let's have a look at the statistics for the client + CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); + CU_ASSERT_EQUAL(stats.connect.called, statss.nconnect); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, (statss.ngai > 0 && statss.nsocket > 0)); + CU_ASSERT_EQUAL(stats.sendmsg.called, statss.nsend); + CU_ASSERT_EQUAL(stats.send_all.called, statss.nsend); + CU_ASSERT_EQUAL(stats.recv.called, statss.nrecv); + CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecv); + } else { + CU_FAIL(); + } + + reinit_stats_network_dns(); + reinit_network_socket_stats(); + + pid = fork(); + if (pid == 0) { + struct stats2_client statss; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_client(&statss); + SANDBOX_END; + // I must exit to let the other process terminate. No test here ! + __real_exit(0); + } else if (pid != -1) { + struct stats2_server statss; + memset(&statss, 0, sizeof(statss)); + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_server(&statss); + SANDBOX_END; + int status = 0; + waitpid(pid, &status, 0); + // Let's have a look at the statistics for the server + CU_ASSERT_EQUAL(stats.bind.called, statss.nbind); + CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); + CU_ASSERT_EQUAL(stats.sendto.called, statss.nsendto); + CU_ASSERT_EQUAL(stats.recvfrom.called, statss.nrecvfrom); + CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecvfrom); + CU_ASSERT_EQUAL(stats.send_all.called, statss.nsendto); + } else { + CU_FAIL(); + } + + monitored.getaddrinfo = monitored.freeaddrinfo = false; + monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = false; + MONITOR_ALL_RECV(monitored, false); + MONITOR_ALL_SEND(monitored, false); + CU_ASSERT_EQUAL(monitored.recv, false); + CU_ASSERT_EQUAL(monitored.recvfrom, false); + CU_ASSERT_EQUAL(monitored.recvmsg, false); + CU_ASSERT_EQUAL(monitored.send, false); + CU_ASSERT_EQUAL(monitored.sendto, false); + CU_ASSERT_EQUAL(monitored.sendmsg, false); + + reinit_stats_network_dns(); + reinit_network_socket_stats(); + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, 0); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, 0); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, 0); + CU_ASSERT_EQUAL(stats.socket.called, 0); + + CU_ASSERT_EQUAL(stats.accept.last_return, 0); + CU_ASSERT_EQUAL(stats.bind.last_return, 0); + CU_ASSERT_EQUAL(stats.connect.last_return, 0); + CU_ASSERT_EQUAL(stats.listen.last_return, 0); + CU_ASSERT_EQUAL(stats.recv.last_return, 0); + CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); + CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.send.last_return, 0); + CU_ASSERT_EQUAL(stats.sendto.last_return, 0); + CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.socket.last_return, 0); +} +// Check failures: +// Recall we must use the failures struct, and assign its (call), (call)_ret and (call)_errno fields. +// And we have the fileds FAIL_ALWAYS, FAIL_NEVER, FAIL_FIRST, FAIL_SECOND, FAIL_THIRD and FAIL_TWICE + +int main(int argc, char **argv) +{ + RUN(test_wrapper_stats); +} + diff --git a/student/CTester/wrap.h b/student/CTester/wrap.h index e454ed9..b4ee72d 100644 --- a/student/CTester/wrap.h +++ b/student/CTester/wrap.h @@ -13,6 +13,7 @@ #include "wrap_malloc.h" #include "wrap_mutex.h" #include "wrap_network_dns.h" +#include "wrap_network_socket.h" #include "wrap_sleep.h" // Basic structures for system call wrapper @@ -46,8 +47,28 @@ struct wrap_monitor_t { bool getnameinfo; bool freeaddrinfo; bool gai_strerror; + + bool accept; + bool bind; + bool connect; + bool listen; + bool recv; + bool recvfrom; + bool recvmsg; + bool send; + bool sendto; + bool sendmsg; + bool socket; }; +#define MONITOR_ALL_RECV(m, v) do { \ + m.recv = m.recvfrom = m.recvmsg = v; \ +} while (0); + +#define MONITOR_ALL_SEND(m, v) do { \ + m.send = m.sendto = m.sendmsg = v; \ +} while (0); + #define MAX_LOG 1000 // log for specific system calls @@ -158,6 +179,49 @@ struct wrap_fail_t { // gai_strerror - this call cannot fail; // if its parameter is an unkown error code, it just returns "Unknown error code" + uint32_t accept; + int accept_ret; + int accept_errno; + + uint32_t bind; + int bind_ret; + int bind_errno; + + uint32_t connect; + int connect_ret; + int connect_errno; + + uint32_t listen; + int listen_ret; + int listen_errno; + + uint32_t recv; + int recv_ret; + int recv_errno; + + uint32_t recvfrom; + int recvfrom_ret; + int recvfrom_errno; + + uint32_t recvmsg; + int recvmsg_ret; + int recvmsg_errno; + + uint32_t send; + int send_ret; + int send_errno; + + uint32_t sendto; + int sendto_ret; + int sendto_errno; + + uint32_t sendmsg; + int sendmsg_ret; + int sendmsg_errno; + + uint32_t socket; + int socket_ret; + int socket_errno; } ; @@ -187,6 +251,20 @@ struct wrap_stats_t { struct stats_getnameinfo_t getnameinfo; struct stats_freeaddrinfo_t freeaddrinfo; struct stats_gai_strerror_t gai_strerror; + + struct stats_accept_t accept; + struct stats_bind_t bind; + struct stats_connect_t connect; + struct stats_listen_t listen; + struct stats_recv_t recv; + struct stats_recvfrom_t recvfrom; + struct stats_recvmsg_t recvmsg; + struct stats_recv_all_t recv_all; + struct stats_send_t send; + struct stats_sendto_t sendto; + struct stats_sendmsg_t sendmsg; + struct stats_send_all_t send_all; + struct stats_socket_t socket; }; #endif // __WRAP_H_ diff --git a/student/CTester/wrap_network_dns.c b/student/CTester/wrap_network_dns.c index 3cb74e3..4471f1f 100644 --- a/student/CTester/wrap_network_dns.c +++ b/student/CTester/wrap_network_dns.c @@ -201,6 +201,15 @@ const char * __wrap_gai_strerror(int ecode) } +void reinit_stats_network_dns() +{ + memset(&(stats.getaddrinfo), 0, sizeof(stats.getaddrinfo)); + memset(&(stats.freeaddrinfo), 0, sizeof(stats.freeaddrinfo)); + memset(&(stats.getnameinfo), 0, sizeof(stats.getnameinfo)); + memset(&(stats.gai_strerror), 0, sizeof(stats.gai_strerror)); +} + + void set_getaddrinfo_method(getaddrinfo_method_t method) { getaddrinfo_method = method; diff --git a/student/CTester/wrap_network_dns.h b/student/CTester/wrap_network_dns.h index a2d3e0e..eba1dc9 100644 --- a/student/CTester/wrap_network_dns.h +++ b/student/CTester/wrap_network_dns.h @@ -79,6 +79,9 @@ struct stats_gai_strerror_t { // TODO Maybe add some init, clean and resetstats methods to these calls ? +void reinit_stats_network_dns(); + + /** * When check is true, freeaddrinfo will check if its argument has been "returned" by an earlier call to getaddrinfo directly, and will report if it is not the case. */ diff --git a/student/CTester/wrap_network_socket.c b/student/CTester/wrap_network_socket.c new file mode 100644 index 0000000..c076055 --- /dev/null +++ b/student/CTester/wrap_network_socket.c @@ -0,0 +1,366 @@ +/* + * Wrapper for accept, bind, connect, listen, poll, select, + * recv, recvfrom, recvmsg, send, sendto, sendmsg, socket. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "wrap.h" + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + + +int __real_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +int __real_bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int __real_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int __real_listen(int sockfd, int backlog); +ssize_t __real_recv(int sockfd, void *buf, size_t len, int flags); +ssize_t __real_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); +ssize_t __real_recvmsg(int sockfd, struct msghdr *msg, int flags); +ssize_t __real_send(int sockfd, const void *buf, size_t len, int flags); +ssize_t __real_sendmsg(int sockfd, const struct msghdr *msg, int flags); +ssize_t __real_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); +int __real_socket(int domain, int type, int protocol); + + +extern bool wrap_monitoring; +extern struct wrap_stats_t stats; +extern struct wrap_monitor_t monitored; +extern struct wrap_fail_t failures; + + +/* + * Note: addr should point to an existing sockaddr, or NULL if we don't want it + * and addrlen should point to an existing socklen_t containing the size + * of the existing sockaddr, and will be modified to reflect the true size + * of the returned sockaddr; it may be greater than the original value + * if the provided sockaddr is too small, and this sockaddr will be truncated. + * If addr in NULL, addrlen should also be NULL. + */ +int __wrap_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) +{ + if (!(wrap_monitoring && monitored.accept)) { + return __real_accept(sockfd, addr, addrlen); + } + socklen_t old_addrlen = (addrlen == NULL ? 0 : *addrlen); // May crash if addrlen doesn't point to a valid address. + stats.accept.called++; + stats.accept.last_params = (struct params_accept_t) { + .sockfd = sockfd, + .addr = addr, + .addrlen_ptr = addrlen + }; + // Reinit stats + stats.accept.last_returns.addrlen = 0; + memset(&(stats.accept.last_returns.addr), 0, sizeof(struct sockaddr_storage)); + if (FAIL(failures.accept)) { + failures.accept = NEXT(failures.accept); + errno = failures.accept_errno; + return (stats.accept.last_return = failures.accept_ret); + } + failures.accept = NEXT(failures.accept); + int ret = -2; + ret = __real_accept(sockfd, addr, addrlen); + if (ret == 0 && addr != NULL) { + /* + * We should only copy the returned address + * - if there was no error (otherwise it is probably not wise) + * - if addr != NULL; if addr == NULL, then whatever addrlen is, + * nothing was returned + */ + stats.accept.last_returns.addrlen = (addrlen == NULL ? 0 : *addrlen); + /* + * We need the minimum as *addrlen may be greater than old_addrlen + * and grater than the size of *addr, and we're not interested + * in potential garbage not set by accept in the remaining space. + * This can't segfault as ret == 0 + */ + memcpy(&(stats.accept.last_returns.addr), addr, MIN(old_addrlen, *addrlen)); + } // else: there was an error, so the safest thing to do is ignore the value provided + return (stats.accept.last_return = ret); +} + +int __wrap_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + if (!(wrap_monitoring && monitored.bind)) { + return __real_bind(sockfd, addr, addrlen); + } + stats.bind.called++; + stats.bind.last_params = (struct params_bind_t) { + .sockfd = sockfd, + .addr = addr, + .addrlen = addrlen + }; + if (FAIL(failures.bind)) { + failures.bind = NEXT(failures.bind); + errno = failures.bind_errno; + return (stats.bind.last_return = failures.bind_ret); + } + failures.bind = NEXT(failures.bind); + int ret = -2; + ret = __real_bind(sockfd, addr, addrlen); + return (stats.bind.last_return = ret); +} + +int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + if (!(wrap_monitoring && monitored.connect)) { + return __real_connect(sockfd, addr, addrlen); + } + stats.connect.called++; + stats.connect.last_params = (struct params_connect_t) { + .sockfd = sockfd, + .addr = addr, + .addrlen = addrlen + }; + if (FAIL(failures.connect)) { + failures.connect = NEXT(failures.connect); + errno = failures.connect_errno; + return (stats.connect.last_return = failures.connect_ret); + } + failures.connect = NEXT(failures.connect); + int ret = -2; + ret = __real_connect(sockfd, addr, addrlen); + return (stats.connect.last_return = ret); +} + +int __wrap_listen(int sockfd, int backlog) +{ + if (!(wrap_monitoring && monitored.listen)) { + return __real_listen(sockfd, backlog); + } + stats.listen.called++; + stats.listen.last_params = (struct params_listen_t) { + .sockfd = sockfd, + .backlog = backlog + }; + if (FAIL(failures.listen)) { + failures.listen = NEXT(failures.listen); + errno = failures.listen_errno; + return (stats.listen.last_return = failures.listen_ret); + } + failures.listen = NEXT(failures.listen); + int ret = -2; + ret = __real_listen(sockfd, backlog); + return (stats.listen.last_return = ret); +} + +ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) +{ + if (!(wrap_monitoring && monitored.recv)) { + return __real_recv(sockfd, buf, len, flags); + } + stats.recv.called++; + stats.recv_all.called++; + stats.recv.last_params = (struct params_recv_t) { + .sockfd = sockfd, + .buf = buf, + .len = len, + .flags = flags + }; + if (FAIL(failures.recv)) { + failures.recv = NEXT(failures.recv); + errno = failures.recv_errno; + return (stats.recv.last_return = failures.recv_ret); + } + failures.recv = NEXT(failures.recv); + ssize_t ret = -1; + ret = __real_recv(sockfd, buf, len, flags); + return (stats.recv.last_return = ret); +} + +ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) +{ + if (!(wrap_monitoring && monitored.recvfrom)) { + return __real_recvfrom(sockfd, buf, len, flags, src_addr, addrlen); + } + socklen_t old_addrlen = (addrlen == NULL ? 0 : *addrlen); // May crash + stats.recvfrom.called++; + stats.recv_all.called++; + stats.recvfrom.last_params = (struct params_recvfrom_t) { + .sockfd = sockfd, + .buf = buf, + .len = len, + .flags = flags, + .src_addr = src_addr, + .addrlen_ptr = addrlen + }; + stats.recvfrom.last_returned_addr.addrlen = 0; + memset(&(stats.recvfrom.last_returned_addr.src_addr), 0, sizeof(struct sockaddr_storage)); + if (FAIL(failures.recvfrom)) { + failures.recvfrom = NEXT(failures.recvfrom); + errno = failures.recvfrom_errno; + return (stats.recv.last_return = failures.recvfrom_ret); + } + failures.recvfrom = NEXT(failures.recvfrom); + ssize_t ret = -1; + ret = __real_recvfrom(sockfd, buf, len, flags, src_addr, addrlen); + if (ret >= 0 && src_addr != NULL) { + // Same justification as in accept + stats.recvfrom.last_returned_addr.addrlen = *addrlen; + memcpy(&(stats.recvfrom.last_returned_addr.src_addr), src_addr, MIN(old_addrlen, *addrlen)); + } + return (stats.recvfrom.last_return = ret); +} + +ssize_t __wrap_recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + if (!(wrap_monitoring && monitored.recvmsg)) { + return __real_recvmsg(sockfd, msg, flags); + } + stats.recvmsg.called++; + stats.recv_all.called++; + stats.recvmsg.last_params = (struct params_recvmsg_t) { + .sockfd = sockfd, + .msg = msg, + .flags = flags + }; + // Reinit struct + memset(&(stats.recvmsg.last_returned_msg), 0, sizeof(struct msghdr)); + if (FAIL(failures.recvmsg)) { + failures.recvmsg = NEXT(failures.recvmsg); + errno = failures.recvmsg_errno; + return (stats.recvmsg.last_return = failures.recvmsg_ret); + } + failures.recvmsg = NEXT(failures.recvmsg); + ssize_t ret = -1; + ret = __real_recvmsg(sockfd, msg, flags); + if (ret == 0) { + // Assume that msg doesn't point to an invalid location + memcpy(&(stats.recvmsg.last_returned_msg), msg, sizeof(struct msghdr)); + } + return ret; +} + +ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags) +{ + if (!(wrap_monitoring && monitored.send)) { + return __real_send(sockfd, buf, len, flags); + } + stats.send.called++; + stats.send_all.called++; + stats.send.last_params = (struct params_send_t) { + .sockfd = sockfd, + .buf = buf, + .len = len, + .flags = flags + }; + if (FAIL(failures.send)) { + failures.send = NEXT(failures.send); + errno = failures.send_errno; + return (stats.send.last_return = failures.send_ret); + } + failures.send = NEXT(failures.send); + ssize_t ret = -1; + ret = __real_send(sockfd, buf, len, flags); + return (stats.send.last_return = ret); +} + +ssize_t __wrap_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) +{ + if (!(wrap_monitoring && monitored.sendto)) { + return __real_sendto(sockfd, buf, len, flags, dest_addr, addrlen); + } + stats.sendto.called++; + stats.send_all.called++; + stats.sendto.last_params = (struct params_sendto_t) { + .sockfd = sockfd, + .buf = buf, + .len = len, + .flags = flags, + .dest_addr_ptr = dest_addr, + //.dest_addr = (struct sockaddr_storage)0, + .addrlen = addrlen + }; + /*if (dest_addr != NULL) { + memcpy(&(stats.sendto.last_params.dest_addr), dest_addr, MIN(addrlen, sizeof(struct sockaddr_storage))); // May segfault if dest_addr is badly defined. + }*/ + if (FAIL(failures.sendto)) { + failures.sendto = NEXT(failures.sendto); + errno = failures.sendto_errno; + return (stats.sendto.last_return = failures.sendto_ret); + } + failures.sendto = NEXT(failures.sendto); + ssize_t ret = -1; + ret = __real_sendto(sockfd, buf, len, flags, dest_addr, addrlen); + return (stats.sendto.last_return = ret); +} + +ssize_t __wrap_sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + if (!(wrap_monitoring && monitored.sendmsg)) { + return __real_sendmsg(sockfd, msg, flags); + } + stats.sendmsg.called++; + stats.send_all.called++; + stats.sendmsg.last_params = (struct params_sendmsg_t) { + .sockfd = sockfd, + .msg_ptr = msg, + //.msg = (struct msghdr)0, + .flags = flags + }; + /*if (msg != NULL) { + memcpy(&(stats.sendmsg.last_params.msg), msg, sizeof(struct msghdr)); + }*/ + if (FAIL(failures.sendmsg)) { + failures.sendmsg = NEXT(failures.sendmsg); + errno = failures.sendmsg_errno; + return (stats.sendmsg.last_return = failures.sendmsg_ret); + } + failures.sendmsg = NEXT(failures.sendmsg); + ssize_t ret = -1; + ret = __real_sendmsg(sockfd, msg, flags); + return ret; +} + +int __wrap_socket(int domain, int type, int protocol) +{ + if (!(wrap_monitoring && monitored.socket)) { + return __real_socket(domain, type, protocol); + } + stats.socket.called++; + stats.socket.last_params = (struct params_socket_t) { + .domain = domain, + .type = type, + .protocol = protocol + }; + if (FAIL(failures.socket)) { + failures.socket = NEXT(failures.socket); + errno = failures.socket_errno; + return (stats.socket.last_return = failures.socket_ret); + } + failures.socket = NEXT(failures.socket); + int ret = -2; + ret = __real_socket(domain, type, protocol); + return (stats.socket.last_return = ret); +} + +void reinit_network_socket_stats() +{ + memset(&(stats.accept), 0, sizeof(struct stats_accept_t)); + memset(&(stats.bind), 0, sizeof(struct stats_bind_t)); + memset(&(stats.connect), 0, sizeof(struct stats_connect_t)); + memset(&(stats.listen), 0, sizeof(struct stats_listen_t)); + memset(&(stats.recv), 0, sizeof(struct stats_recv_t)); + memset(&(stats.recvfrom), 0, sizeof(struct stats_recvfrom_t)); + memset(&(stats.recvmsg), 0, sizeof(struct stats_recvmsg_t)); + memset(&(stats.recv_all), 0, sizeof(struct stats_recv_all_t)); + memset(&(stats.send), 0, sizeof(struct stats_send_t)); + memset(&(stats.sendto), 0, sizeof(struct stats_sendto_t)); + memset(&(stats.sendmsg), 0, sizeof(struct stats_sendmsg_t)); + memset(&(stats.send_all), 0, sizeof(struct stats_send_all_t)); + memset(&(stats.socket), 0, sizeof(struct stats_socket_t)); +} + diff --git a/student/CTester/wrap_network_socket.h b/student/CTester/wrap_network_socket.h new file mode 100644 index 0000000..5c8eeca --- /dev/null +++ b/student/CTester/wrap_network_socket.h @@ -0,0 +1,216 @@ +/* + * Wrapper for accept, bind, connect, listen, poll, select, + * recv, recvfrom, recvmsg, send, sendto, sendmsg, socket. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __WRAP_NETWORK_SOCKET_H_ +#define __WRAP_NETWORK_SOCKET_H_ + +/** + * Structures for accept. + * In addition to the common stats structures, + * there is an additional structure called return_accept_t, + * which will retain the returned contained, in the case that the student + * frees the allocated memory before we have the possibility to check it. + */ +struct params_accept_t { + int sockfd; + struct sockaddr *addr; + socklen_t *addrlen_ptr; +}; + +/** + * Structure to remember the returned value. + * ret: return value of the call + * addr: sockaddr filled in *addr in the call. Set to 0 if failure. + * addrlen: size of the sockaddr filled in *addr, or zero if failure. + */ +struct return_accept_t { + struct sockaddr_storage addr; // We use a sockaddr_storage as it is guaranteed to always be big enough to contain sockaddresses. + socklen_t addrlen; +}; + +struct stats_accept_t { + int called; + struct params_accept_t last_params; + int last_return; + struct return_accept_t last_returns; +}; + +/** + * Structures for bind + */ +struct params_bind_t { + int sockfd; + const struct sockaddr *addr; + socklen_t addrlen; +}; + +struct stats_bind_t { + int called; + struct params_bind_t last_params; + int last_return; +}; + + +/** + * Structures for connect + */ +struct params_connect_t { + int sockfd; + const struct sockaddr *addr; + socklen_t addrlen; +}; + +struct stats_connect_t { + int called; + struct params_connect_t last_params; + int last_return; +}; + + +/** + * Structures for listen + */ +struct params_listen_t { + int sockfd; + int backlog; +}; + +struct stats_listen_t { + int called; + struct params_listen_t last_params; + int last_return; +}; + + +/** + * Structures for recv, recvfrom, recvmsg. + */ +struct params_recv_t { + int sockfd; + void *buf; + size_t len; + int flags; +}; +struct params_recvfrom_t { + int sockfd; + void *buf; + size_t len; + int flags; + struct sockaddr *src_addr; + socklen_t *addrlen_ptr; +}; + +struct params_recvmsg_t { + int sockfd; + struct msghdr *msg; + int flags; +}; +struct return_recvfrom_t { + struct sockaddr_storage src_addr; + socklen_t addrlen; +}; + +// TODO see if we can merge last_returns.ret and last_return as only one variable within the structure, and with only this simplified access. +struct stats_recv_t { + int called; + struct params_recv_t last_params; + ssize_t last_return; +}; +struct stats_recvfrom_t { + int called; + struct params_recvfrom_t last_params; + ssize_t last_return; + struct return_recvfrom_t last_returned_addr; +}; +struct stats_recvmsg_t { + int called; + struct params_recvmsg_t last_params; + ssize_t last_return; + struct msghdr last_returned_msg; +}; +struct stats_recv_all_t { + int called; // Should be incremented each time we increment another one +}; + + +/** + * Structures for send, sendto and sendmsg. + */ +struct params_send_t { + int sockfd; + const void *buf; + size_t len; + int flags; +}; +struct params_sendto_t { + int sockfd; + const void *buf; + size_t len; + int flags; + const struct sockaddr *dest_addr_ptr; + //struct sockaddr_storage dest_addr; + socklen_t addrlen; +}; +struct params_sendmsg_t { + int sockfd; + const struct msghdr *msg_ptr; + //struct msghdr msg; + int flags; +}; + +struct stats_send_t { + int called; + struct params_send_t last_params; + ssize_t last_return; +}; +struct stats_sendto_t { + int called; + struct params_sendto_t last_params; + ssize_t last_return; +}; +struct stats_sendmsg_t { + int called; + struct params_sendmsg_t last_params; + ssize_t last_return; +}; +struct stats_send_all_t { + int called; // Should be incremented each time we increment another one. +}; + + +/** + * Structures for socket + */ +struct params_socket_t { + int domain; + int type; + int protocol; +}; + +struct stats_socket_t { + int called; // Number of times the function has been called + struct params_socket_t last_params; // Arguments of the last call to socket(2) + int last_return; // Return value of the last call to socket(2) +}; + + +/** + * Utility functions + */ +void reinit_network_socket_stats(); + + +#endif // __WRAP_NETWORK_SOCKET_H__ diff --git a/student/Makefile b/student/Makefile index 885a683..cabc46d 100644 --- a/student/Makefile +++ b/student/Makefile @@ -14,6 +14,7 @@ WRAP += -Wl,-wrap=getpid WRAP += -Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=realloc -Wl,-wrap=calloc WRAP += -Wl,-wrap=pthread_mutex_lock -Wl,-wrap=pthread_mutex_unlock -Wl,-wrap=pthread_mutex_trylock -Wl,-wrap=pthread_mutex_init -Wl,-wrap=pthread_mutex_destroy WRAP += -Wl,-wrap=getaddrinfo -Wl,-wrap=getnameinfo -Wl,-wrap=freeaddrinfo -Wl,-wrap=gai_strerror +WRAP += -Wl,-wrap=accept -Wl,-wrap=bind -Wl,-wrap=connect -Wl,-wrap=listen -Wl,-wrap=recv -Wl,-wrap=recvfrom -Wl,-wrap=recvmsg -Wl,-wrap=send -Wl,-wrap=sendto -Wl,-wrap=sendmsg -Wl,-wrap=socket WRAP += -Wl,-wrap=sleep all: $(EXEC) From 13d65e91c2bbae6d0c4d49e081f3a81ae49f571e Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 5 Sep 2018 17:28:26 +0200 Subject: [PATCH 16/53] Make CTester compile on CentOS --- student/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/student/Makefile b/student/Makefile index cabc46d..3ade83d 100644 --- a/student/Makefile +++ b/student/Makefile @@ -3,7 +3,8 @@ EXEC=tests LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic SRC=$(sort $(wildcard *.c)) $(sort $(wildcard CTester/*.c)) OBJ=$(SRC:.c=.o) -CFLAGS = -Wall -Werror -Wextra -Wshadow -DC99 -std=gnu99 -ICTester -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE +CFLAGS = -Wall -Werror -Wextra -Wshadow -DC99 -std=gnu99 -ICTester +CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE DEBUGFLAGS = -g -Wno-unused-variable -Wno-unused-parameter OTHERFLAGS = -fstack-protector-all -D_FORTIFY_SOURCE=2 -fno-omit-frame-pointer #CFLAGS += $(DEBUGFLAGS) From 98c3073780e11679fa52d3fcc422de41dc7d2157 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Fri, 7 Sep 2018 01:50:30 +0200 Subject: [PATCH 17/53] Add select, poll and byte-order functions --- student/CTester/wrap.h | 35 +++++++++++ student/CTester/wrap_file.h | 1 + student/CTester/wrap_network_inet.c | 70 ++++++++++++++++++++++ student/CTester/wrap_network_inet.h | 59 ++++++++++++++++++ student/CTester/wrap_network_socket.c | 86 +++++++++++++++++++++++++++ student/CTester/wrap_network_socket.h | 56 +++++++++++++++++ student/Makefile | 8 ++- 7 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 student/CTester/wrap_network_inet.c create mode 100644 student/CTester/wrap_network_inet.h diff --git a/student/CTester/wrap.h b/student/CTester/wrap.h index b4ee72d..811a19c 100644 --- a/student/CTester/wrap.h +++ b/student/CTester/wrap.h @@ -14,6 +14,7 @@ #include "wrap_mutex.h" #include "wrap_network_dns.h" #include "wrap_network_socket.h" +#include "wrap_network_inet.h" #include "wrap_sleep.h" // Basic structures for system call wrapper @@ -52,13 +53,21 @@ struct wrap_monitor_t { bool bind; bool connect; bool listen; + bool poll; bool recv; bool recvfrom; bool recvmsg; + bool select; bool send; bool sendto; bool sendmsg; + bool shutdown; bool socket; + + bool htons; + bool ntohs; + bool htonl; + bool ntohl; }; #define MONITOR_ALL_RECV(m, v) do { \ @@ -69,6 +78,10 @@ struct wrap_monitor_t { m.send = m.sendto = m.sendmsg = v; \ } while (0); +#define MONITOR_ALL_BYTEORDER(m, v) do { \ + m.ntohs = m.htons = m.ntohl = m.htonl = v; \ +} while (0); + #define MAX_LOG 1000 // log for specific system calls @@ -195,6 +208,10 @@ struct wrap_fail_t { int listen_ret; int listen_errno; + uint32_t poll; + int poll_ret; + int poll_errno; + uint32_t recv; int recv_ret; int recv_errno; @@ -207,6 +224,10 @@ struct wrap_fail_t { int recvmsg_ret; int recvmsg_errno; + uint32_t select; + int select_ret; + int select_errno; + uint32_t send; int send_ret; int send_errno; @@ -219,9 +240,15 @@ struct wrap_fail_t { int sendmsg_ret; int sendmsg_errno; + uint32_t shutdown; + int shutdown_ret; + int shutdown_errno; + uint32_t socket; int socket_ret; int socket_errno; + + // byte-order functions (htonl...) cannot fail } ; @@ -256,15 +283,23 @@ struct wrap_stats_t { struct stats_bind_t bind; struct stats_connect_t connect; struct stats_listen_t listen; + struct stats_poll_t poll; struct stats_recv_t recv; struct stats_recvfrom_t recvfrom; struct stats_recvmsg_t recvmsg; struct stats_recv_all_t recv_all; + struct stats_select_t select; struct stats_send_t send; struct stats_sendto_t sendto; struct stats_sendmsg_t sendmsg; struct stats_send_all_t send_all; + struct stats_shutdown_t shutdown; struct stats_socket_t socket; + + struct stats_htons_t htons; + struct stats_ntohs_t ntohs; + struct stats_htonl_t htonl; + struct stats_ntohl_t ntohl; }; #endif // __WRAP_H_ diff --git a/student/CTester/wrap_file.h b/student/CTester/wrap_file.h index 64b6887..99b5a52 100644 --- a/student/CTester/wrap_file.h +++ b/student/CTester/wrap_file.h @@ -6,6 +6,7 @@ #include #include +// TODO fcntl // basic structure to record the parameters of the last open call diff --git a/student/CTester/wrap_network_inet.c b/student/CTester/wrap_network_inet.c new file mode 100644 index 0000000..f356d2b --- /dev/null +++ b/student/CTester/wrap_network_inet.c @@ -0,0 +1,70 @@ +/* + * Wrapper for htons, ntohs, htonl, ntohl, and other future functions. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "wrap.h" + +uint16_t __real_htons(uint16_t hostshort); +uint16_t __real_ntohs(uint16_t netshort); +uint32_t __real_htonl(uint32_t hostlong); +uint32_t __real_ntohl(uint32_t netlong); + +extern bool wrap_monitoring; +extern struct wrap_stats_t stats; +extern struct wrap_monitor_t monitored; + +uint16_t __wrap_htons(uint16_t hostshort) +{ + uint16_t ret = __real_htons(hostshort); + if (wrap_monitoring && monitored.htons) { + stats.htons.called++; + stats.htons.last_params.hostshort = hostshort; + stats.htons.last_return = ret; + } + return ret; +} + +uint16_t __wrap_ntohs(uint16_t netshort) +{ + uint16_t ret = __real_ntohs(netshort); + if (wrap_monitoring && monitored.ntohs) { + stats.ntohs.called++; + stats.ntohs.last_params.netshort = netshort; + stats.ntohs.last_return = ret; + } + return ret; +} + +uint32_t __wrap_htonl(uint32_t hostlong) +{ + uint32_t ret = __real_htonl(hostlong); + if (wrap_monitoring && monitored.htonl) { + stats.htonl.called++; + stats.htonl.last_params.hostlong = hostlong; + stats.htonl.last_return = ret; + } + return ret; +} + +uint32_t __wrap_ntohl(uint32_t netlong) +{ + uint32_t ret = __real_ntohl(netlong); + if (wrap_monitoring && monitored.ntohl) { + stats.ntohl.called++; + stats.ntohl.last_params.netlong = netlong; + stats.ntohl.last_return = ret; + } + return ret; +} + diff --git a/student/CTester/wrap_network_inet.h b/student/CTester/wrap_network_inet.h new file mode 100644 index 0000000..c49c4f1 --- /dev/null +++ b/student/CTester/wrap_network_inet.h @@ -0,0 +1,59 @@ +/* + * Wrapper for htons, ntohs, htonl, ntohl, and other future functions. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __WRAP_NETWORK_INET_H_ +#define __WRAP_NETWORK_INET_H_ + +/** + * Structures for htons, ntohs, htonl, ntohl. + */ + +struct params_htons_t { + uint16_t hostshort; +}; +struct params_ntohs_t { + uint16_t netshort; +}; +struct params_htonl_t { + uint32_t hostlong; +}; +struct params_ntohl_t { + uint32_t netlong; +}; + +struct stats_htons_t { + int called; + struct params_htons_t last_params; + uint16_t last_return; +}; +struct stats_ntohs_t { + int called; + struct params_ntohs_t last_params; + uint16_t last_return; +}; +struct stats_htonl_t { + int called; + struct params_htonl_t last_params; + uint32_t last_return; +}; +struct stats_ntohl_t { + int called; + struct params_ntohl_t last_params; + uint32_t last_return; +}; + + +#endif // __WRAP_NETWORK_INET_H_ + diff --git a/student/CTester/wrap_network_socket.c b/student/CTester/wrap_network_socket.c index c076055..b81e8ef 100644 --- a/student/CTester/wrap_network_socket.c +++ b/student/CTester/wrap_network_socket.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "wrap.h" @@ -27,12 +28,15 @@ int __real_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int __real_bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen); int __real_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int __real_listen(int sockfd, int backlog); +int __real_poll(struct pollfd *fds, nfds_t nfds, int timeout); ssize_t __real_recv(int sockfd, void *buf, size_t len, int flags); ssize_t __real_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t __real_recvmsg(int sockfd, struct msghdr *msg, int flags); +int __real_select(int nfds, fd_set *readfds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout); ssize_t __real_send(int sockfd, const void *buf, size_t len, int flags); ssize_t __real_sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t __real_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); +int __real_shutdown(int sockfd, int how); int __real_socket(int domain, int type, int protocol); @@ -157,6 +161,36 @@ int __wrap_listen(int sockfd, int backlog) return (stats.listen.last_return = ret); } +int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + if (!(wrap_monitoring && monitored.poll)) { + return __real_poll(fds, nfds, timeout); + } + stats.poll.called++; + stats.poll.last_params = (struct params_poll_t) { + .fds_ptr = fds, + .nfds = nfds, + .timeout = timeout, + .fds_copy = NULL + }; + if (FAIL(failures.poll)) { + failures.poll = NEXT(failures.poll); + errno = failures.poll_errno; + return (stats.poll.last_return = failures.poll_ret); + } + failures.poll = NEXT(failures.poll); + int ret = -2; + ret = __real_poll(fds, nfds, timeout); + if (! (ret == -1 && errno == EFAULT)) { + struct pollfd *tmp = malloc(nfds * sizeof(struct pollfd)); + if (tmp) { + memcpy(fds, tmp, nfds * sizeof(struct pollfd)); + stats.poll.last_params.fds_copy = tmp; + } + } + return ret; +} + ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) { if (!(wrap_monitoring && monitored.recv)) { @@ -244,6 +278,34 @@ ssize_t __wrap_recvmsg(int sockfd, struct msghdr *msg, int flags) return ret; } +int __wrap_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +{ + if (!(wrap_monitoring && monitored.select)) { + return __real_select(nfds, readfds, writefds, exceptfds, timeout); + } + stats.select.called++; + stats.select.last_params = (struct params_select_t) { + .nfds = nfds, + .readfds_ptr = readfds, + .writefds_ptr = writefds, + .exceptfds_ptr = exceptfds, + .timeout_ptr = timeout, + .readfds = *readfds, // FIXME may cause a segfault if the student passed garbage + .writefds = *writefds, + .exceptfds = *exceptfds, + .timeout = *timeout + }; + if (FAIL(failures.select)) { + failures.select = NEXT(failures.select); + errno = failures.select_errno; + return (stats.select.last_return = failures.select_ret); + } + failures.select = NEXT(failures.select); + int ret = -1; + ret = __real_select(nfds, readfds, writefds, exceptfds, timeout); + return ret; +} + ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags) { if (!(wrap_monitoring && monitored.send)) { @@ -325,6 +387,27 @@ ssize_t __wrap_sendmsg(int sockfd, const struct msghdr *msg, int flags) return ret; } +int __wrap_shutdown(int sockfd, int how) +{ + if (!(wrap_monitoring && monitored.shutdown)) { + return __real_shutdown(sockfd, how); + } + stats.shutdown.called++; + stats.shutdown.last_params = (struct params_shutdown_t) { + .sockfd = sockfd, + .how = how + }; + if (FAIL(failures.shutdown)) { + failures.shutdown = NEXT(failures.shutdown); + errno = failures.shutdown_errno; + return (stats.shutdown.last_return = failures.shutdown_ret); + } + failures.shutdown = NEXT(failures.shutdown); + int ret = -1; + ret = __real_shutdown(sockfd, how); + return ret; +} + int __wrap_socket(int domain, int type, int protocol) { if (!(wrap_monitoring && monitored.socket)) { @@ -353,14 +436,17 @@ void reinit_network_socket_stats() memset(&(stats.bind), 0, sizeof(struct stats_bind_t)); memset(&(stats.connect), 0, sizeof(struct stats_connect_t)); memset(&(stats.listen), 0, sizeof(struct stats_listen_t)); + memset(&(stats.poll), 0, sizeof(stats.poll)); memset(&(stats.recv), 0, sizeof(struct stats_recv_t)); memset(&(stats.recvfrom), 0, sizeof(struct stats_recvfrom_t)); memset(&(stats.recvmsg), 0, sizeof(struct stats_recvmsg_t)); memset(&(stats.recv_all), 0, sizeof(struct stats_recv_all_t)); + memset(&(stats.select), 0, sizeof(stats.select)); memset(&(stats.send), 0, sizeof(struct stats_send_t)); memset(&(stats.sendto), 0, sizeof(struct stats_sendto_t)); memset(&(stats.sendmsg), 0, sizeof(struct stats_sendmsg_t)); memset(&(stats.send_all), 0, sizeof(struct stats_send_all_t)); + memset(&(stats.shutdown), 0, sizeof(stats.shutdown)); memset(&(stats.socket), 0, sizeof(struct stats_socket_t)); } diff --git a/student/CTester/wrap_network_socket.h b/student/CTester/wrap_network_socket.h index 5c8eeca..13b0b1a 100644 --- a/student/CTester/wrap_network_socket.h +++ b/student/CTester/wrap_network_socket.h @@ -95,6 +95,23 @@ struct stats_listen_t { }; +/** + * Structures for poll + */ +struct params_poll_t { + struct pollfd *fds_ptr; + unsigned long int nfds; // Strictly speaking, this is a nfds_t, but is is at least an unsigned long int. + int timeout; + struct pollfd *fds_copy; +}; + +struct stats_poll_t { + int called; + struct params_poll_t last_params; + int last_return; +}; + + /** * Structures for recv, recvfrom, recvmsg. */ @@ -146,6 +163,29 @@ struct stats_recv_all_t { }; +/** + * Structures for select. + */ + +struct params_select_t { + int nfds; + fd_set *readfds_ptr; + fd_set *writefds_ptr; + fd_set *exceptfds_ptr; + struct timeval *timeout_ptr; + fd_set readfds; + fd_set writefds; + fd_set exceptfds; + struct timeval timeout; +}; + +struct stats_select_t { + int called; + struct params_select_t last_params; + int last_return; +}; + + /** * Structures for send, sendto and sendmsg. */ @@ -191,6 +231,22 @@ struct stats_send_all_t { }; +/** + * Structures for shutdown. + */ + +struct params_shutdown_t { + int sockfd; + int how; +}; + +struct stats_shutdown_t { + int called; + struct params_shutdown_t last_params; + int last_return; +}; + + /** * Structures for socket */ diff --git a/student/Makefile b/student/Makefile index 3ade83d..8d9d8a2 100644 --- a/student/Makefile +++ b/student/Makefile @@ -3,8 +3,9 @@ EXEC=tests LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic SRC=$(sort $(wildcard *.c)) $(sort $(wildcard CTester/*.c)) OBJ=$(SRC:.c=.o) -CFLAGS = -Wall -Werror -Wextra -Wshadow -DC99 -std=gnu99 -ICTester -CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE +CFLAGS = -Wall -Werror -Wextra -Wshadow +CFLAGS += -std=gnu99 +CFLAGS += -ICTester # to include the folder. DEBUGFLAGS = -g -Wno-unused-variable -Wno-unused-parameter OTHERFLAGS = -fstack-protector-all -D_FORTIFY_SOURCE=2 -fno-omit-frame-pointer #CFLAGS += $(DEBUGFLAGS) @@ -15,7 +16,8 @@ WRAP += -Wl,-wrap=getpid WRAP += -Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=realloc -Wl,-wrap=calloc WRAP += -Wl,-wrap=pthread_mutex_lock -Wl,-wrap=pthread_mutex_unlock -Wl,-wrap=pthread_mutex_trylock -Wl,-wrap=pthread_mutex_init -Wl,-wrap=pthread_mutex_destroy WRAP += -Wl,-wrap=getaddrinfo -Wl,-wrap=getnameinfo -Wl,-wrap=freeaddrinfo -Wl,-wrap=gai_strerror -WRAP += -Wl,-wrap=accept -Wl,-wrap=bind -Wl,-wrap=connect -Wl,-wrap=listen -Wl,-wrap=recv -Wl,-wrap=recvfrom -Wl,-wrap=recvmsg -Wl,-wrap=send -Wl,-wrap=sendto -Wl,-wrap=sendmsg -Wl,-wrap=socket +WRAP += -Wl,-wrap=accept -Wl,-wrap=bind -Wl,-wrap=connect -Wl,-wrap=listen -Wl,-wrap=poll -Wl,-wrap=recv -Wl,-wrap=recvfrom -Wl,-wrap=recvmsg -Wl,-wrap=select -Wl,-wrap=send -Wl,-wrap=sendto -Wl,-wrap=sendmsg -Wl,-wrap=shutdown -Wl,-wrap=socket +WRAP += -Wl,-wrap=htons -Wl,-wrap=ntohs -Wl,-wrap=htonl -Wl,-wrap=ntohl WRAP += -Wl,-wrap=sleep all: $(EXEC) From 3ec71e59535e307e4712a6b91137bff04a705c21 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Mon, 10 Sep 2018 14:59:08 +0200 Subject: [PATCH 18/53] Add delayed chunks to recv Not well-tested, however --- student/CTester/wrap_network_socket.c | 304 +++++++++++++++++++++++++- student/CTester/wrap_network_socket.h | 37 +++- student/Makefile | 4 +- 3 files changed, 342 insertions(+), 3 deletions(-) diff --git a/student/CTester/wrap_network_socket.c b/student/CTester/wrap_network_socket.c index b81e8ef..1c4ff14 100644 --- a/student/CTester/wrap_network_socket.c +++ b/student/CTester/wrap_network_socket.c @@ -22,6 +22,7 @@ #include "wrap.h" #define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define BILLION (1000*1000*1000) int __real_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); @@ -46,6 +47,268 @@ extern struct wrap_monitor_t monitored; extern struct wrap_fail_t failures; +/** + * Auxiliary structures and functions + */ + +void getnanotime(struct timespec *res) +{ + clock_gettime(CLOCK_REALTIME, res); +} + +int64_t get_time_interval(const struct timespec *pasttime, const struct timespec *curtime) +{ + int64_t past = pasttime->tv_sec; + past *= 1000*1000*1000; + past += pasttime->tv_nsec; + int64_t cur = curtime->tv_sec; + cur *= 1000*1000*1000; + cur += curtime->tv_nsec; + int64_t interval = cur - past; + if (interval < 0) { + fprintf(stderr, "BUG: negative time interval"); + } + return interval; +} + + +/** + * recv-related + */ + +// #define RECV_MODE_BUFCHUK 1 // not used + +/** + * Precision for the 'interval' field. + * It contains the wait time for the current chunk, as measured from the previous call. + * It may be positive: in this case, it means the next data was not available + * at the end of the previous call, and it is the time the current call has + * to wait before accessing data. Minus the interval between the two calls. + * It may be negative: in this case, it represents the opposite of the amout + * of time the current chunk has been available. + * Thanks to this field, we can enable the chunks at more or less the right moment. + */ +struct recv_item { + int fd; // the file descriptor this structure applies to + //int mode; // the type of data provider used; not used + const struct recv_buffer_t *buf; // Provided recv_buffer_t structure + unsigned int chunk_id; // Current chunk, or next chunk to be received + size_t bytes_read; // Number of bytes read inside the current chunk + struct timespec last_time; // Time of the end of the last call of recv on this socket + int64_t interval; // In real-time mode (RECV_REAL_INTERVAL), real wait interval for the current chunk. +}; + +struct recv_fd_table_t { + size_t n; + struct recv_item *items; +} recv_fd_table; + +struct recv_item *recv_get_entry(int fd) +{ + for (unsigned int i = 0; i < recv_fd_table.n; i++) { + if (recv_fd_table.items[i].fd == fd) + return &(recv_fd_table.items[i]); + } + return NULL; +} + +bool fd_is_recv_buffered(int fd) +{ + return (recv_get_entry(fd) != NULL); +} + +void reinit_recv_fd_table_item(int i) +{ + // We need to clean up _recv_fd_table.items[i] + recv_fd_table.items[i].buf = NULL; // We're not responsible to free it. + recv_fd_table.items[i].chunk_id = 0; + recv_fd_table.items[i].interval = 0; + recv_fd_table.items[i].last_time = (struct timespec) { + .tv_sec = 0, + .tv_nsec = 0 + }; +} + +void reinit_recv_fd_table() +{ + // As we're not responsible to clean up all the recv_buffer_t, we can just free everything up + free(recv_fd_table.items); + recv_fd_table.n = 0; +} + +int recv_remove_entry(int fd) +{ + bool found = false; + for (unsigned int i = 0; i < recv_fd_table.n; i++) { + if (recv_fd_table.items[i].fd == fd) { + found = true; + if (i < recv_fd_table.n - 1) { + struct recv_item *dest = &(recv_fd_table.items[i]); + memmove(dest, dest + 1, recv_fd_table.n - i - 1); + memset(&(recv_fd_table.items[recv_fd_table.n-1]), 0, sizeof(struct recv_item)); + // This is not really a problem that we have a useless thing at the end + } + recv_fd_table.n--; + } + } + return (found ? 1 : 0); +} + +/** + * Returns a pointer to an entry in the table, + * where we can safely store our informations. + * May be a pre-existing chunk, + * in which case the fd's will match. + */ +struct recv_item *recv_get_new_entry(int fd) +{ + unsigned int i = 0; + for (i = 0; i < recv_fd_table.n; i++) { + if (recv_fd_table.items[i].fd == fd) { + // We should clean it before changing it + reinit_recv_fd_table_item(i); + break; + } + } + if (i >= recv_fd_table.n) { + // realloc + struct recv_item *tmp = realloc(recv_fd_table.items, (recv_fd_table.n + 1) * sizeof(struct recv_item)); + if (tmp == NULL) + return NULL; + recv_fd_table.items = tmp; + recv_fd_table.n++; + } + // Now, we can insert at index i + return &(recv_fd_table.items[i]); +} + +size_t recv_handle_buffer_no_rt(struct recv_item *cur, void *buf, size_t len, int flags, int64_t call_interval) +{ + const struct recv_bufchunk_t *curchunk = &(cur->buf->chunks[cur->chunk_id]); + if (cur->bytes_read == 0) { + // We may have to wait + int64_t sleeptime = 0; + if (cur->buf->mode == RECV_BEFORE_INTERVAL) { + sleeptime = curchunk->interval; + } else { + sleeptime = curchunk->interval - call_interval; + } + if (sleeptime > 0) { + if ((flags & MSG_DONTWAIT) != 0) { + errno = EAGAIN; + return -1; + } + struct timespec tmp = (struct timespec) { + .tv_sec = sleeptime / BILLION, + .tv_nsec = sleeptime % BILLION + }; + nanosleep(&tmp, NULL); + getnanotime(&(cur->last_time)); // We need to update + } + } + size_t bytes_left = curchunk->buflen - cur->bytes_read; + size_t transfered_bytes = MIN(len, bytes_left); + memmove(buf, (curchunk->buf + cur->bytes_read), transfered_bytes); + cur->bytes_read += transfered_bytes; + if (cur->bytes_read >= curchunk->buflen) { + cur->chunk_id++; + cur->bytes_read = 0; + } + return transfered_bytes; +} + +size_t recv_handle_buffer_rt(struct recv_item *cur, void *buf, size_t len, int flags, int64_t call_interval) +{ + /* + * Assume the following: + * - cur->interval was the time to wait for the current chunk to become active, at the end of the previous call. + * - cur->bytes_read is 0 only if we have to wait. + */ + cur->interval -= call_interval; + /* + * Assume the following: + * - cur->interval is the remaining time to wait for the current chunk to become active. + * - cur->bytes_read is 0 only if we have to wait. + */ + if (cur->interval > 0) { + if ((flags & MSG_DONTWAIT) != 0) { + // The call would block but the caller requested it shouldn't block + errno = EAGAIN; + return -1; + } + int64_t sleeptime = cur->interval; + struct timespec tmp = (struct timespec) { + .tv_sec = sleeptime / BILLION, + .tv_nsec = sleeptime % BILLION + }; + nanosleep(&tmp, NULL); + cur->interval = 0; + getnanotime(&(cur->last_time)); + } + /* + * Assume the following: + * - cur->interval is the negative of the time the current chunk has been available. + */ + size_t total_bytes = 0; + for (unsigned int i = cur->chunk_id; i < cur->buf->nchunks; i++) { + if (len <= 0) { + break; + } + const struct recv_bufchunk_t *curchunk = &(cur->buf->chunks[i]); + size_t bytes_left = curchunk->buflen - cur->bytes_read; + size_t transfered_bytes = MIN(len, bytes_left); + memmove((buf + total_bytes), (curchunk->buf + cur->bytes_read), transfered_bytes); + cur->bytes_read += transfered_bytes; + len -= transfered_bytes; + total_bytes += transfered_bytes; + if (cur->bytes_read >= curchunk->buflen) { + // Emptied chunk + cur->chunk_id++; + cur->bytes_read = 0; + if (cur->chunk_id < cur->buf->nchunks) { + // Update interval + cur->interval += cur->buf->chunks[cur->chunk_id].interval; + if (cur->interval > 0) { + break; // Not available yet + } + } + } + } + return total_bytes; +} + +ssize_t recv_handle_buffer(int sockfd, void *buf, size_t len, int flags) +{ + struct recv_item *cur = recv_get_entry(sockfd); + if (cur == NULL) { + errno = EINTR; // This is about the only case where this can happen + return -1; + } + struct timespec curtime; + getnanotime(&curtime); + int64_t call_interval = get_time_interval(&(cur->last_time), &curtime); + cur->last_time = curtime; + if (cur->chunk_id >= cur->buf->nchunks) { + // Nothing left to read; we can return immediately 0 + // TODO remove this as a particular case + return 0; + } + size_t bytes_transfered = 0; + if (cur->buf->mode == RECV_BEFORE_INTERVAL || cur->buf->mode == RECV_AFTER_INTERVAL) { + bytes_transfered = recv_handle_buffer_no_rt(cur, buf, len, flags, call_interval); + } else if (cur->buf->mode == RECV_REAL_INTERVAL) { + bytes_transfered = recv_handle_buffer_rt(cur, buf, len, flags, call_interval); + } else { + return -1; + } + return bytes_transfered; +} + +/** + * Wrap functions. + */ + + /* * Note: addr should point to an existing sockaddr, or NULL if we don't want it * and addrlen should point to an existing socklen_t containing the size @@ -211,7 +474,11 @@ ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) } failures.recv = NEXT(failures.recv); ssize_t ret = -1; - ret = __real_recv(sockfd, buf, len, flags); + if (fd_is_recv_buffered(sockfd)) { + ret = recv_handle_buffer(sockfd, buf, len, flags); + } else { + ret = __real_recv(sockfd, buf, len, flags); + } return (stats.recv.last_return = ret); } @@ -430,6 +697,8 @@ int __wrap_socket(int domain, int type, int protocol) return (stats.socket.last_return = ret); } +// Additionnal functions + void reinit_network_socket_stats() { memset(&(stats.accept), 0, sizeof(struct stats_accept_t)); @@ -448,5 +717,38 @@ void reinit_network_socket_stats() memset(&(stats.send_all), 0, sizeof(struct stats_send_all_t)); memset(&(stats.shutdown), 0, sizeof(stats.shutdown)); memset(&(stats.socket), 0, sizeof(struct stats_socket_t)); + reinit_recv_fd_table(); +} + +int set_recv_data(int fd, const struct recv_buffer_t *buf) +{ + if (buf == NULL) { + return recv_remove_entry(fd); + } + if (buf->mode != RECV_REAL_INTERVAL && + buf->mode != RECV_AFTER_INTERVAL && + buf->mode != RECV_BEFORE_INTERVAL) { + return -2; + } + bool already_there = false; + if (fd_is_recv_buffered(fd)) { + already_there = true; + } + struct recv_item *tmp = recv_get_new_entry(fd); + if (tmp == NULL) { + return -1; + } + tmp->fd = fd; + tmp->buf = buf; + tmp->chunk_id = 0; + tmp->bytes_read = 0; + getnanotime(&(tmp->last_time)); + if (buf->mode == RECV_REAL_INTERVAL && buf->nchunks > 0) { + // The first wait interval should be that of the first chunk. + tmp->interval = buf->chunks[0].interval; + } else { + tmp->interval = 0; + } + return (already_there ? 1 : 0); } diff --git a/student/CTester/wrap_network_socket.h b/student/CTester/wrap_network_socket.h index 13b0b1a..3b0fdba 100644 --- a/student/CTester/wrap_network_socket.h +++ b/student/CTester/wrap_network_socket.h @@ -264,9 +264,44 @@ struct stats_socket_t { /** - * Utility functions + * Utility functions and structures */ void reinit_network_socket_stats(); +//int set_recv_source(int fd, int sourcefd); + +//int set_recv_buffer(int fd, void *buf, size_t buflen); + +//int set_recv_policy(int fd, struct recv_policy_t *policy); +// Should define at some point the recv_policy_t structure. + +struct recv_bufchunk_t { + uint32_t interval; // Time interval (in microseconds) to wait before the chunk can be received. Should be no more than 1000000. Relative to each other. + const void *buf; // Buffer of data to be read. + size_t buflen; // Length of this buffer +}; + +#define RECV_REAL_INTERVAL 1 // Real-time interval: if the student waits a lot, he won't see the interval +#define RECV_AFTER_INTERVAL 2 // At least interval µs after emptying the chunk +#define RECV_BEFORE_INTERVAL 3 // At least interval µs before reading a new chunk + +/** + * Important: + * There are two ways (mode) to interpret the interval value: + * - the first possibility (RECV_REAL_INTERVAL) tracks the time interval in real time, which means that the student may not see any actual time interval if it waits long enough. + * - the second possibility (RECV_AFTER_INTERVAL) only enforce the time interval between the end of a call that emptied a chunk and the actual read of the following call, which will start a new chunk. If the student waits enough, it may not see it. + * - the third possibility (RECV_BEFORE_INTERVAL) imposes an actual wait each time the student calls recv and the chunk has not been read before (if it has, then it won't wait, but it will read only one chunk). The interval is enforced between the start of this call and the actual read of this same call. The student cannot ignore it. + * Be careful to select the correct mode of operation, otherwise it won't work very much. + */ +struct recv_buffer_t { + int mode; // The mode of interpretation of interval + size_t nchunks; // Number of chunks + const struct recv_bufchunk_t *chunks; // Table of chunks +}; + +/** + * Returns -1 if malloc error, -2 if argument error (buf->mode typically), 0 if fd was not present, and 1 if it was already present. + */ +int set_recv_data(int fd, const struct recv_buffer_t *buf); #endif // __WRAP_NETWORK_SOCKET_H__ diff --git a/student/Makefile b/student/Makefile index 8d9d8a2..a59ee2e 100644 --- a/student/Makefile +++ b/student/Makefile @@ -4,8 +4,10 @@ LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic SRC=$(sort $(wildcard *.c)) $(sort $(wildcard CTester/*.c)) OBJ=$(SRC:.c=.o) CFLAGS = -Wall -Werror -Wextra -Wshadow -CFLAGS += -std=gnu99 +CFLAGS += -std=gnu99 # was gnu99 FIXME +#CFLAGS += -DC99 # Don't know why TODO CFLAGS += -ICTester # to include the folder. +#CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE DEBUGFLAGS = -g -Wno-unused-variable -Wno-unused-parameter OTHERFLAGS = -fstack-protector-all -D_FORTIFY_SOURCE=2 -fno-omit-frame-pointer #CFLAGS += $(DEBUGFLAGS) From 1a321cd52a3bc30fbed3e26aaffe6d763726c116 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 12 Sep 2018 23:52:08 +0200 Subject: [PATCH 19/53] Update run file to allow copying CTester from a external folder --- run | 165 ++++++++++++++------------------------------------------- run.py | 135 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 126 deletions(-) create mode 100644 run.py diff --git a/run b/run index 01fb6d2..7f9051d 100644 --- a/run +++ b/run @@ -1,135 +1,48 @@ #!/bin/python3 # Script d'interface entre INGInious et des tests unitaires écrits à l'aide de CUnit -# Auteurs : Mathieu Xhonneux, Anthony Gégo +# Auteurs : Jean-Martin Vlaeminck # Licence : GPLv3 -import subprocess, shlex, re, os, yaml -from inginious import feedback, rst, input - -# Switch working directory to student/ -os.chdir("student") - -# Fetch and save the student code into a file for compilation -input.parse_template("student_code.c.tpl", "student_code.c") - -# Compilation -p = subprocess.Popen(shlex.split("make"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) -make_output = p.communicate()[0].decode('utf-8') -# If compilation failed, exit with "failed" result -if p.returncode: - feedback.set_tag("not_compile", True) - feedback.set_global_result("failed") - feedback.set_global_feedback("La compilation de votre code a échoué. Voici le message de sortie de la commande ``make`` :") - feedback.set_global_feedback(rst.get_codeblock('', make_output), True) - exit(0) -else: - feedback.set_global_result("success") - feedback.set_global_feedback("- Votre code compile.\n") - -# Parse banned functions -try: - banned_funcs = re.findall("BAN_FUNCS\(([a-zA-Z0-9_, ]*)\)", open('tests.c').read())[-1].replace(" ", "").split(",") - banned_funcs = list(filter(None, banned_funcs)) -except IndexError: - banned_funcs = [] - -if banned_funcs: - p = subprocess.Popen(shlex.split("readelf -s student_code.o"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) - readelf_output = p.communicate()[0].decode('utf-8') - for func in banned_funcs: - if re.search("UND {}\n".format(func), readelf_output): - feedback.set_tag("banned_funcs", True) - feedback.set_global_result("failed") - feedback.set_global_feedback("Vous utilisez la fonction {}, qui n'est pas autorisée.".format(func)) - exit(0) - - -# Remove source files -subprocess.run("rm -rf *.c *.tpl *.h *.o", shell=True) - -LANG = input.get_input('@lang') - -# Run the code in a parallel container -p = subprocess.Popen(shlex.split("run_student --time 20 --hard-time 60 ./tests LANGUAGE={}".format(LANG)), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) -p.communicate() - -# If run failed, exit with "failed" result -if p.returncode: - feedback.set_global_result("failed") - if p.returncode == 256-8: - montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur. Le signal SIGFPE a été envoyé : *Floating Point Exception*.") - feedback.set_tag("sigfpe", True) - elif p.returncode == 256-11: - montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur. Le signal SIGSEGV a été envoyé : *Segmentation Fault*.") - elif p.returncode == 252: - montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a tenté d'allouer plus de mémoire que disponible.") - feedback.set_tag("memory", True) - elif p.returncode == 253: - montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a pris trop de temps pour s'exécuter.") - else: - montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur.") - feedback.set_global_feedback(rst.indent_block(2, montest_output, " "), True) - exit(0) -#elif run_output: -# feedback.set_global_feedback("- Sortie de votre méthode de test:\n" + rst.indent_block(2, rst.get_codeblock('', run_output), " "), True) - -# Comment to run the tests -#feedback.set_global_feedback("- **Cette note n'est pas finale.** Une série de tests sera exécutée sur votre code après l'examen.\n", True) -#exit(0) - -# Fetch CUnit test results -results_raw = [r.split('#') for r in open('results.txt').read().splitlines()] -results = [{'pid':r[0], 'code':r[1], 'desc':r[2], 'weight':int(r[3]), 'tags': r[4].split(","), 'info_msgs':r[5:]} for r in results_raw] - - -# Produce feedback -if all([r['code'] == 'SUCCESS' for r in results]): - feedback.set_global_feedback("\n- Votre code a passé tous les tests.", True) -else: - feedback.set_global_feedback("\n- Il y a des erreurs dans votre solution.", True) - -score = 0 -total = 0 -tests_result = {} - -for test in results: - total += test['weight'] - for tag in test['tags']: - if tag != "": - feedback.set_tag(tag, True) - if test['code'] == 'SUCCESS': - score += test['weight'] - feedback.set_problem_feedback("* {desc}\n\n => réussi ({weight}/{weight}) pts)\n\n".format(**test)+(" Info: {}\n\n".format(" — ".join(test['info_msgs'])) if test['info_msgs'] else '\n'), - test['pid'], True) - tests_result[test['pid']] = True if tests_result.get(test['pid'], True) else False - else: - feedback.set_problem_feedback("* {desc}\n\n => échoué (0/{weight}) pts)\n\n".format(**test)+(" Info: {}\n\n".format(" — ".join(test['info_msgs'])) if test['info_msgs'] else '\n'), - test['pid'], True) - tests_result[test['pid']] = False - -for pid, result in tests_result.items(): - if result: - feedback.set_problem_result("success", pid) +import subprocess, os + +# Get a copy of CTester in the student/ directory, as well as the run.py file +# From CTester, we need: +# - the run.py file, at the root of the task +# - the Makefile, in the student/ folder +# - the CTester folder, containing all the wraping code, in the student/ folder too +# We can find it +# - in the current directory + +"""CTester_files = ["/student/Ctester", "/run.py", "/student/Makefile"]""" + +def has_CTester(path="."): + return os.path.isdir(path + "/student/CTester") and os.path.exists(path + "/run.py") and os.path.exists(path + "/student/Makefile") + +def copy_CTester(): + if has_CTester("."): + return 0 # Use it right here + elif has_CTester("/common/CTester"): + # Copy it to here + p = subprocess.Popen(shlex.split("cp -r /common/CTester/* ."), stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (cp_output, cp_errput) = p.communicate() + if p.returncode: + print("Unable to move CTester") + return 1 + return 0 else: - feedback.set_problem_result("failed", pid) + print("Unable to locate CTester") + return 1 -with open("../task.yaml", 'r') as stream: - problems = yaml.load(stream)['problems'] - - for name, meta in problems.items(): - if meta['type'] == 'match': - answer = input.get_input(name) - if answer == meta['answer']: - feedback.set_problem_result("success", name) - feedback.set_problem_feedback("Votre réponse est correcte. (1/1 pts)", name, True) - score += 1 - else: - feedback.set_problem_result("failed", name) - feedback.set_problem_feedback("Votre réponse est incorrecte. (0/1 pts)", name, True) +def run(): + """Call the subprocess""" + p = subprocess.Popen(shlex.split("/bin/python3 run.py")) + return p.returncode - total += 1 +if __name__ == "__main__": + ret = copy_CTester() + if ret: + exit(ret) + ret = run() + exit(ret) -score = 100*score/(total if not total == 0 else 1) -feedback.set_grade(score) -feedback.set_global_result("success" if score >= 50 else "failed") diff --git a/run.py b/run.py new file mode 100644 index 0000000..01fb6d2 --- /dev/null +++ b/run.py @@ -0,0 +1,135 @@ +#!/bin/python3 + +# Script d'interface entre INGInious et des tests unitaires écrits à l'aide de CUnit +# Auteurs : Mathieu Xhonneux, Anthony Gégo +# Licence : GPLv3 + +import subprocess, shlex, re, os, yaml +from inginious import feedback, rst, input + +# Switch working directory to student/ +os.chdir("student") + +# Fetch and save the student code into a file for compilation +input.parse_template("student_code.c.tpl", "student_code.c") + +# Compilation +p = subprocess.Popen(shlex.split("make"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) +make_output = p.communicate()[0].decode('utf-8') +# If compilation failed, exit with "failed" result +if p.returncode: + feedback.set_tag("not_compile", True) + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici le message de sortie de la commande ``make`` :") + feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) +else: + feedback.set_global_result("success") + feedback.set_global_feedback("- Votre code compile.\n") + +# Parse banned functions +try: + banned_funcs = re.findall("BAN_FUNCS\(([a-zA-Z0-9_, ]*)\)", open('tests.c').read())[-1].replace(" ", "").split(",") + banned_funcs = list(filter(None, banned_funcs)) +except IndexError: + banned_funcs = [] + +if banned_funcs: + p = subprocess.Popen(shlex.split("readelf -s student_code.o"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + readelf_output = p.communicate()[0].decode('utf-8') + for func in banned_funcs: + if re.search("UND {}\n".format(func), readelf_output): + feedback.set_tag("banned_funcs", True) + feedback.set_global_result("failed") + feedback.set_global_feedback("Vous utilisez la fonction {}, qui n'est pas autorisée.".format(func)) + exit(0) + + +# Remove source files +subprocess.run("rm -rf *.c *.tpl *.h *.o", shell=True) + +LANG = input.get_input('@lang') + +# Run the code in a parallel container +p = subprocess.Popen(shlex.split("run_student --time 20 --hard-time 60 ./tests LANGUAGE={}".format(LANG)), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) +p.communicate() + +# If run failed, exit with "failed" result +if p.returncode: + feedback.set_global_result("failed") + if p.returncode == 256-8: + montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur. Le signal SIGFPE a été envoyé : *Floating Point Exception*.") + feedback.set_tag("sigfpe", True) + elif p.returncode == 256-11: + montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur. Le signal SIGSEGV a été envoyé : *Segmentation Fault*.") + elif p.returncode == 252: + montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a tenté d'allouer plus de mémoire que disponible.") + feedback.set_tag("memory", True) + elif p.returncode == 253: + montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a pris trop de temps pour s'exécuter.") + else: + montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur.") + feedback.set_global_feedback(rst.indent_block(2, montest_output, " "), True) + exit(0) +#elif run_output: +# feedback.set_global_feedback("- Sortie de votre méthode de test:\n" + rst.indent_block(2, rst.get_codeblock('', run_output), " "), True) + +# Comment to run the tests +#feedback.set_global_feedback("- **Cette note n'est pas finale.** Une série de tests sera exécutée sur votre code après l'examen.\n", True) +#exit(0) + +# Fetch CUnit test results +results_raw = [r.split('#') for r in open('results.txt').read().splitlines()] +results = [{'pid':r[0], 'code':r[1], 'desc':r[2], 'weight':int(r[3]), 'tags': r[4].split(","), 'info_msgs':r[5:]} for r in results_raw] + + +# Produce feedback +if all([r['code'] == 'SUCCESS' for r in results]): + feedback.set_global_feedback("\n- Votre code a passé tous les tests.", True) +else: + feedback.set_global_feedback("\n- Il y a des erreurs dans votre solution.", True) + +score = 0 +total = 0 +tests_result = {} + +for test in results: + total += test['weight'] + for tag in test['tags']: + if tag != "": + feedback.set_tag(tag, True) + if test['code'] == 'SUCCESS': + score += test['weight'] + feedback.set_problem_feedback("* {desc}\n\n => réussi ({weight}/{weight}) pts)\n\n".format(**test)+(" Info: {}\n\n".format(" — ".join(test['info_msgs'])) if test['info_msgs'] else '\n'), + test['pid'], True) + tests_result[test['pid']] = True if tests_result.get(test['pid'], True) else False + else: + feedback.set_problem_feedback("* {desc}\n\n => échoué (0/{weight}) pts)\n\n".format(**test)+(" Info: {}\n\n".format(" — ".join(test['info_msgs'])) if test['info_msgs'] else '\n'), + test['pid'], True) + tests_result[test['pid']] = False + +for pid, result in tests_result.items(): + if result: + feedback.set_problem_result("success", pid) + else: + feedback.set_problem_result("failed", pid) + +with open("../task.yaml", 'r') as stream: + problems = yaml.load(stream)['problems'] + + for name, meta in problems.items(): + if meta['type'] == 'match': + answer = input.get_input(name) + if answer == meta['answer']: + feedback.set_problem_result("success", name) + feedback.set_problem_feedback("Votre réponse est correcte. (1/1 pts)", name, True) + score += 1 + else: + feedback.set_problem_result("failed", name) + feedback.set_problem_feedback("Votre réponse est incorrecte. (0/1 pts)", name, True) + + total += 1 + +score = 100*score/(total if not total == 0 else 1) +feedback.set_grade(score) +feedback.set_global_result("success" if score >= 50 else "failed") From 8df0a72dfc708569a9a17e0a3ee850efe3de64ba Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 12 Sep 2018 23:53:05 +0200 Subject: [PATCH 20/53] Add fstdout and fstderr to allow tests to bypass the pipes when writing on stdout and stderr --- student/CTester/CTester.c | 6 ++++++ student/CTester/CTester.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index 854eef2..d908cfc 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -46,6 +46,10 @@ int true_stdout; */ int pipe_stderr[2], usr_pipe_stderr[2]; int pipe_stdout[2], usr_pipe_stdout[2]; +/** + * File stream objects used to print on true_stdout and true_stderr while in sandbox. + */ +extern FILE *fstdout, *fstderr; extern int stdout_cpy, stderr_cpy; struct itimerval it_val; @@ -278,6 +282,8 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { mallopt(M_CHECK_ACTION, 1); // don't abort if double free true_stderr = dup(STDERR_FILENO); // preparing a non-blocking pipe for stderr true_stdout = dup(STDOUT_FILENO); // preparing a non-blocking pipe for stderr + fstdout = fdopen(true_stdout, "w"); // We can't just copy-paster stdout and stderr + fstderr = fdopen(true_stderr, "w"); // as these structures use the file descriptor int *pipes[] = {pipe_stderr, pipe_stdout, usr_pipe_stdout, usr_pipe_stderr}; for(int i=0; i < 4; i++) { // Configuring pipes to be non-blocking diff --git a/student/CTester/CTester.h b/student/CTester/CTester.h index 2ac4f1c..2f3acd6 100644 --- a/student/CTester/CTester.h +++ b/student/CTester/CTester.h @@ -43,4 +43,6 @@ struct wrap_log_t logs; */ int stdout_cpy, stderr_cpy; +FILE *fstdout, *fstderr; + sigjmp_buf segv_jmp; From f99a7ae2ebaddc25f6f44b62bf00e442448deeb3 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Mon, 17 Sep 2018 11:31:30 +0200 Subject: [PATCH 21/53] Reorganizing code of read and recv buffering and partial return, for use by wrap_file and wrap_socket --- student/CTester/read_write.c | 343 ++++++++++++++++++++++++++ student/CTester/read_write.h | 205 +++++++++++++++ student/CTester/wrap_file.c | 27 +- student/CTester/wrap_file.h | 8 +- student/CTester/wrap_network_socket.c | 301 +--------------------- student/CTester/wrap_network_socket.h | 35 --- 6 files changed, 588 insertions(+), 331 deletions(-) create mode 100644 student/CTester/read_write.c create mode 100644 student/CTester/read_write.h diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c new file mode 100644 index 0000000..ea81f46 --- /dev/null +++ b/student/CTester/read_write.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "read_write.h" + +#define BILLION (1000*1000*1000) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + + +/** + * Auxiliary structures and functions + */ + +void getnanotime(struct timespec *res) +{ + clock_gettime(CLOCK_REALTIME, res); +} + +int64_t get_time_interval(const struct timespec *pasttime, const struct timespec *curtime) +{ + int64_t past = pasttime->tv_sec; + past *= BILLION; + past += pasttime->tv_nsec; + int64_t cur = curtime->tv_sec; + cur *= BILLION; + cur += curtime->tv_nsec; + int64_t interval = cur - past; + if (interval < 0) { + fprintf(stderr, "BUG: negative time interval"); + } + return interval; +} + + +/** + * read-related + */ + +// #define READ_MODE_BUFCHUK 1 // not used + +/** + * Precision for the 'interval' field. + * It contains the wait time for the current chunk, as measured from the previous call. + * It may be positive: in this case, it means the next data was not available + * at the end of the previous call, and it is the time the current call has + * to wait before accessing data. Minus the interval between the two calls. + * It may be negative: in this case, it represents the opposite of the amout + * of time the current chunk has been available. + * Thanks to this field, we can enable the chunks at more or less the right moment. + */ +struct read_item { + int fd; // the file descriptor this structure applies to + //int mode; // the type of data provider used; not used + const struct read_buffer_t *buf; // Provided read_buffer_t structure + unsigned int chunk_id; // Current chunk, or next chunk to be received + size_t bytes_read; // Number of bytes read inside the current chunk + struct timespec last_time; // Time of the end of the last call of read on this fd/socket + int64_t interval; // In real-time mode (READ_WRITE_REAL_INTERVAL), real wait interval for the current chunk. +}; + +struct read_fd_table_t { + size_t n; + struct read_item *items; +} read_fd_table; + +struct write_item { + int fd; // the file descriptor this structure applies to. + const struct write_buffer_t *buf; // Provided write_buffer_t structure + unsigned int chunk_id; // Current chunk, or next chunk to be received + size_t bytes_written; // Number of bytes of the current chunk already written + struct timespec last_time; // Time of the end of the last call of write on this fd/socket + int64_t interval; // In real-time mode (READ_WRITE_REAL_INTERVAL), read wait interval for the current chunk. +}; + +struct write_fd_table_t { + size_t n; + struct write_item *items; +} write_fd_table; + +struct read_item *read_get_entry(int fd) +{ + for (unsigned int i = 0; i < read_fd_table.n; i++) { + if (read_fd_table.items[i].fd == fd) + return &(read_fd_table.items[i]); + } + return NULL; +} + +bool fd_is_read_buffered(int fd) +{ + return (read_get_entry(fd) != NULL); +} + +void reinit_read_fd_table_item(int i) +{ + // We need to clean up _recv_fd_table.items[i] + read_fd_table.items[i].buf = NULL; // We're not responsible to free it. + read_fd_table.items[i].chunk_id = 0; + read_fd_table.items[i].interval = 0; + read_fd_table.items[i].last_time = (struct timespec) { + .tv_sec = 0, + .tv_nsec = 0 + }; +} + + +int read_remove_entry(int fd) +{ + bool found = false; + for (unsigned int i = 0; i < read_fd_table.n; i++) { + if (read_fd_table.items[i].fd == fd) { + found = true; + if (i < read_fd_table.n - 1) { + struct read_item *dest = &(read_fd_table.items[i]); + memmove(dest, dest + 1, read_fd_table.n - i - 1); + memset(&(read_fd_table.items[read_fd_table.n-1]), 0, sizeof(struct read_item)); + // This is not really a problem that we have a useless thing at the end + } + read_fd_table.n--; + } + } + return (found ? 1 : 0); +} + +/** + * Returns a pointer to an entry in the table, + * where we can safely store our informations. + * May be a pre-existing chunk, + * in which case the fd's will match. + */ +struct read_item *read_get_new_entry(int fd) +{ + unsigned int i = 0; + for (i = 0; i < read_fd_table.n; i++) { + if (read_fd_table.items[i].fd == fd) { + // We should clean it before changing it + reinit_read_fd_table_item(i); + break; + } + } + if (i >= read_fd_table.n) { + // realloc + struct read_item *tmp = realloc(read_fd_table.items, (read_fd_table.n + 1) * sizeof(struct read_item)); + if (tmp == NULL) + return NULL; + read_fd_table.items = tmp; + read_fd_table.n++; + } + // Now, we can insert at index i + return &(read_fd_table.items[i]); +} + +size_t read_handle_buffer_no_rt(struct read_item *cur, void *buf, size_t len, int flags, int64_t call_interval) +{ + const struct read_bufchunk_t *curchunk = &(cur->buf->chunks[cur->chunk_id]); + if (cur->bytes_read == 0) { + // We may have to wait + int64_t sleeptime = 0; + if (cur->buf->mode == READ_WRITE_BEFORE_INTERVAL) { + sleeptime = curchunk->interval; + } else { // READ_WRITE_AFTER_INTERVAL + sleeptime = curchunk->interval - call_interval; + } + if (sleeptime > 0) { + if ((flags & MSG_DONTWAIT) != 0) { + errno = EAGAIN; + return -1; + } + struct timespec tmp = (struct timespec) { + .tv_sec = sleeptime / BILLION, + .tv_nsec = sleeptime % BILLION + }; + nanosleep(&tmp, NULL); + getnanotime(&(cur->last_time)); // We need to update + } + } + size_t bytes_left = curchunk->buflen - cur->bytes_read; + size_t transfered_bytes = MIN(len, bytes_left); + memmove(buf, (curchunk->buf + cur->bytes_read), transfered_bytes); + cur->bytes_read += transfered_bytes; + if (cur->bytes_read >= curchunk->buflen) { + cur->chunk_id++; + cur->bytes_read = 0; + } + return transfered_bytes; +} + +size_t read_handle_buffer_rt(struct read_item *cur, void *buf, size_t len, int flags, int64_t call_interval) +{ + /* + * Assume the following: + * - cur->interval was the time to wait for the current chunk to become + * active, at the end of the previous call. + * - cur->bytes_read is 0 only if we have to wait. + */ + cur->interval -= call_interval; + /* + * Assume the following: + * - cur->interval is the remaining time to wait for the current chunk to become active. + * - cur->bytes_read is 0 only if we have to wait. + */ + if (cur->interval > 0) { + if ((flags & MSG_DONTWAIT) != 0) { + // The call would block but the caller requested it shouldn't block + errno = EAGAIN; + return -1; + } + int64_t sleeptime = cur->interval; + struct timespec tmp = (struct timespec) { + .tv_sec = sleeptime / BILLION, + .tv_nsec = sleeptime % BILLION + }; + nanosleep(&tmp, NULL); + cur->interval = 0; + getnanotime(&(cur->last_time)); + } + /* + * Assume the following: + * - cur->interval is the negative of the time the current chunk has been available. + */ + size_t total_bytes = 0; + for (unsigned int i = cur->chunk_id; i < cur->buf->nchunks; i++) { + if (len <= 0) { + break; + } + const struct read_bufchunk_t *curchunk = &(cur->buf->chunks[i]); + size_t bytes_left = curchunk->buflen - cur->bytes_read; + size_t transfered_bytes = MIN(len, bytes_left); + memmove((buf + total_bytes), (curchunk->buf + cur->bytes_read), transfered_bytes); + cur->bytes_read += transfered_bytes; + len -= transfered_bytes; + total_bytes += transfered_bytes; + if (cur->bytes_read >= curchunk->buflen) { + // Emptied chunk + cur->chunk_id++; + cur->bytes_read = 0; + if (cur->chunk_id < cur->buf->nchunks) { + // Update interval + cur->interval += cur->buf->chunks[cur->chunk_id].interval; + if (cur->interval > 0) { + break; // Not available yet + } + } + } + } + return total_bytes; +} + +ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags) +{ + struct read_item *cur = read_get_entry(fd); + if (cur == NULL) { + errno = EINTR; // This is about the only case where this can happen + return -1; + } + struct timespec curtime; + getnanotime(&curtime); + int64_t call_interval = get_time_interval(&(cur->last_time), &curtime); + cur->last_time = curtime; + if (cur->chunk_id >= cur->buf->nchunks) { + // Nothing left to read; we can return immediately 0 + // TODO remove this as a particular case + return 0; + } + size_t bytes_transfered = 0; + if (cur->buf->mode == READ_WRITE_BEFORE_INTERVAL || cur->buf->mode == READ_WRITE_AFTER_INTERVAL) { + bytes_transfered = read_handle_buffer_no_rt(cur, buf, len, flags, call_interval); + } else if (cur->buf->mode == READ_WRITE_REAL_INTERVAL) { + bytes_transfered = read_handle_buffer_rt(cur, buf, len, flags, call_interval); + } else { + return -1; + } + return bytes_transfered; +} + +void reinit_read_fd_table() +{ + // As we're not responsible to clean up all the recv_buffer_t, we can just free everything up + free(read_fd_table.items); + read_fd_table.n = 0; +} + +/*int enable_socket_recv_send_monitoring(bool active) +{ + // TODO +}*/ + +/*int enable_socket_all_monitoring(bool active) +{ + // TODO +}*/ + +/* +int enable_pipe_monitoring(bool active) +{ + // TODO +} +*/ + + +int set_read_data(int fd, const struct read_buffer_t *buf) +{ + if (buf == NULL) { + return read_remove_entry(fd); + } + if (buf->mode != READ_WRITE_REAL_INTERVAL && + buf->mode != READ_WRITE_AFTER_INTERVAL && + buf->mode != READ_WRITE_BEFORE_INTERVAL) { + return -2; + } + bool already_there = false; + if (fd_is_read_buffered(fd)) { + already_there = true; + } + struct read_item *tmp = read_get_new_entry(fd); + if (tmp == NULL) { + return -1; + } + tmp->fd = fd; + tmp->buf = buf; + tmp->chunk_id = 0; + tmp->bytes_read = 0; + getnanotime(&(tmp->last_time)); + if (buf->mode == READ_WRITE_REAL_INTERVAL && buf->nchunks > 0) { + // The first wait interval should be that of the first chunk. + tmp->interval = buf->chunks[0].interval; + } else { + tmp->interval = 0; + } + return (already_there ? 1 : 0); +} + +int set_write_buffer(int fd, const struct write_buffer_t *buf) +{ + // TODO implement this functions and the related functions. + // Currently not used so this is not a problem + return 0; +} diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h new file mode 100644 index 0000000..7bd4e9e --- /dev/null +++ b/student/CTester/read_write.h @@ -0,0 +1,205 @@ +/* + * Functions and structure for manipulating read and write system calls, + * as well as recv and send calls, in the presence of buffered data. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __CTESTER_READ_WRITE_H__ +#define __CTESTER_READ_WRITE_H__ + +#include +#include +#include + +/** + * Functions and structures for manipulating read and write system calls, + * as well as recv and send calls, in the presence of buffered data. + * + * An example use of these functions is with the recv system call on a socket. + * In real-world situations, recv has a blocking and partial return + * behaviour: when called, it blocks waiting for data to arrive (in general + * through the network), and when the data has arrived, it returns all + * the data readily available, and doesn't wait until more data arrives. + * + * In order to simulate that, we can provide recv with the information that + * each read action should return up to a specified chunk of data, and should + * therefore fragment the data returned to the caller. + * + * As we can use recv, recvfrom, recvmsg and read on a socket, all 4 system + * calls need to be wrapped. + * + * Similarly, send can also partially send the provided data, if the underlying + * send buffer and network doesn't have enough space to process the request. + * + * This partial-return behaviour of recv only happens on SOCK_STREAM sockets, + * on which we generaly use the recv and send system calls (but we can still + * use read, recvfrom and recvmsg without paying attention to flags or remote + * address). On UDP (SOCK_DGRAM), this behaviour doesn't happen, in the sense + * that it will try to return the whole datagram and discard the rest, and + * the use of recvfrom (or recvmsg) is recommanded (recv is allowed but will + * ignore the address). On SOCK_STREAM, send is recommanded (for sendto, + * sendmsg, address should correspond to the connected address), and + * on SOCK_DGRAM, sendto (and sendmsg) is recommanded (send or write should be + * connected), and there is no valid partial send (a second send will create + * a new datagram). + * + * On non-socket files, recv, recvfrom and recvmsg cannot be used, only read + * is authorized. read may block if the file is a pipe or a fifo (these have + * limited capacity). read also has a partial-return behaviour on pipes + * and fifos. + * + * On non-socket files, send, sento and sendmsg cannot be used, only write is + * authorized. write may block if the underlying device has no space available + * at the moment (pipes and fifos), and may result in a partial-return. + * + * The functions listed here implement these blocking behaviour, on pipes + * (which are remembered internally as pipes) and on sockets (which is a + * subclass of pipes in some sense, with the addition of the recv and send + * system calls) (which are remembered internally as sockets). + */ + + +/** + * Enable globally the socket monitoring functionnalities. A value of true + * activates it, a value of false deactivates it. + * This means socket will add the returned fd to the list of sockets, + * and subsequent recv and send will check that it is indeed a socket. + * Additionnaly, read and write will also know that this is a pipe-like object. + */ +//int enable_socket_recv_send_monitoring(bool active); + +/** + * In addition to enable_socket_recv_send_monitoring, the bind, connect, + * listen, accept and shutdown calls check whether it is a socket. + */ +//int enable_socket_all_monitoring(bool active); + +/** + * Enable globally the pipe monitoring functionnalities. + * Currently it is not supported. Maybe in a future version... + */ +//int enable_pipe_monitoring(bool active); + + + +//int set_recv_source(int fd, int sourcefd); + +//int set_recv_buffer(int fd, void *buf, size_t buflen); + +//int set_recv_policy(int fd, struct recv_policy_t *policy); +// Should define at some point the recv_policy_t structure. + +/** + * Structure representing a chunk of data, that is a fragment of the data + * that is returned as a single entity by read/recv. + * When simulating a partial-return read/recv, this is the fragments of data + * that the caller receives sequentially (if it asks for the full fragment). + * Fields: + * - interval: time interval (in microseconds) to wait before the chunk can be + * received. Should be no more than 1 million. Relative to the arrival + * of the previous chunk, and thus relative. + * - buf : chunk of data to be read. + * - buflen: length of this buffer. + */ +struct read_bufchunk_t { + uint32_t interval; + const void *buf; + size_t buflen; +}; + +/** + * The following macros define the modes to interpret the interval value: + * - the first possibility (READ_WRITE_REAL_INTERVAL) tracks the time interval + * in "real time", which means that the student may not see the actual + * specified time interval if it waits long enough. + * - the second possibility (READ_WRITE_AFTER_INTERVAL) only enforce the time + * interval between the end of a call that emptied a chunk and the actual + * read of the following call, which will start a new chunk. If the student + * waits enough, it may not see it. + * - the third possibility (READ_WRITE_BEFORE_INTERVAL) imposes an actual wait + * each time the student calls recv and the chunk has not been read before + * (if it has, then it won't wait, but it will read only one chunk). + * The interval is enforced between the start of this call and the actual + * read of this same call. The student cannot ignore it. + * Be careful to select the correct mode of operation, + * otherwise it won't work like you want it. + */ +#define READ_WRITE_REAL_INTERVAL 1 // Real-time interval: if the student waits a lot, he won't see the interval +#define READ_WRITE_AFTER_INTERVAL 2 // At least interval µs after emptying the chunk +#define READ_WRITE_BEFORE_INTERVAL 3 // At least interval µs before reading a new chunk + +/** + * Structure representing a group of fragments of data, as it would be received + * by subsequent read/recv of fragmented data. When simulating a partial-return + * read/recv, this is the full data to be read, split into different chunks. + */ +struct read_buffer_t { + int mode; // The mode of interpretation of interval + size_t nchunks; // Number of chunks + const struct read_bufchunk_t *chunks; // Table of chunks +}; + +/** + * Sets the data to be retrieved from read/recv, for partial-return, + * when simulating fragmented arrival of data. + * - fd: file descriptor. + * - buf: the data to be received. + * Returns + * - 0 if fd was not previously set to have a read_buffer_t + * - 1 if fd has been previously set to have a read_buffer_t + * - -1 if malloc error + * - -2 if argument error (typically buf->mode) + */ +int set_read_data(int fd, const struct read_buffer_t *buf); + +/** + * Chunk of buffer. The write operation waits for {interval} before allowing + * the write/send to use this buffer, if the previous chunk has been filled. + * The fields are similar to the read_bufchunk_t structure. + */ +struct write_bufchunk_t { + uint32_t interval; + void *buf; + size_t buflen; +}; + +/** + * Buffer for the write/send operations. It allows for partial write and send + * of data on the provided file descriptor, as would happen with send over + * a socket, or with write over a buffered file (e.g. a pipe). The structure + * and its fields are pretty similar to the read_buffer_t structure. The only + * difference is that currently, the only planned mode supported is + * READ_WRITE_BEFORE_INTERVAL. + */ +struct write_buffer_t { + int mode; // The mode of interpretation of interval + size_t nchunks; // Number of chunks + const struct write_bufchunk_t *chunks; // Table of chunks +}; + +/** + * Sets the buffer for the write/send operation, for simulating partial return. + * Currently, it does nothing other than returning zero; functionnalities + * will be added when needed. + */ +int set_write_buffer(int fd, const struct write_buffer_t *buf); + +/** + * Removes all the association between fd's and read_buffer_t's that have been + * previously set. It doesn't free the structures, however. + */ +void reinit_read_fd_table(); + + +#endif // __CTESTER_READ_WRITE_H__ + diff --git a/student/CTester/wrap_file.c b/student/CTester/wrap_file.c index 122a817..f65c1a0 100644 --- a/student/CTester/wrap_file.c +++ b/student/CTester/wrap_file.c @@ -6,6 +6,7 @@ #include #include "wrap.h" +#include "read_write.h" int __real_open(const char *pathname, int flags, mode_t mode); int __real_creat(const char *pathname, mode_t mode); @@ -25,6 +26,21 @@ extern struct wrap_monitor_t monitored; extern struct wrap_fail_t failures; extern struct wrap_log_t logs; + +/** + * Auxiliary functions and structures for implementing partial-returns in read/write. + */ +extern bool fd_is_read_buffered(int fd); + +extern ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags); + +extern struct read_fd_table_t read_fd_table; + + +/** + * Wrap functions. + */ + int __wrap_open(char *pathname, int flags, mode_t mode) { if(!wrap_monitoring || !monitored.open) { @@ -95,7 +111,7 @@ int __wrap_close(int fd){ } -int __wrap_read(int fd, void *buf, size_t count){ +ssize_t __wrap_read(int fd, void *buf, size_t count){ if(!wrap_monitoring || !monitored.read) { return __real_read(fd,buf,count); @@ -113,14 +129,19 @@ int __wrap_read(int fd, void *buf, size_t count){ } failures.read=NEXT(failures.read); // did not fail - int ret=__real_read(fd,buf,count); + int ret = 0; + if (fd_is_read_buffered(fd)) { + ret = read_handle_buffer(fd, buf, count, 0); + } else { + ret = __real_read(fd, buf, count); + } stats.read.last_return=ret; return ret; } -int __wrap_write(int fd, void *buf, size_t count){ +ssize_t __wrap_write(int fd, void *buf, size_t count){ if(!wrap_monitoring || !monitored.write) { return __real_write(fd,buf,count); diff --git a/student/CTester/wrap_file.h b/student/CTester/wrap_file.h index 99b5a52..6db20d5 100644 --- a/student/CTester/wrap_file.h +++ b/student/CTester/wrap_file.h @@ -54,7 +54,7 @@ struct stats_close_t { struct params_read_t { int fd; void *buf; - ssize_t count; + size_t count; }; // basic statistics for the utilisation of the read system call @@ -62,13 +62,13 @@ struct params_read_t { struct stats_read_t { int called; // number of times the read system call has been issued struct params_read_t last_params; // parameters for the last call issued - int last_return; // return value of the last read call issued + ssize_t last_return; // return value of the last read call issued }; struct params_write_t { int fd; void *buf; - ssize_t count; + size_t count; }; // basic statistics for the utilisation of the write system call @@ -76,7 +76,7 @@ struct params_write_t { struct stats_write_t { int called; // number of times the write system call has been issued struct params_read_t last_params; // parameters for the last call issued - int last_return; // return value of the last write call issued + ssize_t last_return; // return value of the last write call issued }; struct params_stat_t { diff --git a/student/CTester/wrap_network_socket.c b/student/CTester/wrap_network_socket.c index 1c4ff14..e09b1ce 100644 --- a/student/CTester/wrap_network_socket.c +++ b/student/CTester/wrap_network_socket.c @@ -19,10 +19,10 @@ #include #include +#include "read_write.h" #include "wrap.h" #define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define BILLION (1000*1000*1000) int __real_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); @@ -46,263 +46,15 @@ extern struct wrap_stats_t stats; extern struct wrap_monitor_t monitored; extern struct wrap_fail_t failures; - -/** - * Auxiliary structures and functions - */ - -void getnanotime(struct timespec *res) -{ - clock_gettime(CLOCK_REALTIME, res); -} - -int64_t get_time_interval(const struct timespec *pasttime, const struct timespec *curtime) -{ - int64_t past = pasttime->tv_sec; - past *= 1000*1000*1000; - past += pasttime->tv_nsec; - int64_t cur = curtime->tv_sec; - cur *= 1000*1000*1000; - cur += curtime->tv_nsec; - int64_t interval = cur - past; - if (interval < 0) { - fprintf(stderr, "BUG: negative time interval"); - } - return interval; -} - - -/** - * recv-related - */ - -// #define RECV_MODE_BUFCHUK 1 // not used - -/** - * Precision for the 'interval' field. - * It contains the wait time for the current chunk, as measured from the previous call. - * It may be positive: in this case, it means the next data was not available - * at the end of the previous call, and it is the time the current call has - * to wait before accessing data. Minus the interval between the two calls. - * It may be negative: in this case, it represents the opposite of the amout - * of time the current chunk has been available. - * Thanks to this field, we can enable the chunks at more or less the right moment. - */ -struct recv_item { - int fd; // the file descriptor this structure applies to - //int mode; // the type of data provider used; not used - const struct recv_buffer_t *buf; // Provided recv_buffer_t structure - unsigned int chunk_id; // Current chunk, or next chunk to be received - size_t bytes_read; // Number of bytes read inside the current chunk - struct timespec last_time; // Time of the end of the last call of recv on this socket - int64_t interval; // In real-time mode (RECV_REAL_INTERVAL), real wait interval for the current chunk. -}; - -struct recv_fd_table_t { - size_t n; - struct recv_item *items; -} recv_fd_table; - -struct recv_item *recv_get_entry(int fd) -{ - for (unsigned int i = 0; i < recv_fd_table.n; i++) { - if (recv_fd_table.items[i].fd == fd) - return &(recv_fd_table.items[i]); - } - return NULL; -} - -bool fd_is_recv_buffered(int fd) -{ - return (recv_get_entry(fd) != NULL); -} - -void reinit_recv_fd_table_item(int i) -{ - // We need to clean up _recv_fd_table.items[i] - recv_fd_table.items[i].buf = NULL; // We're not responsible to free it. - recv_fd_table.items[i].chunk_id = 0; - recv_fd_table.items[i].interval = 0; - recv_fd_table.items[i].last_time = (struct timespec) { - .tv_sec = 0, - .tv_nsec = 0 - }; -} - -void reinit_recv_fd_table() -{ - // As we're not responsible to clean up all the recv_buffer_t, we can just free everything up - free(recv_fd_table.items); - recv_fd_table.n = 0; -} - -int recv_remove_entry(int fd) -{ - bool found = false; - for (unsigned int i = 0; i < recv_fd_table.n; i++) { - if (recv_fd_table.items[i].fd == fd) { - found = true; - if (i < recv_fd_table.n - 1) { - struct recv_item *dest = &(recv_fd_table.items[i]); - memmove(dest, dest + 1, recv_fd_table.n - i - 1); - memset(&(recv_fd_table.items[recv_fd_table.n-1]), 0, sizeof(struct recv_item)); - // This is not really a problem that we have a useless thing at the end - } - recv_fd_table.n--; - } - } - return (found ? 1 : 0); -} - /** - * Returns a pointer to an entry in the table, - * where we can safely store our informations. - * May be a pre-existing chunk, - * in which case the fd's will match. + * Auxiliary functions and structures for use by implementers for read/write */ -struct recv_item *recv_get_new_entry(int fd) -{ - unsigned int i = 0; - for (i = 0; i < recv_fd_table.n; i++) { - if (recv_fd_table.items[i].fd == fd) { - // We should clean it before changing it - reinit_recv_fd_table_item(i); - break; - } - } - if (i >= recv_fd_table.n) { - // realloc - struct recv_item *tmp = realloc(recv_fd_table.items, (recv_fd_table.n + 1) * sizeof(struct recv_item)); - if (tmp == NULL) - return NULL; - recv_fd_table.items = tmp; - recv_fd_table.n++; - } - // Now, we can insert at index i - return &(recv_fd_table.items[i]); -} +extern bool fd_is_read_buffered(int fd); -size_t recv_handle_buffer_no_rt(struct recv_item *cur, void *buf, size_t len, int flags, int64_t call_interval) -{ - const struct recv_bufchunk_t *curchunk = &(cur->buf->chunks[cur->chunk_id]); - if (cur->bytes_read == 0) { - // We may have to wait - int64_t sleeptime = 0; - if (cur->buf->mode == RECV_BEFORE_INTERVAL) { - sleeptime = curchunk->interval; - } else { - sleeptime = curchunk->interval - call_interval; - } - if (sleeptime > 0) { - if ((flags & MSG_DONTWAIT) != 0) { - errno = EAGAIN; - return -1; - } - struct timespec tmp = (struct timespec) { - .tv_sec = sleeptime / BILLION, - .tv_nsec = sleeptime % BILLION - }; - nanosleep(&tmp, NULL); - getnanotime(&(cur->last_time)); // We need to update - } - } - size_t bytes_left = curchunk->buflen - cur->bytes_read; - size_t transfered_bytes = MIN(len, bytes_left); - memmove(buf, (curchunk->buf + cur->bytes_read), transfered_bytes); - cur->bytes_read += transfered_bytes; - if (cur->bytes_read >= curchunk->buflen) { - cur->chunk_id++; - cur->bytes_read = 0; - } - return transfered_bytes; -} +extern ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags); -size_t recv_handle_buffer_rt(struct recv_item *cur, void *buf, size_t len, int flags, int64_t call_interval) -{ - /* - * Assume the following: - * - cur->interval was the time to wait for the current chunk to become active, at the end of the previous call. - * - cur->bytes_read is 0 only if we have to wait. - */ - cur->interval -= call_interval; - /* - * Assume the following: - * - cur->interval is the remaining time to wait for the current chunk to become active. - * - cur->bytes_read is 0 only if we have to wait. - */ - if (cur->interval > 0) { - if ((flags & MSG_DONTWAIT) != 0) { - // The call would block but the caller requested it shouldn't block - errno = EAGAIN; - return -1; - } - int64_t sleeptime = cur->interval; - struct timespec tmp = (struct timespec) { - .tv_sec = sleeptime / BILLION, - .tv_nsec = sleeptime % BILLION - }; - nanosleep(&tmp, NULL); - cur->interval = 0; - getnanotime(&(cur->last_time)); - } - /* - * Assume the following: - * - cur->interval is the negative of the time the current chunk has been available. - */ - size_t total_bytes = 0; - for (unsigned int i = cur->chunk_id; i < cur->buf->nchunks; i++) { - if (len <= 0) { - break; - } - const struct recv_bufchunk_t *curchunk = &(cur->buf->chunks[i]); - size_t bytes_left = curchunk->buflen - cur->bytes_read; - size_t transfered_bytes = MIN(len, bytes_left); - memmove((buf + total_bytes), (curchunk->buf + cur->bytes_read), transfered_bytes); - cur->bytes_read += transfered_bytes; - len -= transfered_bytes; - total_bytes += transfered_bytes; - if (cur->bytes_read >= curchunk->buflen) { - // Emptied chunk - cur->chunk_id++; - cur->bytes_read = 0; - if (cur->chunk_id < cur->buf->nchunks) { - // Update interval - cur->interval += cur->buf->chunks[cur->chunk_id].interval; - if (cur->interval > 0) { - break; // Not available yet - } - } - } - } - return total_bytes; -} +extern struct read_fd_table_t read_fd_table; -ssize_t recv_handle_buffer(int sockfd, void *buf, size_t len, int flags) -{ - struct recv_item *cur = recv_get_entry(sockfd); - if (cur == NULL) { - errno = EINTR; // This is about the only case where this can happen - return -1; - } - struct timespec curtime; - getnanotime(&curtime); - int64_t call_interval = get_time_interval(&(cur->last_time), &curtime); - cur->last_time = curtime; - if (cur->chunk_id >= cur->buf->nchunks) { - // Nothing left to read; we can return immediately 0 - // TODO remove this as a particular case - return 0; - } - size_t bytes_transfered = 0; - if (cur->buf->mode == RECV_BEFORE_INTERVAL || cur->buf->mode == RECV_AFTER_INTERVAL) { - bytes_transfered = recv_handle_buffer_no_rt(cur, buf, len, flags, call_interval); - } else if (cur->buf->mode == RECV_REAL_INTERVAL) { - bytes_transfered = recv_handle_buffer_rt(cur, buf, len, flags, call_interval); - } else { - return -1; - } - return bytes_transfered; -} /** * Wrap functions. @@ -474,8 +226,8 @@ ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) } failures.recv = NEXT(failures.recv); ssize_t ret = -1; - if (fd_is_recv_buffered(sockfd)) { - ret = recv_handle_buffer(sockfd, buf, len, flags); + if (fd_is_read_buffered(sockfd)) { + ret = read_handle_buffer(sockfd, buf, len, flags); } else { ret = __real_recv(sockfd, buf, len, flags); } @@ -507,7 +259,11 @@ ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct soc } failures.recvfrom = NEXT(failures.recvfrom); ssize_t ret = -1; - ret = __real_recvfrom(sockfd, buf, len, flags, src_addr, addrlen); + if (fd_is_read_buffered(sockfd) && src_addr == NULL && addrlen == NULL) { + ret = read_handle_buffer(sockfd, buf, len, flags); + } else { + ret = __real_recvfrom(sockfd, buf, len, flags, src_addr, addrlen); + } if (ret >= 0 && src_addr != NULL) { // Same justification as in accept stats.recvfrom.last_returned_addr.addrlen = *addrlen; @@ -717,38 +473,5 @@ void reinit_network_socket_stats() memset(&(stats.send_all), 0, sizeof(struct stats_send_all_t)); memset(&(stats.shutdown), 0, sizeof(stats.shutdown)); memset(&(stats.socket), 0, sizeof(struct stats_socket_t)); - reinit_recv_fd_table(); -} - -int set_recv_data(int fd, const struct recv_buffer_t *buf) -{ - if (buf == NULL) { - return recv_remove_entry(fd); - } - if (buf->mode != RECV_REAL_INTERVAL && - buf->mode != RECV_AFTER_INTERVAL && - buf->mode != RECV_BEFORE_INTERVAL) { - return -2; - } - bool already_there = false; - if (fd_is_recv_buffered(fd)) { - already_there = true; - } - struct recv_item *tmp = recv_get_new_entry(fd); - if (tmp == NULL) { - return -1; - } - tmp->fd = fd; - tmp->buf = buf; - tmp->chunk_id = 0; - tmp->bytes_read = 0; - getnanotime(&(tmp->last_time)); - if (buf->mode == RECV_REAL_INTERVAL && buf->nchunks > 0) { - // The first wait interval should be that of the first chunk. - tmp->interval = buf->chunks[0].interval; - } else { - tmp->interval = 0; - } - return (already_there ? 1 : 0); } diff --git a/student/CTester/wrap_network_socket.h b/student/CTester/wrap_network_socket.h index 3b0fdba..61836e7 100644 --- a/student/CTester/wrap_network_socket.h +++ b/student/CTester/wrap_network_socket.h @@ -268,40 +268,5 @@ struct stats_socket_t { */ void reinit_network_socket_stats(); -//int set_recv_source(int fd, int sourcefd); - -//int set_recv_buffer(int fd, void *buf, size_t buflen); - -//int set_recv_policy(int fd, struct recv_policy_t *policy); -// Should define at some point the recv_policy_t structure. - -struct recv_bufchunk_t { - uint32_t interval; // Time interval (in microseconds) to wait before the chunk can be received. Should be no more than 1000000. Relative to each other. - const void *buf; // Buffer of data to be read. - size_t buflen; // Length of this buffer -}; - -#define RECV_REAL_INTERVAL 1 // Real-time interval: if the student waits a lot, he won't see the interval -#define RECV_AFTER_INTERVAL 2 // At least interval µs after emptying the chunk -#define RECV_BEFORE_INTERVAL 3 // At least interval µs before reading a new chunk - -/** - * Important: - * There are two ways (mode) to interpret the interval value: - * - the first possibility (RECV_REAL_INTERVAL) tracks the time interval in real time, which means that the student may not see any actual time interval if it waits long enough. - * - the second possibility (RECV_AFTER_INTERVAL) only enforce the time interval between the end of a call that emptied a chunk and the actual read of the following call, which will start a new chunk. If the student waits enough, it may not see it. - * - the third possibility (RECV_BEFORE_INTERVAL) imposes an actual wait each time the student calls recv and the chunk has not been read before (if it has, then it won't wait, but it will read only one chunk). The interval is enforced between the start of this call and the actual read of this same call. The student cannot ignore it. - * Be careful to select the correct mode of operation, otherwise it won't work very much. - */ -struct recv_buffer_t { - int mode; // The mode of interpretation of interval - size_t nchunks; // Number of chunks - const struct recv_bufchunk_t *chunks; // Table of chunks -}; - -/** - * Returns -1 if malloc error, -2 if argument error (buf->mode typically), 0 if fd was not present, and 1 if it was already present. - */ -int set_recv_data(int fd, const struct recv_buffer_t *buf); #endif // __WRAP_NETWORK_SOCKET_H__ From 60a83ea89d6c23d9c8be2cdefe504e7ef88d0993 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Tue, 18 Sep 2018 09:45:37 +0200 Subject: [PATCH 22/53] Add utility functions for inet and sockets --- student/CTester/CTester.h | 6 + student/CTester/trap.h | 6 + student/CTester/util_inet.c | 32 +++ student/CTester/util_inet.h | 30 ++ student/CTester/util_sockets.c | 489 +++++++++++++++++++++++++++++++++ student/CTester/util_sockets.h | 153 +++++++++++ 6 files changed, 716 insertions(+) create mode 100644 student/CTester/util_inet.c create mode 100644 student/CTester/util_inet.h create mode 100644 student/CTester/util_sockets.c create mode 100644 student/CTester/util_sockets.h diff --git a/student/CTester/CTester.h b/student/CTester/CTester.h index 2f3acd6..4bb3101 100644 --- a/student/CTester/CTester.h +++ b/student/CTester/CTester.h @@ -1,3 +1,6 @@ +#ifndef __CTESTER_MAIN_H__ +#define __CTESTER_MAIN_H__ + #include #include #include @@ -46,3 +49,6 @@ int stdout_cpy, stderr_cpy; FILE *fstdout, *fstderr; sigjmp_buf segv_jmp; + +#endif // __CTESTER_MAIN_H__ + diff --git a/student/CTester/trap.h b/student/CTester/trap.h index fa29af4..8f1417f 100644 --- a/student/CTester/trap.h +++ b/student/CTester/trap.h @@ -1,3 +1,6 @@ +#ifndef __CTESTER_TRAP_H__ +#define __CTESTER_TRAP_H__ + enum { TRAP_LEFT, TRAP_RIGHT @@ -6,3 +9,6 @@ enum { void *trap_buffer(size_t size, int type, int flags, void *data); int free_trap(void *ptr, size_t size); + +#endif // __CTESTER_TRAP_H__ + diff --git a/student/CTester/util_inet.c b/student/CTester/util_inet.c new file mode 100644 index 0000000..bb9c903 --- /dev/null +++ b/student/CTester/util_inet.c @@ -0,0 +1,32 @@ +#include + +#include "util_inet.h" + +void htons_tab(uint16_t *src, uint16_t *dest, size_t len) +{ + for (unsigned i = 0; i < len; i++) { + dest[i] = htons(src[i]); + } +} + +void ntohs_tab(uint16_t *src, uint16_t *dest, size_t len) +{ + for (unsigned i = 0; i < len; i++) { + dest[i] = ntohs(src[i]); + } +} + +void htonl_tab(uint32_t *src, uint32_t *dest, size_t len) +{ + for (unsigned i = 0; i < len; i++) { + dest[i] = htonl(src[i]); + } +} + +void ntohl_tab(uint32_t *src, uint32_t *dest, size_t len) +{ + for (unsigned i = 0; i < len; i++) { + dest[i] = ntohl(src[i]); + } +} + diff --git a/student/CTester/util_inet.h b/student/CTester/util_inet.h new file mode 100644 index 0000000..c5ea5f1 --- /dev/null +++ b/student/CTester/util_inet.h @@ -0,0 +1,30 @@ +/** + * Utility functions related to htons, ntohs, htonl, ntohl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __CTESTER_UTIL_INET_H__ +#define __CTESTER_UTIL_INET_H__ + +#include + +void htons_tab(uint16_t *src, uint16_t *dest, size_t len); + +void ntohs_tab(uint16_t *src, uint16_t *dest, size_t len); + +void htonl_tab(uint32_t *src, uint32_t *dest, size_t len); + +void ntohl_tab(uint32_t *src, uint32_t *dest, size_t len); + +#endif // __CTESTER_UTIL_INET_H__ + diff --git a/student/CTester/util_sockets.c b/student/CTester/util_sockets.c new file mode 100644 index 0000000..824ddf4 --- /dev/null +++ b/student/CTester/util_sockets.c @@ -0,0 +1,489 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util_sockets.h" + +const uint8_t _OK = 0; +const uint8_t _TOO_MUCH = 1; +const uint8_t _TOO_FEW = 2; +const uint8_t _NOT_SAME = 4; +const uint8_t _NOTHING_RECV = 8; +const uint8_t _RECV_ERROR = 16; +const uint8_t _SEND_ERROR = 32; +const uint8_t _EXIT_PROCESS = 64; +const uint8_t _EXTEND_MSG = 128; + +int create_socket(const char *host, const char *serv, int domain, int type, int flags, int do_bind) +{ + errno = 0; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | flags; + hints.ai_family = domain; + hints.ai_socktype = type; + struct addrinfo *rep; + int r = getaddrinfo(host, serv, &hints, &rep); + if (r || !rep) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r)); + return -1; + } + int fd = socket(rep->ai_family, rep->ai_socktype, rep->ai_protocol); + if (fd == -1) { + perror("socket"); + freeaddrinfo(rep); + return -1; + } + if (do_bind) { + /*int yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) { + perror("setsockopt"); + CU_FAIL("Coucou"); + close(fd); + freeaddrinfo(rep); + return -1; + }*/ + if (bind(fd, rep->ai_addr, rep->ai_addrlen)) { + perror("bind"); + close(fd); + freeaddrinfo(rep); + return -1; + } + } else { + if (connect(fd, rep->ai_addr, rep->ai_addrlen)) { + perror("bind"); + close(fd); + freeaddrinfo(rep); + return -1; + } + } + freeaddrinfo(rep); + return fd; +} + +int create_tcp_server_socket(const char *serv, int domain, int type) +{ + int serverfd = create_socket(NULL, serv, domain, type, AI_PASSIVE, true); + if (listen(serverfd, 1)) { + perror("listen"); + close(serverfd); + return -1; + } + return serverfd; +} + +int create_tcp_client_socket(const char *host, const char *serv, int domain, int type) +{ + int clientfd = create_socket(host, serv, domain, type, 0, false); + return clientfd; +} + +int create_udp_server_socket(const char *serv, int domain) +{ + int serverfd = create_socket(NULL, serv, domain, SOCK_DGRAM, AI_PASSIVE, true); + return serverfd; +} + +int create_udp_client_socket(const char *host, const char *serv, int domain) +{ + int clientfd = create_socket(host, serv, domain, SOCK_DGRAM, 0, false); + return clientfd; +} + +#define PIPE_AND_FORK_BEGIN() \ +if (pipe(c2s)) { \ + perror("pipe"); \ + return -1; \ +} \ +if (pipe(s2c)) { \ + perror("pipe"); \ + close(c2s[0]); \ + close(c2s[1]); \ + return -1; \ +} \ +int pid = fork(); \ +if (pid == -1) { \ + perror("fork"); \ + close(c2s[0]); \ + close(c2s[1]); \ + close(s2c[0]); \ + close(s2c[1]); \ + return -1; \ +} else if (pid == 0) { \ + close(c2s[1]); \ + close(s2c[0]); \ + /* server : end of macro, start of custom */ + +#define PIPE_AND_FORK_MIDDLE() \ + /* server : end of custom, start of macro */ \ +} else { \ + close(c2s[0]); \ + close(s2c[1]); \ + /* client : end of macro, start of custom */ + +#define PIPE_AND_FORK_END() \ + /* client : end of custom */ \ +} + +/** + * Returns 0 if correct, + * -3 if syscall problem + * -2 if pipe_in got a problem, + * -1 if recv error + * 1 if pipe_in got a message + * 2 if too few data recv'd + * 3 if too much data recv'd + * 4 if different data + */ +int wait_recv_tcp(int sfd, int pipe_in, int pipe_out, const struct cs_network_chunk *chunk) +{ + size_t required_bytes = chunk->data_length, recv_bytes = 0, total_bytes = required_bytes + 1; + ssize_t r = -1; + void *buf = malloc(total_bytes); + if (!buf) { + perror("malloc"); + return -3; + } + nfds_t nfds = 2; + struct pollfd fds[2]; + fds[0] = (struct pollfd) { + .fd = sfd, + .events = POLLIN | POLLERR, + .revents = 0 + }; + fds[1] = (struct pollfd) { + .fd = pipe_in, + .events = POLLIN | POLLERR, + .revents = 0 + }; + do { + int p = poll(fds, nfds, -1); + if (p == -1) { + perror("poll"); + free(buf); + return -3; + } + if (fds[1].revents) { + // Got something on pipe + if (fds[1].revents & POLLIN) { + // Read; quit + free(buf); + return 1; + } else { + // Error: quit + free(buf); + return -4; + } + } + if (fds[0].revents) { + // Got something on socket + if (fds[0].revents & POLLIN) { + r = recv(sfd, (buf + recv_bytes), (total_bytes - recv_bytes), 0); + if (r == -1) { + // Error condition on socket + perror("recv"); + free(buf); + return -1; + } + recv_bytes += r; + } else { + // Error condition on socket + free(buf); + return -1; + } + } + } while (recv_bytes <= required_bytes && r != 0); + if (r == -1) { + // recv error : normally we shouldn't get here + free(buf); + return -1; + } + if (recv_bytes < required_bytes) { + free(buf); + write(pipe_out, &_TOO_FEW, U8SZ); + write(pipe_out, &recv_bytes, sizeof(recv_bytes)); + return 2; + } + if (recv_bytes > required_bytes) { + free(buf); + write(pipe_out, &_TOO_MUCH, U8SZ); + write(pipe_out, &recv_bytes, sizeof(recv_bytes)); + return 3; + } + int c = memcmp(buf, chunk->data, chunk->data_length); + free(buf); + if (c) { + return 4; + } + return 0; +} + +/** + * Returns + * -3 if syscall problem + * -2 if pipe_in got a problem + * -1 if send got a problem + * 0 if ok + * 1 if pipe_in got a message + */ +int wait_send_tcp(int sfd, int pipe_in, const struct cs_network_chunk *chunk) +{ + size_t required_bytes = chunk->data_length, sent_bytes = 0; + ssize_t s = -1; + nfds_t nfds = 2; + struct pollfd fds[2]; + fds[0] = (struct pollfd) { + .fd = sfd, + .events = POLLOUT | POLLERR, + .revents = 0 + }; + fds[1] = (struct pollfd) { + .fd = pipe_in, + .events = POLLIN | POLLERR, + .revents = 0 + }; + do { + int p = poll(fds, nfds, -1); + if (p == -1) { + perror("poll"); + return -3; + } + if (fds[1].revents) { + if (fds[1].revents & POLLIN) { + return 1; + } else { + return -2; + } + } + if (fds[0].revents) { + if (fds[0].revents & POLLOUT) { + s = send(sfd, (chunk->data + sent_bytes), (required_bytes - sent_bytes), 0); + if (s == -1) { + perror("send"); + return -1; + } + sent_bytes += s; + } else { + return -1; + } + } + } while (sent_bytes != required_bytes && s != -1); + if (s == -1) { + // Normally we shouldn't get here + return -1; + } + return 0; +} + +/** + * Return 3 if process was asked to stop, 2 if error, 0 on success. + */ +int handle_tcp_transaction(int sfd, const struct cs_network_transaction *transaction, int pipe_in, int pipe_out) +{ + for (unsigned j = 0; j < transaction->nchunks; j++) { + struct cs_network_chunk *curchunk = &(transaction->chunks[j]); + if (curchunk->type == RECV_CHUNK) { + // recv + int r = wait_recv_tcp(sfd, pipe_in, pipe_out, curchunk); + if (r < -1) { + fprintf(stderr, "Server error\n"); + return 2; + } + switch (r) { + case -1: + // recv error + write(pipe_out, &_RECV_ERROR, U8SZ); + write(pipe_out, &errno, sizeof(errno)); + continue; + case 1: + // Stop the server + return 3; + case 2: + // Not enough bytes before connection was closed + continue; + case 3: + // Sent too much data + continue; + case 4: + // Different data + write(pipe_out, &_NOT_SAME, U8SZ); + //write(pipe_out, &(res->expected_request_length), sizeof(res->expected_request_length)); + // So that the reader can skip the data without knowing its length in advance + //write(pipe_out, buf, res->expected_request_length); + continue; + } + } else if (curchunk->type == SEND_CHUNK) { + // send + int s = wait_send_tcp(sfd, pipe_in, curchunk); + if (s < -1) { + fprintf(stderr, "Server error\n"); + return 2; + } + if (s == -1) { + // Send error + write(pipe_out, &_SEND_ERROR, U8SZ); + write(pipe_out, &errno, sizeof(errno)); + continue; + } + if (s == 1) { + return 3; + } + } // end if type + } // end for chunks in transaction + write(pipe_out, &_OK, U8SZ); // correct transaction + return 0; +} + +int handle_udp_transaction(int sfd, const struct cs_network_transaction *transaction, int pipe_in, int pipe_out) +{ + for (unsigned int j = 0; j < transaction->nchunks; j++) { + struct cs_network_chunk *curchunk = &(transaction->chunks[j]); + if (curchunk->type == RECV_CHUNK) { + // recvfrom + // TODO complete + } else if (curchunk->type == SEND_CHUNK) { + // sendto + // TODO complete + } + } + return 0; +} + +int launch_test_tcp_server(struct cs_network_transactions *transactions, const char *serv, int domain, int *server_in, int *server_out, int *spid) +{ + int c2s[2], s2c[2]; + PIPE_AND_FORK_BEGIN(); + // c2s[0] and s2c[1] + int sfd = create_tcp_server_socket(serv, domain, SOCK_STREAM); + if (sfd == -1) { + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(1); + } + for (unsigned i = 0; i < transactions->ntransactions; i++) { + struct cs_network_transaction *curtrans = &(transactions->transactions[i]); + // First, accept the connection + struct sockaddr_storage client_addr; + socklen_t client_addrlen = sizeof(client_addr); + int cfd = accept(sfd, (struct sockaddr*)&client_addr, &client_addrlen); + if (cfd == -1) { + perror("accept"); + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(1); + } + int h = handle_tcp_transaction(cfd, curtrans, c2s[0], s2c[1]); + switch (h) { + case 0: + break; + case 2: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(2); + case 3: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(3); + } + } // end for transaction in transactions + exit(0); + PIPE_AND_FORK_MIDDLE(); + // s2c[0] and c2s[1] + *spid = pid; + *server_in = c2s[1]; + *server_out = s2c[0]; + return 0; + PIPE_AND_FORK_END(); +} + +int launch_test_tcp_client(struct cs_network_transactions *transactions, const char *host, const char *serv, int domain, int *client_in, int *client_out, int *cpid) +{ + int c2s[2], s2c[2]; + PIPE_AND_FORK_BEGIN(); + // c2s[0] and s2c[1] + int cfd = create_tcp_client_socket(host, serv, domain, SOCK_STREAM); + if (cfd == -1) { + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(1); + } + uint8_t buf_in[1]; + for (unsigned i = 0; i < transactions->ntransactions; i++) { + struct cs_network_transaction *curtrans = &(transactions->transactions[i]); + // First, wait until the server is up + ssize_t rr = read(c2s[0], buf_in, sizeof(buf_in)); + if (rr == -1) { + perror("read"); + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(2); + } + int h = handle_tcp_transaction(cfd, curtrans, c2s[0], s2c[1]); + switch (h) { + case 0: + break; + case 2: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(2); + case 3: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(3); + } + } // end for transaction in transactions + exit(0); + PIPE_AND_FORK_MIDDLE(); + // s2c[0] and c2s[1] + *cpid = pid; + *client_in = c2s[1]; + *client_out = s2c[0]; + return 0; + PIPE_AND_FORK_END(); +} + +int launch_test_udp_server(struct cs_network_transactions *transactions, const char *serv, int domain, int *server_in, int *server_out, int *spid) +{ + int c2s[2], s2c[2]; + PIPE_AND_FORK_BEGIN(); + // c2s[0] and s2c[1] + int sfd = create_udp_server_socket(serv, domain); + if (sfd == -1) { + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(1); + } + for (unsigned i = 0; i < transactions->ntransactions; i++) { + struct cs_network_transaction *curtrans = &(transactions->transactions[i]); + int h = handle_udp_transaction(sfd, curtrans, c2s[0], s2c[1]); + // TODO complete + } + exit(0); + PIPE_AND_FORK_MIDDLE(); + *spid = pid; + *server_in = c2s[1]; + *server_out = s2c[0]; + return 0; + PIPE_AND_FORK_END(); +} + +int launch_test_udp_client(struct cs_network_transactions *transactions, const char *host, const char *serv, int domain, int *client_in, int *client_out, int *cpid) +{ + int c2s[2], s2c[2]; + PIPE_AND_FORK_BEGIN(); + int cfd = create_udp_client_socket(host, serv, domain); + if (cfd == -1) { + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(1); + } + for (unsigned i = 0; i < transactions->ntransactions; i++) { + struct cs_network_transaction *curtrans = &(transactions->transactions[i]); + int h = handle_udp_transaction(cfd, curtrans, c2s[0], s2c[1]); + // TODO complete + } + exit(0); + PIPE_AND_FORK_MIDDLE(); + *cpid = pid; + *client_in = c2s[1]; + *client_out = s2c[0]; + return 0; + PIPE_AND_FORK_END(); +} + diff --git a/student/CTester/util_sockets.h b/student/CTester/util_sockets.h new file mode 100644 index 0000000..d6faed0 --- /dev/null +++ b/student/CTester/util_sockets.h @@ -0,0 +1,153 @@ +#ifndef __CTESTER_UTIL_SOCKETS_H__ +#define __CTESTER_UTIL_SOCKETS_H__ + +#include + +/** + * Constants used when reporting the output of the mock server or client. + * Currenly, they may be combined with any of them; + * maybe we should exclude some combination. + */ +#define OK 0 +#define TOO_MUCH 1 // And the amount should follow +#define TOO_FEW 2 // And the amount should follow +#define NOT_SAME 4 // And the recvd tab should follow +#define NOTHING_RECV 8 +#define RECV_ERROR 16 +#define SEND_ERROR 32 +#define EXIT_PROCESS 64 // The child process exits: beware of SIGPIPE! +#define EXTEND_MSG 128 // Next octet gives the status +#define U8SZ (sizeof(uint8_t)) +#define U16SZ (sizeof(uint16_t)) + +/** + * Creates a socket with the specified domain, type, a nought protocol, + * and which is either bound to the specified host name (which should be NULL) + * and service name (if do_bind is true and flags has AI_PASSIVE) or which is + * connected to the specified host name and service name (if do_bind is false). + * The call to getaddrinfo has flags AI_NUMERICHOST and AI_NUMERICSERV + * set, in order to disable DNS; additionnal flags are then passed with it. + * If do_bind, it is important to note that the socket will NOT listen + * for incoming connections: it is up to the caller to activate it. + */ +int create_socket(const char *host, const char *serv, int domain, int type, int flags, int do_bind); + +/** + * Creates a socket with the specified domain (AF_INET or AF_INET6), + * type (either SOCK_STREAM or SOCK_SEQPACKET), a nought protocol, + * and which is listening for incoming connections on port specified in serv. + * This calls create_socket, and thus requires serv to be numeric. + */ +int create_tcp_server_socket(const char *serv, int domain, int type); + +/** + * Creates a socket with the specified domain (AF_INET or AF_INET6), + * type (either SOCK_STREAM or SOCK_SEQPACKET), a nought protocol, + * and connected to the specified address and port (host and serv). + * This calls create_socket, and thus requires host and serv to be numeric. + */ +int create_tcp_client_socket(const char *host, const char *serv, int domain, int type); + +/** + * Creates a UDP socket with the specified domain (AF_INET or AF_INET6), + * a nought protocol, and which accepts UDP packets on port specified in serv. + * This calls create_socket, and thus requires serv to be numeric. + */ +int create_udp_server_socket(const char *serv, int domain); + +/** + * Creates a UDP socket with the specified domain (AF_INET or AF_INET6), a zero + * protocol, and connected to the specified address and port (host and serv). + * This calls create_socket, and thus requires host and serv to be numeric. + */ +int create_udp_client_socket(const char *host, const char *serv, int domain); + +#define RECV_CHUNK 1 +#define SEND_CHUNK 2 + +/** + * A chunk of data to be recv or send by the server/client. + * type can be + * - RECV_CHUNK, in which case the server/client will try to receive it + * and will compare the received bytes with the provided data + * (no byte order conversion are done!) + * - SEND_CHUNK, in which case the server/client will send to the remote end + * the data, and will report any transmission error. + * Warning, the chunks should have the network byte order, as the server/client + * will not try to reverse the bytes. + */ +struct cs_network_chunk { + void *data; + size_t data_length; + int type; +}; + +/** + * A transaction, which is a list of chunks exchanged between the server/client + * and the remote end. + * A transaction is successful if all RECV_CHUNK chunks have been received + * correctly, and if the server/client hasn't been asked to quit. + */ +struct cs_network_transaction { + struct cs_network_chunk *chunks; + size_t nchunks; +}; + +/** + * A list of transactions to be tested one after another, using one instance + * of the server/client. + */ +struct cs_network_transactions { + struct cs_network_transaction *transactions; + size_t ntransactions; +}; + +/** + * Launches a TCP-listening and accepting server, on port specified by serv, + * in a separate process. In order to communicate with the caller, + * server_in is filled with the writing end of a pipe transfering toward + * the server (for example, stop condition), server_out is filled with + * the reading end of a pipe written by the server with the results of + * the requested transactions, and spid is filled with the process pid, + * so that the caller can wait on it. + * Returns 0 on successful launch of the server, 1 otherwise. + * The server exits with code 3 if it receives something on server_in, + * with code 2 if there was an error, and with 1 if it couldn't create the socket. + */ +int launch_test_tcp_server(struct cs_network_transactions *transactions, const char *serv, int domain, int *server_in, int *server_out, int *spid); + +/** + * Launches a TCP client, connecting and sending to host host on port serv, + * in a separate process. To communicate with the caller, client_in is filled + * with the writing end of a pipe transfering toward the client (for example, + * stop condition), client_out is filled with the reading end of a pipe written + * by the client with the results of the requested transactions, and cpid + * is filled with the process pid, so that the caller can wait on it. + * Returns 0 on successful launch of the client, 1 otherwise. + */ +int launch_test_tcp_client(struct cs_network_transactions *transactions, const char *host, const char *serv, int domain, int *client_in, int *client_out, int *cpid); + +/** + * Launches a UDP server, accepting messages on port specified by serv, + * in a separate process. To communicate with the caller, server_in is filled + * with the writing end of a pipe transfering toward the server (for example, + * stop condition), server_out is filled with the reading end of a pipe + * written by the server with the results of the requested transactions, + * and spid is filled with the process pid, so that the caller can wait on it. + * Returns 0 on successful launch of the server, 1 otherwise. + */ +int launch_test_udp_server(struct cs_network_transactions *transactions, const char *serv, int domain, int *server_in, int *server_out, int *spid); + +/** + * Launches a UDP client, sending messages to host host on port serv, + * in a separate process. To communicate with the caller, client_in is filled + * with the writing end of a pipe transfering toward the client (for example, + * stop condition), client_out is filled with the reading end of a pipe written + * by the client with the results of the requested transactions, and cpid + * is filled with the process pid, so that the caller can wait on it. + * Returns 0 on successful launch of the server, 1 otherwise. + */ +int launch_test_udp_client(struct cs_network_transactions *transactions, const char *host, const char *serv, int domain, int *client_in, int *client_out, int *cpid); + +#endif // __CTESTER_UTIL_SOCKETS_H__ + From b279f35b0738ca4fd871a9a3b969db80cd4244a5 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Tue, 18 Sep 2018 10:16:42 +0200 Subject: [PATCH 23/53] Renaming (well, copying) test_network_dns ro test_network_gai_simple_socket --- ci/run_ci | 29 ++- .../expected_results.txt | 1 + .../student_code.c | 215 ++++++++++++++++++ .../student_code.h | 31 +++ ci/test_network_gai_simple_socket/tests.c | 185 +++++++++++++++ 5 files changed, 452 insertions(+), 9 deletions(-) create mode 100644 ci/test_network_gai_simple_socket/expected_results.txt create mode 100644 ci/test_network_gai_simple_socket/student_code.c create mode 100644 ci/test_network_gai_simple_socket/student_code.h create mode 100644 ci/test_network_gai_simple_socket/tests.c diff --git a/ci/run_ci b/ci/run_ci index 7326f87..db35e65 100755 --- a/ci/run_ci +++ b/ci/run_ci @@ -1,7 +1,11 @@ #!/bin/bash -declare -a tests=("test-simple-fail" "test-simple-success" "test_network_dns") +shopt -s nullglob +declare -a tests=(*/) +shopt -u nullglob cd "$(dirname "$0")" +echo $tests +echo "${#tests[@]}" exec_test() { mkdir env/ @@ -11,22 +15,22 @@ exec_test() { pushd env make - echo "### $1: executing ..." + echo -e "##### $1: executing ...\n" ./tests if [ -f ./results.txt ]; then cmp --silent results.txt expected_results.txt if [ $? -eq 0 ]; then - echo '###' $1 ': OK' + echo -e "\n#####" $1 ": OK" pushd rm -rf env return 1 else - echo '###' $1 ': results.txt diverges from the expected output:' + echo -e "\n#####" $1 ": results.txt diverges from the expected output:" diff results.txt expected_results.txt fi else - echo '###' $1 ': CTester did not create a results.txt file' + echo -e "\n#####" $1 ": CTester did not create a results.txt file" fi popd @@ -34,21 +38,28 @@ exec_test() { return 0 } +if ((${#tests[@]} == 0)); then + echo -e "No tests found!" >&2 + exit 1 +fi -echo '### Executing tests' +echo -e "##### Executing tests" tests_ok=0 for i in "${tests[@]}" do - echo '##########' $i + echo -e "\n####################" $i exec_test $i if [ $? -eq '1' ]; then tests_ok=$((tests_ok+1)) fi - + echo -e "####################" done -echo '###' $tests_ok '/' ${#tests[@]} 'tests succeeded' +echo -e "\n####################" +echo -e "### End of tests ###" +echo -e "####################" +echo -e "#####" $tests_ok "/" ${#tests[@]} "tests succeeded" if ((tests_ok != ${#tests[@]})); then exit 1 else diff --git a/ci/test_network_gai_simple_socket/expected_results.txt b/ci/test_network_gai_simple_socket/expected_results.txt new file mode 100644 index 0000000..9637098 --- /dev/null +++ b/ci/test_network_gai_simple_socket/expected_results.txt @@ -0,0 +1 @@ +wrapper_stats#SUCCESS#Tests that the wrapper functions correctly remembers the stats#1# diff --git a/ci/test_network_gai_simple_socket/student_code.c b/ci/test_network_gai_simple_socket/student_code.c new file mode 100644 index 0000000..948b53a --- /dev/null +++ b/ci/test_network_gai_simple_socket/student_code.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "student_code.h" + +/** + * This is the simple example from the BeeJ guide. + */ +void run_student_tests_wrapper_stats_1(struct f1_stats *stats) +{ + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + int s = getaddrinfo("inginious.info.ucl.ac.be", "443", &hints, &rep); + if (s != 0) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + stats->getaddrinfo_success = true; + int sfd = -1; + for (rp = rep; rp != NULL; rp = rp->ai_next) { + stats->nb_addr_tried++; + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) { + perror("socket"); + continue; + } + stats->nb_connect++; + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + perror("connect"); + close(sfd); + sfd = -1; + } + freeaddrinfo(rep); + if (sfd != -1) { + char coucou[] = "coucou"; + ssize_t nsent; + do { + nsent = send(sfd, coucou, sizeof(coucou), 0); + stats->nb_send++; + if (nsent != -1) { + stats->nb_bytes += nsent; + } else { + perror("send"); + } + if (nsent != sizeof(coucou)) { + break; + } + } while (nsent != -1 && stats->nb_bytes < 42); + } +} + +/* + * The following two codes are adapted from the Linux man pages + * for getaddrinfo + */ +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = 0; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("socket"); + continue; + } + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nconnect++; + break; + } + perror("connect"); + stats->nconnect++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + char *words[3]; + words[0] = "coucou"; + words[1] = "salut"; + words[2] = "STOP"; + for (int i = 0; i < 3; i++) { + // Send the three messages + int len = strlen(words[i]) + 1; + // Let's use a struct msghdr to see if it works + struct msghdr msg; + msg.msg_name = NULL; + msg.msg_namelen = 0; + struct iovec msg_iov[1]; + msg_iov[0].iov_base = words[i]; + msg_iov[0].iov_len = len; + msg.msg_iov = msg_iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ssize_t nsent = sendmsg(sfd, &msg, 0); + if (nsent == -1) + perror("send"); + stats->nsend++; + if (nsent != len) { + fprintf(stderr, "ACouldn't write\n"); + continue; + } + // Let's read the data again + memset(buf, 0, sizeof(buf)); + nread = recv(sfd, buf, 500, 0); + stats->nrecv++; + if (nread == -1) { + perror("recv"); + continue; + } + if (strcmp(buf, words[i]) != 0) { + fprintf(stderr, "AMessage don't match\n"); + continue; + } + } +} + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + struct sockaddr_storage peer_addr; + socklen_t peer_addrlen; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_PASSIVE; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("Bsocket"); + continue; + } + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nbind++; + break; + } + perror("Bbind"); + stats->nbind++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + for (int failures = 0; failures < 10;) { + memset(buf, 0, sizeof(buf)); + // Stops at the reception of "stop" + peer_addrlen = sizeof(peer_addr); + nread = recvfrom(sfd, buf, 500, 0, (struct sockaddr*) &peer_addr, &peer_addrlen); + stats->nrecvfrom++; + if (nread == -1) + perror("recvfrom"); + if (nread == -1) { + failures++; + continue; + } + // Let's skip the printing here + ssize_t nsent = sendto(sfd, buf, nread, 0, (struct sockaddr*) &peer_addr, peer_addrlen); + stats->nsendto++; + if (nsent != nread) { + failures++; + } + if (nsent == -1) { + perror("Bsent"); + } + if (strcmp(buf, "STOP") == 0) { + break; + } + } +} + diff --git a/ci/test_network_gai_simple_socket/student_code.h b/ci/test_network_gai_simple_socket/student_code.h new file mode 100644 index 0000000..342c2d1 --- /dev/null +++ b/ci/test_network_gai_simple_socket/student_code.h @@ -0,0 +1,31 @@ +#include +struct f1_stats { + bool getaddrinfo_success; + int nb_addr_tried; + int nb_connect; + int nb_send; + ssize_t nb_bytes; +}; +struct stats2_server { + int ngai; + int nsocket; + int nbind; + int nrecvfrom; + int nsendto; +}; +struct stats2_client { + int ngai; + int nsocket; + int nconnect; + int nsend; + int nrecv; +}; + +void run_student_tests_wrapper_stats_1(struct f1_stats *stats); + +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats); + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats); + +// void run_student_tests_wrapper_stats_3(); + diff --git a/ci/test_network_gai_simple_socket/tests.c b/ci/test_network_gai_simple_socket/tests.c new file mode 100644 index 0000000..554b389 --- /dev/null +++ b/ci/test_network_gai_simple_socket/tests.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +#include "CTester/CTester.h" +#include "student_code.h" + +void __real_exit(int status); // Needed as otherwise we'll get a segfault + +void test_wrapper_stats() +{ + // Let's enable the stats for all functions + set_test_metadata("wrapper_stats", _("Tests that the wrapper functions correctly remembers the stats"), 1); + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, 0); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, 0); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, 0); + CU_ASSERT_EQUAL(stats.socket.called, 0); + + CU_ASSERT_EQUAL(stats.accept.last_return, 0); + CU_ASSERT_EQUAL(stats.bind.last_return, 0); + CU_ASSERT_EQUAL(stats.connect.last_return, 0); + CU_ASSERT_EQUAL(stats.listen.last_return, 0); + CU_ASSERT_EQUAL(stats.recv.last_return, 0); + CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); + CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.send.last_return, 0); + CU_ASSERT_EQUAL(stats.sendto.last_return, 0); + CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.socket.last_return, 0); + + monitored.getaddrinfo = monitored.freeaddrinfo = true; + monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = true; + MONITOR_ALL_RECV(monitored, true); + MONITOR_ALL_SEND(monitored, true); + CU_ASSERT_EQUAL(monitored.recv, true); + CU_ASSERT_EQUAL(monitored.recvfrom, true); + CU_ASSERT_EQUAL(monitored.recvmsg, true); + CU_ASSERT_EQUAL(monitored.send, true); + CU_ASSERT_EQUAL(monitored.sendto, true); + CU_ASSERT_EQUAL(monitored.sendmsg, true); + + // wrap_monitoring is set to true inside the sandbox, and set to false at the end of it. + struct f1_stats stats1; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_1(&stats1); + SANDBOX_END; + + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, stats1.nb_connect); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, stats1.nb_send); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, stats1.nb_send); + CU_ASSERT_EQUAL(stats.socket.called, stats1.nb_addr_tried); + + reinit_stats_network_dns(); + reinit_network_socket_stats(); // Simpler for the next calls + + int pid; + pid = fork(); + if (pid == 0) { + struct stats2_server statss; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_server(&statss); + SANDBOX_END; + // I must exit to let the other process terminate. No test here ! + __real_exit(0); + } else if (pid != -1) { + struct stats2_client statss; + memset(&statss, 0, sizeof(statss)); + sleep(1); // Strangely extremely important FIXME + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_client(&statss); + SANDBOX_END; + int status = 0; + waitpid(pid, &status, 0); + // Let's have a look at the statistics for the client + CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); + CU_ASSERT_EQUAL(stats.connect.called, statss.nconnect); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, (statss.ngai > 0 && statss.nsocket > 0)); + CU_ASSERT_EQUAL(stats.sendmsg.called, statss.nsend); + CU_ASSERT_EQUAL(stats.send_all.called, statss.nsend); + CU_ASSERT_EQUAL(stats.recv.called, statss.nrecv); + CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecv); + } else { + CU_FAIL(); + } + + reinit_stats_network_dns(); + reinit_network_socket_stats(); + + pid = fork(); + if (pid == 0) { + struct stats2_client statss; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_client(&statss); + SANDBOX_END; + // I must exit to let the other process terminate. No test here ! + __real_exit(0); + } else if (pid != -1) { + struct stats2_server statss; + memset(&statss, 0, sizeof(statss)); + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_server(&statss); + SANDBOX_END; + int status = 0; + waitpid(pid, &status, 0); + // Let's have a look at the statistics for the server + CU_ASSERT_EQUAL(stats.bind.called, statss.nbind); + CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); + CU_ASSERT_EQUAL(stats.sendto.called, statss.nsendto); + CU_ASSERT_EQUAL(stats.recvfrom.called, statss.nrecvfrom); + CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecvfrom); + CU_ASSERT_EQUAL(stats.send_all.called, statss.nsendto); + } else { + CU_FAIL(); + } + + monitored.getaddrinfo = monitored.freeaddrinfo = false; + monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = false; + MONITOR_ALL_RECV(monitored, false); + MONITOR_ALL_SEND(monitored, false); + CU_ASSERT_EQUAL(monitored.recv, false); + CU_ASSERT_EQUAL(monitored.recvfrom, false); + CU_ASSERT_EQUAL(monitored.recvmsg, false); + CU_ASSERT_EQUAL(monitored.send, false); + CU_ASSERT_EQUAL(monitored.sendto, false); + CU_ASSERT_EQUAL(monitored.sendmsg, false); + + reinit_stats_network_dns(); + reinit_network_socket_stats(); + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, 0); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, 0); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, 0); + CU_ASSERT_EQUAL(stats.socket.called, 0); + + CU_ASSERT_EQUAL(stats.accept.last_return, 0); + CU_ASSERT_EQUAL(stats.bind.last_return, 0); + CU_ASSERT_EQUAL(stats.connect.last_return, 0); + CU_ASSERT_EQUAL(stats.listen.last_return, 0); + CU_ASSERT_EQUAL(stats.recv.last_return, 0); + CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); + CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.send.last_return, 0); + CU_ASSERT_EQUAL(stats.sendto.last_return, 0); + CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.socket.last_return, 0); +} + +// Check failures: +// Recall we must use the failures struct, and assign its (call), (call)_ret and (call)_errno fields. +// And we have the fileds FAIL_ALWAYS, FAIL_NEVER, FAIL_FIRST, FAIL_SECOND, FAIL_THIRD and FAIL_TWICE + +int main(int argc, char **argv) +{ + RUN(test_wrapper_stats); +} + From f2c46e88de0b39a4e44a3a23d691df3fc4303f28 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Tue, 18 Sep 2018 10:54:43 +0200 Subject: [PATCH 24/53] Fix broken ci --- ci/run_ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run_ci b/ci/run_ci index db35e65..9383767 100755 --- a/ci/run_ci +++ b/ci/run_ci @@ -1,9 +1,9 @@ #!/bin/bash +cd "$(dirname "$0")" shopt -s nullglob declare -a tests=(*/) shopt -u nullglob -cd "$(dirname "$0")" echo $tests echo "${#tests[@]}" From 2b7d4d920c9eeb12e2221b115a692311977c2c54 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Tue, 18 Sep 2018 13:39:21 +0200 Subject: [PATCH 25/53] Add UDP support to util_sockets and fix a few errors --- ci/run_ci | 2 - student/CTester/read_write.c | 2 + student/CTester/util_sockets.c | 287 ++++++++++++++++++++++++++++----- student/CTester/util_sockets.h | 15 +- 4 files changed, 259 insertions(+), 47 deletions(-) diff --git a/ci/run_ci b/ci/run_ci index 9383767..267629b 100755 --- a/ci/run_ci +++ b/ci/run_ci @@ -4,8 +4,6 @@ cd "$(dirname "$0")" shopt -s nullglob declare -a tests=(*/) shopt -u nullglob -echo $tests -echo "${#tests[@]}" exec_test() { mkdir env/ diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index ea81f46..0ffe1af 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -339,5 +339,7 @@ int set_write_buffer(int fd, const struct write_buffer_t *buf) { // TODO implement this functions and the related functions. // Currently not used so this is not a problem + (void) fd; + (void) buf; return 0; } diff --git a/student/CTester/util_sockets.c b/student/CTester/util_sockets.c index 824ddf4..24afdc2 100644 --- a/student/CTester/util_sockets.c +++ b/student/CTester/util_sockets.c @@ -10,13 +10,16 @@ #include "util_sockets.h" +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + const uint8_t _OK = 0; const uint8_t _TOO_MUCH = 1; const uint8_t _TOO_FEW = 2; -const uint8_t _NOT_SAME = 4; -const uint8_t _NOTHING_RECV = 8; -const uint8_t _RECV_ERROR = 16; -const uint8_t _SEND_ERROR = 32; +const uint8_t _RECV_ERROR = 3; +const uint8_t _NOTHING_RECV = 4; +const uint8_t _SEND_ERROR = 8; +const uint8_t _NOT_SAME_DATA = 16; +const uint8_t _NOT_SAME_ADDR = 32; const uint8_t _EXIT_PROCESS = 64; const uint8_t _EXTEND_MSG = 128; @@ -41,14 +44,6 @@ int create_socket(const char *host, const char *serv, int domain, int type, int return -1; } if (do_bind) { - /*int yes = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) { - perror("setsockopt"); - CU_FAIL("Coucou"); - close(fd); - freeaddrinfo(rep); - return -1; - }*/ if (bind(fd, rep->ai_addr, rep->ai_addrlen)) { perror("bind"); close(fd); @@ -140,8 +135,9 @@ if (pid == -1) { \ * 2 if too few data recv'd * 3 if too much data recv'd * 4 if different data + * Writes of errors left to calling function, except for 2, 3. */ -int wait_recv_tcp(int sfd, int pipe_in, int pipe_out, const struct cs_network_chunk *chunk) +int wait_recv_tcp(int sockfd, int pipe_in, int pipe_out, const struct cs_network_chunk *chunk) { size_t required_bytes = chunk->data_length, recv_bytes = 0, total_bytes = required_bytes + 1; ssize_t r = -1; @@ -153,7 +149,7 @@ int wait_recv_tcp(int sfd, int pipe_in, int pipe_out, const struct cs_network_ch nfds_t nfds = 2; struct pollfd fds[2]; fds[0] = (struct pollfd) { - .fd = sfd, + .fd = sockfd, .events = POLLIN | POLLERR, .revents = 0 }; @@ -168,7 +164,7 @@ int wait_recv_tcp(int sfd, int pipe_in, int pipe_out, const struct cs_network_ch perror("poll"); free(buf); return -3; - } + } // p != -1 if (fds[1].revents) { // Got something on pipe if (fds[1].revents & POLLIN) { @@ -178,13 +174,13 @@ int wait_recv_tcp(int sfd, int pipe_in, int pipe_out, const struct cs_network_ch } else { // Error: quit free(buf); - return -4; + return -2; } - } + } // not fds[1], so fds[0] if (fds[0].revents) { // Got something on socket if (fds[0].revents & POLLIN) { - r = recv(sfd, (buf + recv_bytes), (total_bytes - recv_bytes), 0); + r = recv(sockfd, (buf + recv_bytes), (total_bytes - recv_bytes), 0); if (r == -1) { // Error condition on socket perror("recv"); @@ -198,12 +194,8 @@ int wait_recv_tcp(int sfd, int pipe_in, int pipe_out, const struct cs_network_ch return -1; } } + // r != -1 and no error } while (recv_bytes <= required_bytes && r != 0); - if (r == -1) { - // recv error : normally we shouldn't get here - free(buf); - return -1; - } if (recv_bytes < required_bytes) { free(buf); write(pipe_out, &_TOO_FEW, U8SZ); @@ -231,15 +223,16 @@ int wait_recv_tcp(int sfd, int pipe_in, int pipe_out, const struct cs_network_ch * -1 if send got a problem * 0 if ok * 1 if pipe_in got a message + * Writes of error left to calling function, except for -1. */ -int wait_send_tcp(int sfd, int pipe_in, const struct cs_network_chunk *chunk) +int wait_send_tcp(int sockfd, int pipe_in, int pipe_out, const struct cs_network_chunk *chunk) { size_t required_bytes = chunk->data_length, sent_bytes = 0; ssize_t s = -1; nfds_t nfds = 2; struct pollfd fds[2]; fds[0] = (struct pollfd) { - .fd = sfd, + .fd = sockfd, .events = POLLOUT | POLLERR, .revents = 0 }; @@ -263,19 +256,180 @@ int wait_send_tcp(int sfd, int pipe_in, const struct cs_network_chunk *chunk) } if (fds[0].revents) { if (fds[0].revents & POLLOUT) { - s = send(sfd, (chunk->data + sent_bytes), (required_bytes - sent_bytes), 0); + s = send(sockfd, (chunk->data + sent_bytes), (required_bytes - sent_bytes), 0); if (s == -1) { perror("send"); + write(pipe_out, &_SEND_ERROR, U8SZ); return -1; } sent_bytes += s; } else { + write(pipe_out, &_SEND_ERROR, U8SZ); return -1; } } } while (sent_bytes != required_bytes && s != -1); - if (s == -1) { - // Normally we shouldn't get here + if (sent_bytes != required_bytes) { + uint8_t err = _TOO_FEW | SEND_ERROR; + write(pipe_out, &err, U8SZ); + return -1; + } + return 0; +} + +/** + * Returns 0 if correct, + * -3 if syscall problem + * -2 if pipe_in got a problem, + * -1 if recv error + * 1 if pipe_in got a message + * 2 if too few data recv'd + * 3 if too much data recv'd + * 4 if different data + * 5 if different address + * Writes of errors left to calling function, except for 2, 3, 4, 5. + */ +int wait_recv_udp(int sockfd, int pipe_in, int pipe_out, const struct cs_network_chunk *chunk, const struct sockaddr *addr, socklen_t addrlen) +{ + size_t required_bytes = chunk->data_length, total_bytes = required_bytes + 1, recv_bytes; + struct sockaddr_storage recv_addr; + socklen_t recv_addrlen = sizeof(addr); + void *buf = malloc(total_bytes); + if (!buf) { + perror("malloc"); + return -3; + } + nfds_t nfds = 2; + struct pollfd fds[2]; + fds[0] = (struct pollfd) { + .fd = sockfd, + .events = POLLIN | POLLERR, + .revents = 0 + }; + fds[1] = (struct pollfd) { + .fd = pipe_in, + .events = POLLIN | POLLERR, + .revents = 0 + }; + int p = poll(fds, nfds, -1); + if (p == -1) { + perror("poll"); + free(buf); + return -3; + } + if (fds[1].revents) { + // pipe_in + if (fds[1].revents & POLLIN) { + // Read: quit with 1 + free(buf); + return 1; + } else { + // Error: quit with error + free(buf); + return -2; + } + } + if (fds[0].revents) { + // sockfd + if (fds[0].revents & POLLIN) { + // read socket + ssize_t r = recvfrom(sockfd, buf, total_bytes, 0, (struct sockaddr*)&recv_addr, &recv_addrlen); + if (r == -1) { + perror("recvfrom"); + free(buf); + return -1; + } + recv_bytes = r; + } else { + free(buf); + return -1; + } + } + if (recv_bytes < required_bytes) { + free(buf); + write(pipe_out, &_TOO_FEW, U8SZ); + write(pipe_out, &recv_bytes, sizeof(recv_bytes)); + return 2; + } + if (recv_bytes > required_bytes) { + free(buf); + write(pipe_out, &_TOO_MUCH, U8SZ); + write(pipe_out, &recv_bytes, sizeof(recv_bytes)); + return 3; + } + int c = memcmp(buf, chunk->data, chunk->data_length); + free(buf); + if (c) { + write(pipe_out, &_NOT_SAME_DATA, U8SZ); + return 4; + } + if (addr != NULL) { + c = memcmp(&addr, &recv_addr, MIN(recv_addrlen, addrlen)); + if (c) { + write(pipe_out, &_NOT_SAME_ADDR, U8SZ); + write(pipe_out, &recv_addrlen, sizeof(recv_addrlen)); + write(pipe_out, &recv_addr, recv_addrlen); + return 5; + } + } + return 0; +} + +/** + * Returns + * -3 if syscall problem + * -2 if pipe_in got a problem + * -1 if send got a problem + * 0 if ok + * 1 if pipe_in got a message + * Writes of errors left to caller function, except for -1 (send). + */ +int wait_send_udp(int sockfd, int pipe_in, int pipe_out, const struct cs_network_chunk *chunk, const struct sockaddr *addr, socklen_t addrlen) +{ + size_t required_bytes = chunk->data_length, sent_bytes = 0; + nfds_t nfds = 2; + struct pollfd fds[2]; + fds[0] = (struct pollfd) { + .fd = sockfd, + .events = POLLOUT | POLLERR, + .revents = 0 + }; + fds[1] = (struct pollfd) { + .fd = pipe_in, + .events = POLLIN | POLLERR, + .revents = 0 + }; + int p = poll(fds, nfds, -1); + if (p == -1) { + perror("poll"); + return -3; + } + if (fds[1].revents) { + // pipe_in + if (fds[1].revents & POLLIN) { + return 1; + } else { + return -2; + } + } + if (fds[0].revents) { + // sockfd + if (fds[0].revents & POLLOUT) { + ssize_t s = sendto(sockfd, chunk->data, required_bytes, 0, addr, addrlen); + if (s == -1) { + perror("sendto"); + write(pipe_out, &_SEND_ERROR, U8SZ); + return -1; + } + sent_bytes = s; + } else { + write(pipe_out, &_SEND_ERROR, U8SZ); + return -1; + } + } + if (sent_bytes != required_bytes) { + uint8_t err = _TOO_FEW | _SEND_ERROR; + write(pipe_out, &err, U8SZ); return -1; } return 0; @@ -284,13 +438,13 @@ int wait_send_tcp(int sfd, int pipe_in, const struct cs_network_chunk *chunk) /** * Return 3 if process was asked to stop, 2 if error, 0 on success. */ -int handle_tcp_transaction(int sfd, const struct cs_network_transaction *transaction, int pipe_in, int pipe_out) +int handle_tcp_transaction(int sockfd, const struct cs_network_transaction *transaction, int pipe_in, int pipe_out) { for (unsigned j = 0; j < transaction->nchunks; j++) { struct cs_network_chunk *curchunk = &(transaction->chunks[j]); if (curchunk->type == RECV_CHUNK) { // recv - int r = wait_recv_tcp(sfd, pipe_in, pipe_out, curchunk); + int r = wait_recv_tcp(sockfd, pipe_in, pipe_out, curchunk); if (r < -1) { fprintf(stderr, "Server error\n"); return 2; @@ -312,7 +466,7 @@ int handle_tcp_transaction(int sfd, const struct cs_network_transaction *transac continue; case 4: // Different data - write(pipe_out, &_NOT_SAME, U8SZ); + write(pipe_out, &_NOT_SAME_DATA, U8SZ); //write(pipe_out, &(res->expected_request_length), sizeof(res->expected_request_length)); // So that the reader can skip the data without knowing its length in advance //write(pipe_out, buf, res->expected_request_length); @@ -320,15 +474,13 @@ int handle_tcp_transaction(int sfd, const struct cs_network_transaction *transac } } else if (curchunk->type == SEND_CHUNK) { // send - int s = wait_send_tcp(sfd, pipe_in, curchunk); + int s = wait_send_tcp(sockfd, pipe_in, pipe_out, curchunk); if (s < -1) { fprintf(stderr, "Server error\n"); return 2; } if (s == -1) { // Send error - write(pipe_out, &_SEND_ERROR, U8SZ); - write(pipe_out, &errno, sizeof(errno)); continue; } if (s == 1) { @@ -336,22 +488,57 @@ int handle_tcp_transaction(int sfd, const struct cs_network_transaction *transac } } // end if type } // end for chunks in transaction - write(pipe_out, &_OK, U8SZ); // correct transaction + write(pipe_out, &_OK, U8SZ); return 0; } -int handle_udp_transaction(int sfd, const struct cs_network_transaction *transaction, int pipe_in, int pipe_out) +int handle_udp_transaction(int sockfd, const struct cs_network_transaction *transaction, int pipe_in, int pipe_out) { for (unsigned int j = 0; j < transaction->nchunks; j++) { struct cs_network_chunk *curchunk = &(transaction->chunks[j]); if (curchunk->type == RECV_CHUNK) { // recvfrom + int r = wait_recv_udp(sockfd, pipe_in, pipe_out, curchunk, transaction->addr, transaction->addrlen); + if (r < -1) { + fprintf(stderr, "Server error\n"); + return 2; + } + switch (r) { + case -1: + //recv error + write(pipe_out, &_RECV_ERROR, U8SZ); + write(pipe_out, &errno, sizeof(errno)); + continue; + case 1: + // pipe_in in + return 3; + // Else: handled by wait_recv_udp + case 2: // too few + continue; + case 3: // too much + continue; + case 4: // data + continue; + case 5: // addr + continue; + } // TODO complete } else if (curchunk->type == SEND_CHUNK) { // sendto - // TODO complete - } - } + int r = wait_send_udp(sockfd, pipe_in, pipe_out, curchunk, transaction->addr, transaction->addrlen); + if (r < -1) { + fprintf(stderr, "Server error\n"); + return 2; + } + switch (r) { + case -1: // send error + continue; + case 1: // pipe_in in + return 3; + } + } // end if type + } // end for chunks in transaction + write(pipe_out, &_OK, U8SZ); return 0; } @@ -453,7 +640,16 @@ int launch_test_udp_server(struct cs_network_transactions *transactions, const c for (unsigned i = 0; i < transactions->ntransactions; i++) { struct cs_network_transaction *curtrans = &(transactions->transactions[i]); int h = handle_udp_transaction(sfd, curtrans, c2s[0], s2c[1]); - // TODO complete + switch (h) { + case 0: + break; + case 2: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(2); + case 3: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(3); + } } exit(0); PIPE_AND_FORK_MIDDLE(); @@ -476,7 +672,16 @@ int launch_test_udp_client(struct cs_network_transactions *transactions, const c for (unsigned i = 0; i < transactions->ntransactions; i++) { struct cs_network_transaction *curtrans = &(transactions->transactions[i]); int h = handle_udp_transaction(cfd, curtrans, c2s[0], s2c[1]); - // TODO complete + switch (h) { + case 0: + break; + case 2: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(2); + case 3: + write(s2c[1], &_EXIT_PROCESS, U8SZ); + exit(3); + } } exit(0); PIPE_AND_FORK_MIDDLE(); diff --git a/student/CTester/util_sockets.h b/student/CTester/util_sockets.h index d6faed0..c14f6d6 100644 --- a/student/CTester/util_sockets.h +++ b/student/CTester/util_sockets.h @@ -8,15 +8,20 @@ * Currenly, they may be combined with any of them; * maybe we should exclude some combination. */ +// Self conflicting constants #define OK 0 #define TOO_MUCH 1 // And the amount should follow #define TOO_FEW 2 // And the amount should follow -#define NOT_SAME 4 // And the recvd tab should follow -#define NOTHING_RECV 8 -#define RECV_ERROR 16 -#define SEND_ERROR 32 +#define RECV_ERROR 3 // Followed by value of errno. +#define NOTHING_RECV 4 +// Left : 6-15 +// Or-ing constants +#define SEND_ERROR 8 // To indicate the real type of error +#define NOT_SAME_DATA 16 // Idealy, the recv'd tab should follow, preceded by its length, but not yet implemented +#define NOT_SAME_ADDR 32 // The recv'd address should follow, preceded by its length (socklen_t) #define EXIT_PROCESS 64 // The child process exits: beware of SIGPIPE! #define EXTEND_MSG 128 // Next octet gives the status + #define U8SZ (sizeof(uint8_t)) #define U16SZ (sizeof(uint16_t)) @@ -91,6 +96,8 @@ struct cs_network_chunk { struct cs_network_transaction { struct cs_network_chunk *chunks; size_t nchunks; + struct sockaddr *addr; + socklen_t addrlen; }; /** From 368598484f1b4416c0712da5ab718af0627aec05 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Tue, 18 Sep 2018 18:07:21 +0200 Subject: [PATCH 26/53] Add tests, clean up read_write, and fix a few bugs - Remove write-related functions from read_write (currently not needed). - Add to run_ci the possibility of executing a single test. - A few corrections in read_write (time.h, NULL, const etc). - Allow exit to be called normally when the sandbox is not running. --- ci/run_ci | 23 +- ci/test_network_recv/expected_results.txt | 4 + ci/test_network_recv/tests.c | 252 ++++++++++++++++++++++ student/CTester/CTester.c | 8 +- student/CTester/read_write.c | 26 +-- student/CTester/read_write.h | 5 +- 6 files changed, 285 insertions(+), 33 deletions(-) create mode 100644 ci/test_network_recv/expected_results.txt create mode 100644 ci/test_network_recv/tests.c diff --git a/ci/run_ci b/ci/run_ci index 267629b..e106434 100755 --- a/ci/run_ci +++ b/ci/run_ci @@ -1,10 +1,5 @@ #!/bin/bash -cd "$(dirname "$0")" -shopt -s nullglob -declare -a tests=(*/) -shopt -u nullglob - exec_test() { mkdir env/ cp $1/* env/ @@ -36,6 +31,24 @@ exec_test() { return 0 } +cd "$(dirname "$0")" + +if [ "$#" -eq "1" ]; then + # Execute only the specified test + echo -e "##### Executing test" $1 + exec_test $1 + if [ $? -eq '1' ]; then + echo -e "##### Successful test" + exit 0 + else + exit 1 + fi +fi + +shopt -s nullglob +declare -a tests=(*/) +shopt -u nullglob + if ((${#tests[@]} == 0)); then echo -e "No tests found!" >&2 exit 1 diff --git a/ci/test_network_recv/expected_results.txt b/ci/test_network_recv/expected_results.txt new file mode 100644 index 0000000..e0b0e17 --- /dev/null +++ b/ci/test_network_recv/expected_results.txt @@ -0,0 +1,4 @@ +fragmented_recv#SUCCESS#Tests the use of a fragmented recv#1# +fragmented_recv_before#SUCCESS#Tests the use of before-intervals#1# +fragmented_recv_after#SUCCESS#Tests the use of after-intervals#1# +fragmented_recv_realtime#SUCCESS#Tests the use of real-time-intervals#1# diff --git a/ci/test_network_recv/tests.c b/ci/test_network_recv/tests.c new file mode 100644 index 0000000..20eb12a --- /dev/null +++ b/ci/test_network_recv/tests.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include + +#include "CTester/CTester.h" +#include "CTester/read_write.h" + +#define BILLION (1000*1000*1000) + +void __real_exit(int status); // Needed as otherwise we'll get a segfault + +struct read_fd_table_t { + size_t n; + struct read_item *items; +}; +struct read_item { + int fd; + const struct read_buffer_t *buf; + unsigned int chunk_id; + size_t bytes_read; + struct timespec last_time; + int64_t interval; +}; + +bool timespec_is_between(const struct timespec *a, const struct timespec *min, const struct timespec *max) +{ + int64_t mint = min->tv_sec; + mint *= BILLION; + mint += min->tv_nsec; + int64_t at = a->tv_sec; + at *= BILLION; + at += a->tv_nsec; + int64_t maxt = max->tv_sec; + maxt *= BILLION; + maxt += max->tv_nsec; + return (mint <= at && at <= maxt); +} + +extern struct read_fd_table_t read_fd_table; + +struct read_item; + +extern bool fd_is_read_buffered(int fd); + +extern ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags); + +void test_fragmented_recv_before() +{ + set_test_metadata("fragmented_recv_before", _("Tests the use of before-intervals"), 1); + // TODO +} + +void test_fragmented_recv_after() +{ + set_test_metadata("fragmented_recv_after", _("Tests the use of after-intervals"), 1); + // TODO +} + +void test_fragmented_recv_realtime() +{ + set_test_metadata("fragmented_recv_realtime", _("Tests the use of real-time-intervals"), 1); + // TODO, big thing +} + +void test_fragmented_recv() +{ + set_test_metadata("fragmented_recv", _("Tests the use of a fragmented recv"), 1); + MONITOR_ALL_RECV(monitored, true); + reinit_network_socket_stats(); + reinit_read_fd_table(); + CU_ASSERT_EQUAL(read_fd_table.n, 0); // "read_fd_table is not empty" + CU_ASSERT_EQUAL(read_fd_table.items, NULL); // "read_fd_table is not empty" + size_t tab1len = 1000; + char *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Couldn't allocate memory"); + for (int i = 0; i < 1000; i++) { + tab1[i] = (char)i; + } + struct read_buffer_t rbuf1, rbuf2, rbuf3; + rbuf1.mode = READ_WRITE_BEFORE_INTERVAL; + rbuf1.nchunks = 4; + rbuf1.chunks = malloc(rbuf1.nchunks * sizeof(struct read_bufchunk_t)); + if (!rbuf1.chunks) { + free(tab1); + CU_FAIL_FATAL("Couldn't allocate memory"); + } + rbuf1.chunks[0] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = 200, + .interval = 2000 + }; + rbuf1.chunks[1] = (struct read_bufchunk_t) { + .buf = (tab1 + 200), + .buflen = 200, + .interval = 3000 + }; + rbuf1.chunks[2] = (struct read_bufchunk_t) { + .buf = (tab1 + 200 + 200), + .buflen = 350, + .interval = 2000 + }; + rbuf1.chunks[3] = (struct read_bufchunk_t) { + .buf = (tab1 + 200 + 200 + 350), + .buflen = 250, + .interval = 2500 + }; + rbuf2.mode = READ_WRITE_AFTER_INTERVAL; + rbuf2.nchunks = 1; + rbuf2.chunks = malloc(rbuf2.nchunks * sizeof(struct read_bufchunk_t)); + if (!rbuf2.chunks) { + free(rbuf1.chunks); + free(tab1); + CU_FAIL_FATAL("Couldn't allocate memory"); + } + rbuf2.chunks[0] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = tab1len, + .interval = 0 + }; + rbuf3.mode = READ_WRITE_REAL_INTERVAL; + rbuf3.nchunks = 2; + rbuf3.chunks = malloc(rbuf3.nchunks * sizeof(struct read_bufchunk_t)); + if (!rbuf3.chunks) { + free(rbuf2.chunks); + free(rbuf1.chunks); + free(tab1); + CU_FAIL_FATAL("Couldn't allocate memory"); + } + rbuf3.chunks[0] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = tab1len, + .interval = 4242 + }; + rbuf3.chunks[1] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = 0, + .interval = 0 + }; + int fd1 = socket(AF_INET, SOCK_STREAM, 0); + if (fd1 == -1) { + free(tab1); + free(rbuf1.chunks); + CU_FAIL_FATAL("Couldn't allocate memory"); + } + CU_ASSERT_EQUAL(set_read_data(fd1, &rbuf1), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 1); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + CU_ASSERT_EQUAL(set_read_data(fd1 + 1, NULL), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 1); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_EQUAL(set_read_data(fd1, NULL), 1); + CU_ASSERT_EQUAL(read_fd_table.n, 0); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + + struct timespec beforets1, afterts1, beforets2, afterts2, beforets3, afterts3; + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets1), 0); + CU_ASSERT_EQUAL_FATAL(set_read_data(fd1, &rbuf1), 0); + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &afterts1), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 1); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + + CU_ASSERT_EQUAL(set_read_data(fd1 + 1, &rbuf1), 0); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_EQUAL(read_fd_table.n, 2); + CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); + CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, (fd1+1)); + + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets3), 0); + CU_ASSERT_EQUAL(set_read_data(fd1 + 2, &rbuf3), 0); + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &afterts3), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 3); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 2)); + CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); + CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, (fd1+1)); + CU_ASSERT_EQUAL(read_fd_table.items[2].buf, &rbuf3); + CU_ASSERT_EQUAL(read_fd_table.items[2].fd, (fd1+2)); + + CU_ASSERT_EQUAL(set_read_data(fd1 + 1, &rbuf2), 1); + CU_ASSERT_EQUAL(read_fd_table.n, 3); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_EQUAL(set_read_data(fd1 + 1, NULL), 1); + CU_ASSERT_EQUAL(read_fd_table.n, 2); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_EQUAL(set_read_data(fd1 + 1, NULL), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 2); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &beforets2), 0); + CU_ASSERT_EQUAL(set_read_data(fd1 + 1, &rbuf2), 0); + CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &afterts2), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 3); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 2)); + + CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); + CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[0].chunk_id, 0); + CU_ASSERT_EQUAL(read_fd_table.items[0].bytes_read, 0); + CU_ASSERT_EQUAL(read_fd_table.items[0].interval, 0); + CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[0].last_time), &beforets1, &afterts1)); + + CU_ASSERT_EQUAL(read_fd_table.items[2].fd, fd1 + 1); + CU_ASSERT_EQUAL(read_fd_table.items[2].buf, &rbuf2); + CU_ASSERT_EQUAL(read_fd_table.items[2].chunk_id, 0); + CU_ASSERT_EQUAL(read_fd_table.items[2].bytes_read, 0); + CU_ASSERT_EQUAL(read_fd_table.items[2].interval, 0); + CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[2].last_time), &beforets2, &afterts2)); + + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd1 + 2); + CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf3); + CU_ASSERT_EQUAL(read_fd_table.items[1].chunk_id, 0); + CU_ASSERT_EQUAL(read_fd_table.items[1].bytes_read, 0); + CU_ASSERT_EQUAL(read_fd_table.items[1].interval, rbuf3.chunks[0].interval); + CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[1].last_time), &beforets3, &afterts3)); + CU_ASSERT_EQUAL(set_read_data(fd1 + 2, NULL), 1); // We don't need it + CU_ASSERT_EQUAL(read_fd_table.n, 2); + // Correctly set up of the tests: done + // Now, all we have to do is recv the data :-) + reinit_read_fd_table(); + CU_ASSERT_EQUAL(read_fd_table.items, NULL); + CU_ASSERT_EQUAL(read_fd_table.n, 0); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 2)); + free(tab1); + free(rbuf1.chunks); + free(rbuf2.chunks); + free(rbuf3.chunks); +} + +// Check failures: +// Recall we must use the failures struct, and assign its (call), (call)_ret and (call)_errno fields. +// And we have the fileds FAIL_ALWAYS, FAIL_NEVER, FAIL_FIRST, FAIL_SECOND, FAIL_THIRD and FAIL_TWICE + +int main(int argc, char **argv) +{ + RUN( + test_fragmented_recv, + test_fragmented_recv_before, + test_fragmented_recv_after, + test_fragmented_recv_realtime + ); +} + diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index d908cfc..14f0b55 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -260,8 +260,12 @@ void __real_exit(int status); * If we accept the possibility of a student calling exit, then it is better * to explicitly raise the segfault in order to have a less-undefined behaviour. */ -void __wrap_exit(int status) { - (void)status; +void __wrap_exit(int status) +{ + if (!(wrap_monitoring)) { + __real_exit(status); + } // else : monitored and in sandbox + raise(SIGSEGV); // raise SIGSEGV if it comes from the student. // TODO add code so that the student's code can actually exit } diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index 0ffe1af..604e244 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -68,20 +69,6 @@ struct read_fd_table_t { struct read_item *items; } read_fd_table; -struct write_item { - int fd; // the file descriptor this structure applies to. - const struct write_buffer_t *buf; // Provided write_buffer_t structure - unsigned int chunk_id; // Current chunk, or next chunk to be received - size_t bytes_written; // Number of bytes of the current chunk already written - struct timespec last_time; // Time of the end of the last call of write on this fd/socket - int64_t interval; // In real-time mode (READ_WRITE_REAL_INTERVAL), read wait interval for the current chunk. -}; - -struct write_fd_table_t { - size_t n; - struct write_item *items; -} write_fd_table; - struct read_item *read_get_entry(int fd) { for (unsigned int i = 0; i < read_fd_table.n; i++) { @@ -117,7 +104,7 @@ int read_remove_entry(int fd) found = true; if (i < read_fd_table.n - 1) { struct read_item *dest = &(read_fd_table.items[i]); - memmove(dest, dest + 1, read_fd_table.n - i - 1); + memmove(dest, dest + 1, (read_fd_table.n - i - 1) * sizeof(struct read_item)); memset(&(read_fd_table.items[read_fd_table.n-1]), 0, sizeof(struct read_item)); // This is not really a problem that we have a useless thing at the end } @@ -282,6 +269,7 @@ void reinit_read_fd_table() { // As we're not responsible to clean up all the recv_buffer_t, we can just free everything up free(read_fd_table.items); + read_fd_table.items = NULL; read_fd_table.n = 0; } @@ -335,11 +323,3 @@ int set_read_data(int fd, const struct read_buffer_t *buf) return (already_there ? 1 : 0); } -int set_write_buffer(int fd, const struct write_buffer_t *buf) -{ - // TODO implement this functions and the related functions. - // Currently not used so this is not a problem - (void) fd; - (void) buf; - return 0; -} diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h index 7bd4e9e..345e4cf 100644 --- a/student/CTester/read_write.h +++ b/student/CTester/read_write.h @@ -19,7 +19,6 @@ #include #include -#include /** * Functions and structures for manipulating read and write system calls, @@ -146,7 +145,7 @@ struct read_bufchunk_t { struct read_buffer_t { int mode; // The mode of interpretation of interval size_t nchunks; // Number of chunks - const struct read_bufchunk_t *chunks; // Table of chunks + struct read_bufchunk_t *chunks; // Table of chunks }; /** @@ -192,7 +191,7 @@ struct write_buffer_t { * Currently, it does nothing other than returning zero; functionnalities * will be added when needed. */ -int set_write_buffer(int fd, const struct write_buffer_t *buf); +//int set_write_buffer(int fd, const struct write_buffer_t *buf); /** * Removes all the association between fd's and read_buffer_t's that have been From 027afe5c1abc6faef5b17056e66b5f29edf8eb6b Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Thu, 20 Sep 2018 17:18:46 +0200 Subject: [PATCH 27/53] Add more unit test, a few features and fix bugs detected by these Among these were incoherence between nanoseconds, microseconds and milliseconds, bad type for intervals, and something else. Functionnalities added include the possibility to create a read_buffer_t from the tables of offsets and intervals, when the data is contigous. --- ci/run_ci | 10 +- ci/test_network_recv/tests.c | 796 +++++++++++++++++++++++++++++++++-- student/CTester/read_write.c | 68 ++- student/CTester/read_write.h | 38 +- 4 files changed, 858 insertions(+), 54 deletions(-) diff --git a/ci/run_ci b/ci/run_ci index e106434..8d00c55 100755 --- a/ci/run_ci +++ b/ci/run_ci @@ -1,5 +1,9 @@ #!/bin/bash +_term() { + kill -TERM "$child" 2>/dev/null +} + exec_test() { mkdir env/ cp $1/* env/ @@ -9,7 +13,9 @@ exec_test() { make echo -e "##### $1: executing ...\n" - ./tests + ./tests & + child=$! + wait "$child" if [ -f ./results.txt ]; then cmp --silent results.txt expected_results.txt @@ -33,6 +39,8 @@ exec_test() { cd "$(dirname "$0")" +trap _term SIGTERM + if [ "$#" -eq "1" ]; then # Execute only the specified test echo -e "##### Executing test" $1 diff --git a/ci/test_network_recv/tests.c b/ci/test_network_recv/tests.c index 20eb12a..14c3549 100644 --- a/ci/test_network_recv/tests.c +++ b/ci/test_network_recv/tests.c @@ -2,12 +2,25 @@ #include #include #include +#include +#include #include "CTester/CTester.h" #include "CTester/read_write.h" #define BILLION (1000*1000*1000) +#define TOL_INF_D 0.3 +#define TOL_SUP_D 2.0 +#define TOL_SUP_A (1000*1000) +#define TOL_INF_A 0 + +#define TIMESPEC_ZERO(ts) \ + struct timespec ts; \ + memset(&ts, 0, sizeof(ts)); +#define TS_ZERO(ts) \ + ts.tv_sec = 0; ts.tv_nsec = 0; + void __real_exit(int status); // Needed as otherwise we'll get a segfault struct read_fd_table_t { @@ -45,26 +58,30 @@ extern bool fd_is_read_buffered(int fd); extern ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags); -void test_fragmented_recv_before() -{ - set_test_metadata("fragmented_recv_before", _("Tests the use of before-intervals"), 1); - // TODO -} +extern int64_t get_time_interval(const struct timespec *pasttime, const struct timespec *curtime); -void test_fragmented_recv_after() -{ - set_test_metadata("fragmented_recv_after", _("Tests the use of after-intervals"), 1); - // TODO -} +extern void getnanotime(struct timespec *res); -void test_fragmented_recv_realtime() +bool is_quasi_equal(int64_t a, int64_t b) { - set_test_metadata("fragmented_recv_realtime", _("Tests the use of real-time-intervals"), 1); - // TODO, big thing + double aa = a, bb = b; + if (a != 0 && b != 0) { + double cc = aa/bb; + return (TOL_INF_D <= cc && cc <= TOL_SUP_D); + } else { + if (a == 0) { + return (TOL_INF_A <= b && b <= TOL_SUP_A); + } else { + return (TOL_INF_A <= a && a <= TOL_SUP_A); + } + } } +extern bool wrap_monitoring; + void test_fragmented_recv() { + int64_t MILLION = 1000*1000; set_test_metadata("fragmented_recv", _("Tests the use of a fragmented recv"), 1); MONITOR_ALL_RECV(monitored, true); reinit_network_socket_stats(); @@ -138,23 +155,18 @@ void test_fragmented_recv() .buflen = 0, .interval = 0 }; - int fd1 = socket(AF_INET, SOCK_STREAM, 0); - if (fd1 == -1) { - free(tab1); - free(rbuf1.chunks); - CU_FAIL_FATAL("Couldn't allocate memory"); - } + int fd1 = 17, fd2 = 42, fd3 = 0; CU_ASSERT_EQUAL(set_read_data(fd1, &rbuf1), 0); CU_ASSERT_EQUAL(read_fd_table.n, 1); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); - CU_ASSERT_EQUAL(set_read_data(fd1 + 1, NULL), 0); + CU_ASSERT_EQUAL(set_read_data(fd2, NULL), 0); CU_ASSERT_EQUAL(read_fd_table.n, 1); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); - CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); CU_ASSERT_EQUAL(set_read_data(fd1, NULL), 1); CU_ASSERT_EQUAL(read_fd_table.n, 0); CU_ASSERT_FALSE(fd_is_read_buffered(fd1)); - CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); struct timespec beforets1, afterts1, beforets2, afterts2, beforets3, afterts3; CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets1), 0); @@ -163,42 +175,42 @@ void test_fragmented_recv() CU_ASSERT_EQUAL(read_fd_table.n, 1); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); - CU_ASSERT_EQUAL(set_read_data(fd1 + 1, &rbuf1), 0); - CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_EQUAL(set_read_data(fd2, &rbuf1), 0); + CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); CU_ASSERT_EQUAL(read_fd_table.n, 2); CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf1); - CU_ASSERT_EQUAL(read_fd_table.items[1].fd, (fd1+1)); + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd2); CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets3), 0); - CU_ASSERT_EQUAL(set_read_data(fd1 + 2, &rbuf3), 0); + CU_ASSERT_EQUAL(set_read_data(fd3, &rbuf3), 0); CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &afterts3), 0); CU_ASSERT_EQUAL(read_fd_table.n, 3); - CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 2)); + CU_ASSERT_TRUE(fd_is_read_buffered(fd3)); CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf1); - CU_ASSERT_EQUAL(read_fd_table.items[1].fd, (fd1+1)); + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd2); CU_ASSERT_EQUAL(read_fd_table.items[2].buf, &rbuf3); - CU_ASSERT_EQUAL(read_fd_table.items[2].fd, (fd1+2)); + CU_ASSERT_EQUAL(read_fd_table.items[2].fd, fd3); - CU_ASSERT_EQUAL(set_read_data(fd1 + 1, &rbuf2), 1); + CU_ASSERT_EQUAL(set_read_data(fd2, &rbuf2), 1); CU_ASSERT_EQUAL(read_fd_table.n, 3); - CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 1)); - CU_ASSERT_EQUAL(set_read_data(fd1 + 1, NULL), 1); + CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); + CU_ASSERT_EQUAL(set_read_data(fd2, NULL), 1); CU_ASSERT_EQUAL(read_fd_table.n, 2); - CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); - CU_ASSERT_EQUAL(set_read_data(fd1 + 1, NULL), 0); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); + CU_ASSERT_EQUAL(set_read_data(fd2, NULL), 0); CU_ASSERT_EQUAL(read_fd_table.n, 2); - CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &beforets2), 0); - CU_ASSERT_EQUAL(set_read_data(fd1 + 1, &rbuf2), 0); + CU_ASSERT_EQUAL(set_read_data(fd2, &rbuf2), 0); CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &afterts2), 0); CU_ASSERT_EQUAL(read_fd_table.n, 3); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); - CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 1)); - CU_ASSERT_TRUE(fd_is_read_buffered(fd1 + 2)); + CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); + CU_ASSERT_TRUE(fd_is_read_buffered(fd3)); CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); @@ -207,20 +219,21 @@ void test_fragmented_recv() CU_ASSERT_EQUAL(read_fd_table.items[0].interval, 0); CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[0].last_time), &beforets1, &afterts1)); - CU_ASSERT_EQUAL(read_fd_table.items[2].fd, fd1 + 1); + CU_ASSERT_EQUAL(read_fd_table.items[2].fd, fd2); CU_ASSERT_EQUAL(read_fd_table.items[2].buf, &rbuf2); CU_ASSERT_EQUAL(read_fd_table.items[2].chunk_id, 0); CU_ASSERT_EQUAL(read_fd_table.items[2].bytes_read, 0); CU_ASSERT_EQUAL(read_fd_table.items[2].interval, 0); CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[2].last_time), &beforets2, &afterts2)); - CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd1 + 2); + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd3); CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf3); CU_ASSERT_EQUAL(read_fd_table.items[1].chunk_id, 0); CU_ASSERT_EQUAL(read_fd_table.items[1].bytes_read, 0); - CU_ASSERT_EQUAL(read_fd_table.items[1].interval, rbuf3.chunks[0].interval); + fprintf(stderr, "%lld %lld\n", (long long)read_fd_table.items[1].interval, (long long)rbuf3.chunks[0].interval); + CU_ASSERT_EQUAL(read_fd_table.items[1].interval, rbuf3.chunks[0].interval * MILLION); CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[1].last_time), &beforets3, &afterts3)); - CU_ASSERT_EQUAL(set_read_data(fd1 + 2, NULL), 1); // We don't need it + CU_ASSERT_EQUAL(set_read_data(fd3, NULL), 1); // We don't need it CU_ASSERT_EQUAL(read_fd_table.n, 2); // Correctly set up of the tests: done // Now, all we have to do is recv the data :-) @@ -228,14 +241,709 @@ void test_fragmented_recv() CU_ASSERT_EQUAL(read_fd_table.items, NULL); CU_ASSERT_EQUAL(read_fd_table.n, 0); CU_ASSERT_FALSE(fd_is_read_buffered(fd1)); - CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 1)); - CU_ASSERT_FALSE(fd_is_read_buffered(fd1 + 2)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd3)); free(tab1); free(rbuf1.chunks); free(rbuf2.chunks); free(rbuf3.chunks); } +void test_fragmented_recv_before() +{ + //int64_t MILLION = 1000*1000; + set_test_metadata("fragmented_recv_before", _("Tests the use of before-intervals"), 1); + MONITOR_ALL_RECV(monitored, true); + reinit_network_socket_stats(); + reinit_read_fd_table(); + int mode = READ_WRITE_BEFORE_INTERVAL; + size_t tab1len = 1000 * sizeof(int); + int *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Memory"); + for (int i = 0; i < 1000; i++) { + tab1[i] = i; + } + off_t offsets1[] = {200 * sizeof(int), 350 * sizeof(int), 445, 250 * sizeof(int), 200 * sizeof(int) - 445}; + int intervals1[] = {20, 25, 10, 15, 20}; + struct read_buffer_t rbuf1; + rbuf1.mode = mode; + if (create_partial_read_buffer(tab1, 5, offsets1, intervals1, &rbuf1)) { + free(tab1); + CU_FAIL_FATAL("Memory"); + } + int fd1 = 42; + int r = set_read_data(fd1, &rbuf1); + if (r < 0) { + free(tab1); + free_partial_read_buffer(&rbuf1); + CU_FAIL_FATAL("Memory"); + } + CU_ASSERT_EQUAL(r, 0); + TIMESPEC_ZERO(ts1); + TIMESPEC_ZERO(ts2); + TIMESPEC_ZERO(ts3); + TIMESPEC_ZERO(ts4); + TIMESPEC_ZERO(ts5); + TIMESPEC_ZERO(ts6); + TIMESPEC_ZERO(ts7); + TIMESPEC_ZERO(ts8); + TIMESPEC_ZERO(ts9); + TIMESPEC_ZERO(ts10); + ssize_t read1 = 0, read2 = 0, read3 = 0, read4 = 0, read5 = 0, read6 = 0, read7 = 0, read8 = 0, read9 = 0, cumread = 0; + int buf1[1010]; + void *buf = buf1; + SANDBOX_BEGIN; + getnanotime(&ts1); + read1 = recv(fd1, buf, 250 * sizeof(int), 0); // Should wait 20msec and return 200*4 bytes, next chunk + cumread += read1; + fprintf(stderr, "1 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts2); // difference with 1 should be 20msec + read2 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Should wait 25msec and return (50+200)*4 bytes + cumread += read2; + fprintf(stderr, "2 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts3); // difference with 2 should be 25msec + read3 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Shouldn't wait and return 100*4 bytes, next chunk + cumread += read3; + fprintf(stderr, "3 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts4); // difference with 3 should be small + read4 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Should wait about 10msec and return 445 bytes, next chunk + cumread += read4; + fprintf(stderr, "4 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts5); // difference with 4 should be 10msec + struct timespec tss1 = (struct timespec) {.tv_sec = 0, .tv_nsec = 20*1000*1000}; // Shouldn't have any impact + nanosleep(&tss1, NULL); + getnanotime(&ts6); // difference with 5 should be 20msec + read5 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Should wait about 15msec and return 250*4 bytes, next chunk + cumread += read5; + fprintf(stderr, "5 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts7); // difference with 6 should be 15msec + read6 = recv(fd1, (buf + cumread), 50 * sizeof(int), 0); // Should wait about 20msec and return 50*4 bytes + cumread += read6; + fprintf(stderr, "6 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts8); // difference with 7 should be 20msec + read7 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Shouldn't wait and return (200*4-50*4-445) = 155 bytes, end + cumread += read7; + fprintf(stderr, "7 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts9); // difference with 8 should be 0msec + read8 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Shouldn't wait and return 0 bytes, end + cumread += read8; + fprintf(stderr, "8 %d %p\n", (int)cumread, (buf + cumread)); + read9 = recv(fd1, NULL, 250 * sizeof(int), 0); // Shouldn't wait, not read the buffer, and return 0 bytes, end + getnanotime(&ts10); + SANDBOX_END; + CU_ASSERT_EQUAL(read1, 200*sizeof(int)); + CU_ASSERT_EQUAL(read2, (50+200)*sizeof(int)); + CU_ASSERT_EQUAL(read3, 100*sizeof(int)); + CU_ASSERT_EQUAL(read4, 445); + CU_ASSERT_EQUAL(read5, 250*sizeof(int)); + CU_ASSERT_EQUAL(read6, 50*sizeof(int)); + CU_ASSERT_EQUAL(read7, ((200-50)*sizeof(int)-445)); + CU_ASSERT_EQUAL(read8, 0); + CU_ASSERT_EQUAL(read9, 0); + CU_ASSERT_EQUAL(cumread, 1000 * sizeof(int)); + int64_t diff1 = get_time_interval(&ts1, &ts2), + diff2 = get_time_interval(&ts2, &ts3), + diff3 = get_time_interval(&ts3, &ts4), + diff4 = get_time_interval(&ts4, &ts5), + diff5 = get_time_interval(&ts5, &ts6), + diff6 = get_time_interval(&ts6, &ts7), + diff7 = get_time_interval(&ts7, &ts8), + diff8 = get_time_interval(&ts8, &ts9), + diff9 = get_time_interval(&ts9, &ts10); + fprintf(stderr, "%d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read7, (int)read8, (int)read9); + fprintf(stderr, "%ld %ld %ld %ld %ld %ld %ld %ld %ld\n", (long)diff1, (long)diff2, (long)diff3, (long)diff4, (long)diff5, (long)diff6, (long)diff7, (long)diff8, (long)diff9); + /* + * TODO should be used to check that the wait times are correct, + * but it is extremely machine-dependent. + CU_ASSERT_TRUE(is_quasi_equal(diff1, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff2, 25*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff3, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff4, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff5, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff6, 15*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff7, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff8, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff9, 0*MILLION));*/ + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(buf1[i], i); + } + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(tab1[i], i); + } + free_partial_read_buffer(&rbuf1); + free(tab1); +} + +void test_fragmented_recv_after() +{ + set_test_metadata("fragmented_recv_after", _("Tests the use of after-intervals"), 1); + int64_t MILLION = 1000*1000; + MONITOR_ALL_RECV(monitored, true); + reinit_network_socket_stats(); + reinit_read_fd_table(); + int mode = READ_WRITE_AFTER_INTERVAL; + size_t tab1len = 1000 * sizeof(int); + int *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Memory"); + for (int i = 0; i < 1000; i++) + tab1[i] = i; + off_t offsets1[] = {250 * sizeof(int), 150 * sizeof(int), 150 * sizeof(int), 200 * sizeof(int), 150 * sizeof(int), 100 * sizeof(int)}; + int intervals1[] = {20, 20, 20, 20, 20, 20}; + struct read_buffer_t rbuf1; + rbuf1.mode = mode; + if (create_partial_read_buffer(tab1, 6, offsets1, intervals1, &rbuf1)) { + free(tab1); + CU_FAIL_FATAL("Memory"); + } + int fd1 = 42; + int r = set_read_data(fd1, &rbuf1); + if (r < 0) { + free(tab1); + free_partial_read_buffer(&rbuf1); + CU_FAIL_FATAL("Memory"); + } + CU_ASSERT_EQUAL(r, 0); + TIMESPEC_ZERO(ts1); + TIMESPEC_ZERO(ts2); + TIMESPEC_ZERO(ts3); + TIMESPEC_ZERO(ts4); + TIMESPEC_ZERO(ts5); + TIMESPEC_ZERO(ts6); + TIMESPEC_ZERO(ts7); + TIMESPEC_ZERO(ts8); + TIMESPEC_ZERO(ts9); + TIMESPEC_ZERO(ts10); + TIMESPEC_ZERO(ts11); + ssize_t read1 = 0, read2 = 0, read3 = 0, read4 = 0, read5 = 0, read6 = 0, read7 = 0, read8 = 0, read9 = 0, cumread = 0; + int errno5 = 0; + errno = 0; + int buf1[1010]; + void *buf = buf1; + SANDBOX_BEGIN; + getnanotime(&ts1); + read1 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait 20, return 200 + cumread += read1; + getnanotime(&ts2); + read2 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // No wait, return 50 + cumread += read2; + getnanotime(&ts3); + read3 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait 20, return 150 + cumread += read3; + getnanotime(&ts4); + struct timespec tss1 = (struct timespec) { + .tv_sec = 0, + .tv_nsec = 20*MILLION + }; + nanosleep(&tss1, NULL); + getnanotime(&ts5); + read4 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // No wait, return 150 + cumread += read4; + getnanotime(&ts6); + read5 = recv(fd1, (buf + cumread), 200 * sizeof(int), MSG_DONTWAIT); // No wait, return -1, errno = EAGAIN | EWOULDBLOCK + errno5 = errno; + getnanotime(&ts7); + read6 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait, return 200 + cumread += read6; + getnanotime(&ts8); + tss1.tv_nsec = 20*MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts9); + read7 = recv(fd1, (buf + cumread), 200 * sizeof(int), MSG_DONTWAIT); // No wait, return 150 + cumread += read7; + getnanotime(&ts10); + read8 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait, return 100 + cumread += read8; + getnanotime(&ts11); + read9 = recv(fd1, (buf + cumread), 42, 0); // No wait, return 0 + SANDBOX_END; + CU_ASSERT_EQUAL(read1, 200*sizeof(int)); + CU_ASSERT_EQUAL(read2, 50*sizeof(int)); + CU_ASSERT_EQUAL(read3, 150*sizeof(int)); + CU_ASSERT_EQUAL(read4, 150*sizeof(int)); + CU_ASSERT_EQUAL(read5, -1); + CU_ASSERT_TRUE(errno5 == EAGAIN || errno == EWOULDBLOCK); + CU_ASSERT_EQUAL(read6, 200*sizeof(int)); + CU_ASSERT_EQUAL(read7, 150*sizeof(int)); + CU_ASSERT_EQUAL(read8, 100*sizeof(int)); + CU_ASSERT_EQUAL(read9, 0); + CU_ASSERT_EQUAL(cumread, 1000*sizeof(int)); + fprintf(stderr, "%d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read7, (int)read8, (int)read9); + int64_t diff1 = get_time_interval(&ts1, &ts2); // 20msec + int64_t diff2 = get_time_interval(&ts2, &ts3); // 0 + int64_t diff3 = get_time_interval(&ts3, &ts4); // 20 + int64_t diff5 = get_time_interval(&ts5, &ts6); // 0 + int64_t diff6 = get_time_interval(&ts6, &ts7); // 0 + int64_t diff7 = get_time_interval(&ts7, &ts8); // 20 + int64_t diff9 = get_time_interval(&ts9, &ts10); // 0 + int64_t diff10 = get_time_interval(&ts10, &ts11); // 20 + fprintf(stderr, "%ld %ld %ld %ld %ld %ld %ld %ld\n", (long)diff1, (long)diff2, (long)diff3, (long)diff5, (long)diff6, (long)diff7, (long)diff9, (long)diff10); + /* + * TODO Should be used to check wait time, + * but is too much machine-dependent. + CU_ASSERT_TRUE(is_quasi_equal(diff1, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff2, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff3, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff5, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff6, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff7, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff9, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff10, 20*MILLION));*/ + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(buf1[i], i); + CU_ASSERT_EQUAL(tab1[i], i); + } + free_partial_read_buffer(&rbuf1); + free(tab1); +} + +void test_fragmented_recv_realtime() +{ + set_test_metadata("fragmented_recv_realtime", _("Tests the use of real-time-intervals"), 1); + int64_t MILLION = 1000*1000; + MONITOR_ALL_RECV(monitored, true); + monitored.read = true; // Otherwise it won't work + reinit_network_socket_stats(); + reinit_read_fd_table(); + int mode = READ_WRITE_REAL_INTERVAL; + size_t tab1len = 1000 * sizeof(int); + int *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Memory"); + for (int i = 0; i < 1000; i++) + tab1[i] = i; + off_t offsets1[] = {200*sizeof(int), 200*sizeof(int), 200*sizeof(int), 200*sizeof(int), 200*sizeof(int)}; + int intervals1[] = {2*100, 5*100, 1*100, 1*100, 3*100}; + struct read_buffer_t *rbuf1 = create_read_buffer(tab1, 5, offsets1, intervals1, mode); + if (!rbuf1) { + free(tab1); + CU_FAIL_FATAL("Memory"); + } + int fd1 = 42; + int r = set_read_data(fd1, rbuf1); + if (r < 0) { + free(tab1); + free_read_buffer(rbuf1); + CU_FAIL_FATAL("Memory"); + } + CU_ASSERT_EQUAL(r, 0); + TIMESPEC_ZERO(tss1); + TIMESPEC_ZERO(ts0); + TIMESPEC_ZERO(ts1); + TIMESPEC_ZERO(ts2); + TIMESPEC_ZERO(ts3); + TIMESPEC_ZERO(ts4); + TIMESPEC_ZERO(ts5); + TIMESPEC_ZERO(ts6); + TIMESPEC_ZERO(ts7); + TIMESPEC_ZERO(ts8); + TIMESPEC_ZERO(ts9); + TIMESPEC_ZERO(ts10); + TIMESPEC_ZERO(ts11); + TIMESPEC_ZERO(ts12); + TIMESPEC_ZERO(ts13); + TIMESPEC_ZERO(ts14); + TIMESPEC_ZERO(ts15); + ssize_t read1 = 0, read2 = 0, read3 = 0, read4 = 0, read5 = 0, read6 = 0, read7 = 0, read8 = 0, read9 = 0, cumread = 0; + int errno3 = 0; + errno = 0; + int buf1[1100]; + memset(buf1, 0, sizeof(buf1)); + void *buf = buf1; + SANDBOX_BEGIN; + getnanotime(&ts0); + + tss1.tv_nsec = 300 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts1); + + read1 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // No wait, return 100 + cumread += read1; + getnanotime(&ts2); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts3); + + read2 = read(fd1, (buf+cumread), 150*sizeof(int)); // No wait, return 100 + cumread += read2; + getnanotime(&ts4); + + read3 = recv(fd1, (buf+cumread), 50*sizeof(int), MSG_DONTWAIT); // No wait, return -1, errno + errno3 = errno; + getnanotime(&ts5); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts6); + + read4 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // Wait 10, return 50 + cumread += read4; + getnanotime(&ts7); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts8); + + read5 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // No wait, return 100 + cumread += read5; + getnanotime(&ts9); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts10); + + read6 = read(fd1, (buf+cumread), 350*sizeof(int)); // No wait, return 350 + cumread += read6; // 700 + getnanotime(&ts11); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts12); + + read7 = recv(fd1, (buf+cumread), 200*sizeof(int), MSG_DONTWAIT); // No wait, return 200 + cumread += read7; + getnanotime(&ts13); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts14); + + read8 = read(fd1, (buf+cumread), 200*sizeof(int)); // No wait, return 100 + cumread += read8; + getnanotime(&ts15); + + read9 = read(fd1, (buf+cumread), 200*sizeof(int)); // No wait, return 0 + SANDBOX_END; + fprintf(stderr, "first test, reads: %d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read7, (int)read8, (int)read9); + CU_ASSERT_EQUAL(read1, 100*sizeof(int)); + CU_ASSERT_EQUAL(read2, 100*sizeof(int)); + CU_ASSERT_EQUAL(read3, -1); + CU_ASSERT_TRUE(errno3 == EAGAIN || EWOULDBLOCK); + CU_ASSERT_EQUAL(read4, 50*sizeof(int)); + CU_ASSERT_EQUAL(read5, 100*sizeof(int)); + CU_ASSERT_EQUAL(read6, 350*sizeof(int)); + CU_ASSERT_EQUAL(read7, 200*sizeof(int)); + CU_ASSERT_EQUAL(read8, 100*sizeof(int)); + CU_ASSERT_EQUAL(read9, 0); + int64_t diff1 = get_time_interval(&ts1, &ts2); // 0 + int64_t diff2 = get_time_interval(&ts3, &ts4); // 0 + int64_t diff3 = get_time_interval(&ts4, &ts5); // 0 + int64_t diff4 = get_time_interval(&ts6, &ts7); // 10 + int64_t diff5 = get_time_interval(&ts8, &ts9); // 0 + int64_t diff6 = get_time_interval(&ts10, &ts11); // 0 + int64_t diff7 = get_time_interval(&ts12, &ts13); // 0 + int64_t diff8 = get_time_interval(&ts14, &ts15); // 0 + int64_t diff11 = get_time_interval(&ts2, &ts3); // 40 + int64_t diff31 = get_time_interval(&ts5, &ts6); // 10 + int64_t diff41 = get_time_interval(&ts7, &ts8); // 20 + int64_t diff51 = get_time_interval(&ts9, &ts10); // 20 + int64_t diff61 = get_time_interval(&ts11, &ts12); // 20 + int64_t diff71 = get_time_interval(&ts13, &ts14); // 20 + fprintf(stderr, "first test, diff1 %ld %ld %ld %ld diff31 %ld %ld %ld %ld diff51 %ld %ld %ld %ld %ld diff8 %ld\n", diff1, diff11, diff2, diff3, diff31, diff4, diff41, diff5, diff51, diff6, diff61, diff7, diff71, diff8); + /* + * TODO Should be used to check wait time, + * but is too much machine-dependent. + CU_ASSERT_TRUE(diff1 <= MILLION); + CU_ASSERT_TRUE(diff2 <= MILLION); + CU_ASSERT_TRUE(diff3 <= MILLION); + CU_ASSERT_TRUE(diff4 <= 10*MILLION); + CU_ASSERT_TRUE(diff5 <= MILLION); + CU_ASSERT_TRUE(diff6 <= MILLION); + CU_ASSERT_TRUE(diff7 <= MILLION); + CU_ASSERT_TRUE(diff8 <= MILLION);*/ + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(buf1[i], i); + CU_ASSERT_EQUAL(tab1[i], i); + } + for (int i = 1000; i < 1100; i++) + CU_ASSERT_EQUAL(buf1[i], 0); + free_partial_read_buffer(rbuf1); + // End of test 1 + // Begin of test 2 + off_t offsets2[] = {400*sizeof(int), 400*sizeof(int), 200*sizeof(int)}; + int intervals2[] = {2*100, 4*100, 5*100}; + if (create_partial_read_buffer(tab1, 3, offsets2, intervals2, rbuf1)) { + free(tab1); + free(rbuf1); + } + if (!fd_is_read_buffered(fd1)) { + free(tab1); + free_read_buffer(rbuf1); + CU_FAIL_FATAL("Memory"); + } + r = set_read_data(fd1, rbuf1); // Again + CU_ASSERT_EQUAL(r, 1); + TS_ZERO(ts0); + TS_ZERO(ts1); + TS_ZERO(ts2); + TS_ZERO(ts3); + TS_ZERO(ts4); + TS_ZERO(ts5); + TS_ZERO(ts6); + TS_ZERO(ts7); + TS_ZERO(ts8); + TS_ZERO(ts9); + TS_ZERO(ts10); + TS_ZERO(ts11); + TS_ZERO(ts12); + TS_ZERO(ts13); + TS_ZERO(ts14); + TS_ZERO(ts15); + TIMESPEC_ZERO(ts16); + TIMESPEC_ZERO(ts17); + TIMESPEC_ZERO(ts18); + TIMESPEC_ZERO(ts19); + TIMESPEC_ZERO(ts20); + TS_ZERO(tss1); + read1 = 0; read2 = 0; read3 = 0; read4 = 0; read5 = 0; read6 = 0; read7 = 0, read8 = 0, read9 = 0, cumread = 0; + ssize_t read10 = 0, read11 = 0; + memset(buf1, 0, sizeof(buf1)); + SANDBOX_BEGIN; + getnanotime(&ts0); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts1); + + read1 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 100, wait 100 + cumread += read1; + getnanotime(&ts2); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts3); + + read2 = recv(fd1, (buf+cumread), 200*sizeof(int), 0); // 200, wait 0 + cumread += read2; + getnanotime(&ts4); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts5); + + read3 = recv(fd1, (buf+cumread), 200*sizeof(int), 0); // 100, wait 0 + cumread += read3; + getnanotime(&ts6); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts7); + + read4 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 100, wait 100 + cumread += read4; + getnanotime(&ts8); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts9); + + read5 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 150, wait 0 + cumread += read5; + getnanotime(&ts10); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts11); + + read6 = read(fd1, (buf+cumread), 100*sizeof(int)); // 150, wait 0 + cumread += read6; + getnanotime(&ts12); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts13); + + read8 = read(fd1, (buf+cumread), 100*sizeof(int)); // 100, wait 0, emptied + cumread += read8; + getnanotime(&ts16); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts17); + + read9 = read(fd1, (buf+cumread), 150*sizeof(int)); // 150, wait 100 + cumread += read9; + getnanotime(&ts18); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts19); + + read10 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // 50, wait 0 + cumread += read10; + getnanotime(&ts20); + + read11 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // 0, wait 0 + SANDBOX_END; + fprintf(stderr, "second test, reads %d %d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read8, (int)read9, (int)read10, (int)read11); + CU_ASSERT_EQUAL(read1, 100*sizeof(int)); + CU_ASSERT_EQUAL(read2, 200*sizeof(int)); + CU_ASSERT_EQUAL(read3, 100*sizeof(int)); + CU_ASSERT_EQUAL(read4, 100*sizeof(int)); + CU_ASSERT_EQUAL(read5, 100*sizeof(int)); + CU_ASSERT_EQUAL(read6, 100*sizeof(int)); + CU_ASSERT_EQUAL(read8, 100*sizeof(int)); + CU_ASSERT_EQUAL(read9, 150*sizeof(int)); + CU_ASSERT_EQUAL(read10, 50*sizeof(int)); + CU_ASSERT_EQUAL(read11, 0); + diff1 = get_time_interval(&ts1, &ts2); + diff2 = get_time_interval(&ts3, &ts4); + diff3 = get_time_interval(&ts5, &ts6); + diff4 = get_time_interval(&ts7, &ts8); + diff5 = get_time_interval(&ts9, &ts10); + diff6 = get_time_interval(&ts11, &ts12); + diff7 = get_time_interval(&ts13, &ts16); + int64_t diff9 = get_time_interval(&ts17, &ts18); + int64_t diff10 = get_time_interval(&ts19, &ts20); + fprintf(stderr, "second test, diff1 %ld %ld %ld diff4 %ld %ld %ld diff7 %ld %ld diff10 %ld\n", (long)diff1, (long)diff2, (long)diff3, (long)diff4, (long)diff5, (long)diff6, (long)diff7, (long)diff9, (long)diff10); + /* + * TODO Should be used to check wait time, + * but is machine-dependent. + CU_ASSERT_TRUE(is_quasi_equal(diff1, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff2, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff3, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff4, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff5, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff6, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff7, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff8, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff9, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff10, 0*MILLION)); */ + for (int i = 0; i < 1000; i++) { + //fprintf(stderr, "%d %d %d\n", i, tab1[i], buf1[i]); + CU_ASSERT_EQUAL(buf1[i], i); + CU_ASSERT_EQUAL(tab1[i], i); + } + for (int i = 1000; i < 1100; i++) + CU_ASSERT_EQUAL(buf1[i], 0); + free_partial_read_buffer(rbuf1); + // End of test 2 + // Begin of test 3 + off_t offsets3[10] = {250*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 50*sizeof(int), 50*sizeof(int), 50*sizeof(int)}; + int intervals3[10] = {2*50, 4*50, 6*50, 1*50, 1*50, 1*50, 2*50, 3*50, 3*50, 1*50}; + if (create_partial_read_buffer(tab1, 10, offsets3, intervals3, rbuf1)) { + free(tab1); + free(rbuf1); + } + r = set_read_data(fd1, rbuf1); + CU_ASSERT_EQUAL_FATAL(r, 1); + // TODO check time interval + TS_ZERO(tss1); + read1 = 0; read2 = 0; read3 = 0; read4 = 0; read5 = 0; read6 = 0; + read7 = 0; read8 = 0; read9 = 0; read10 = 0; read11 = 0; cumread = 0; + ssize_t read12 = 0, read13 = 0, read14 = 0; + memset(buf1, 0, sizeof(buf1)); + SANDBOX_BEGIN; + tss1.tv_nsec = 3*50*MILLION; + nanosleep(&tss1, NULL); + + read1 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read1; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read2 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read2; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read3 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read3; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read4 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 50 + cumread += read4; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read5 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // wait 1*50 + cumread += read5; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read6 = recv(fd1, (buf+cumread), 20*sizeof(int), 0); + cumread += read6; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read7 = recv(fd1, (buf+cumread), (30+50)*sizeof(int), 0); + cumread += read7; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read8 = recv(fd1, (buf+cumread), 200*sizeof(int), 0); + cumread += read8; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read9 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); + cumread += read9; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read10 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read10; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read11 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 50 + cumread += read11; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read12 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 50, wait 1*50 + cumread += read12; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read13 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); + cumread += read13; + + read14 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); + cumread += read14; + SANDBOX_END; + fprintf(stderr, "third test, reads %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, + (int)read6, (int)read7, (int)read8, (int)read9, (int)read10, + (int)read11, (int)read12, (int)read13, (int)read14); + CU_ASSERT_EQUAL(read1, 100*sizeof(int)); + CU_ASSERT_EQUAL(read2, 100*sizeof(int)); + CU_ASSERT_EQUAL(read3, 100*sizeof(int)); + CU_ASSERT_EQUAL(read4, 50*sizeof(int)); + CU_ASSERT_EQUAL(read5, 50*sizeof(int)); + CU_ASSERT_EQUAL(read6, 20*sizeof(int)); + CU_ASSERT_EQUAL(read7, 80*sizeof(int)); + CU_ASSERT_EQUAL(read8, 200*sizeof(int)); + CU_ASSERT_EQUAL(read9, 50*sizeof(int)); + CU_ASSERT_EQUAL(read10, 100*sizeof(int)); + CU_ASSERT_EQUAL(read11, 50*sizeof(int)); + CU_ASSERT_EQUAL(read12, 50*sizeof(int)); + CU_ASSERT_EQUAL(read13, 50*sizeof(int)); + CU_ASSERT_EQUAL(read14, 0*sizeof(int)); + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(i, buf1[i]); + CU_ASSERT_EQUAL(i, tab1[i]); + } + for (int i = 1000; i < 1100; i++) + CU_ASSERT_EQUAL(buf1[i], 0); + free_read_buffer(rbuf1); +} + // Check failures: // Recall we must use the failures struct, and assign its (call), (call)_ret and (call)_errno fields. // And we have the fileds FAIL_ALWAYS, FAIL_NEVER, FAIL_FIRST, FAIL_SECOND, FAIL_THIRD and FAIL_TWICE diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index 604e244..ba30bc7 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -9,6 +9,7 @@ #include "read_write.h" +int64_t MILLION = 1000*1000; #define BILLION (1000*1000*1000) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) @@ -32,7 +33,7 @@ int64_t get_time_interval(const struct timespec *pasttime, const struct timespec cur += curtime->tv_nsec; int64_t interval = cur - past; if (interval < 0) { - fprintf(stderr, "BUG: negative time interval"); + fprintf(stderr, "BUG: negative time interval: %lld %lld %lld", (long long)past, (long long)cur, (long long)interval); fflush(stderr); } return interval; } @@ -61,7 +62,7 @@ struct read_item { unsigned int chunk_id; // Current chunk, or next chunk to be received size_t bytes_read; // Number of bytes read inside the current chunk struct timespec last_time; // Time of the end of the last call of read on this fd/socket - int64_t interval; // In real-time mode (READ_WRITE_REAL_INTERVAL), real wait interval for the current chunk. + int64_t interval; // In real-time mode (READ_WRITE_REAL_INTERVAL), real wait interval for the current chunk (in nanoseconds). }; struct read_fd_table_t { @@ -149,9 +150,9 @@ size_t read_handle_buffer_no_rt(struct read_item *cur, void *buf, size_t len, in // We may have to wait int64_t sleeptime = 0; if (cur->buf->mode == READ_WRITE_BEFORE_INTERVAL) { - sleeptime = curchunk->interval; + sleeptime = MILLION * (curchunk->interval); } else { // READ_WRITE_AFTER_INTERVAL - sleeptime = curchunk->interval - call_interval; + sleeptime = MILLION * (curchunk->interval) - call_interval; } if (sleeptime > 0) { if ((flags & MSG_DONTWAIT) != 0) { @@ -228,7 +229,7 @@ size_t read_handle_buffer_rt(struct read_item *cur, void *buf, size_t len, int f cur->bytes_read = 0; if (cur->chunk_id < cur->buf->nchunks) { // Update interval - cur->interval += cur->buf->chunks[cur->chunk_id].interval; + cur->interval += MILLION * (cur->buf->chunks[cur->chunk_id].interval); if (cur->interval > 0) { break; // Not available yet } @@ -240,6 +241,7 @@ size_t read_handle_buffer_rt(struct read_item *cur, void *buf, size_t len, int f ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags) { + // TODO add support for MSG_OOB, MSG_PEEK and MSG_WAITALL to flags struct read_item *cur = read_get_entry(fd); if (cur == NULL) { errno = EINTR; // This is about the only case where this can happen @@ -316,10 +318,64 @@ int set_read_data(int fd, const struct read_buffer_t *buf) getnanotime(&(tmp->last_time)); if (buf->mode == READ_WRITE_REAL_INTERVAL && buf->nchunks > 0) { // The first wait interval should be that of the first chunk. - tmp->interval = buf->chunks[0].interval; + tmp->interval = MILLION * (buf->chunks[0].interval); } else { tmp->interval = 0; } return (already_there ? 1 : 0); } +int create_partial_read_buffer(void *data, size_t n, off_t *offsets, int *intervals, struct read_buffer_t *buf) +{ + buf->nchunks = n; + buf->chunks = malloc(n * sizeof(struct read_bufchunk_t)); + if (!(buf->chunks)) { + free(buf); + return -1; + } + off_t cum_offset = 0; + for (unsigned int i = 0; i < n; i++) { + buf->chunks[i] = (struct read_bufchunk_t) { + .buf = (data + cum_offset), + .buflen = offsets[i], + .interval = intervals[i] + }; + cum_offset += offsets[i]; + } + return 0; +} + +struct read_buffer_t *create_read_buffer(void *data, size_t n, off_t *offsets, int *intervals, int mode) +{ + struct read_buffer_t *buf = malloc(sizeof(*buf)); + if (!buf) + return NULL; + buf->mode = mode; + if (create_partial_read_buffer(data, n, offsets, intervals, buf)) { + free(buf); + return NULL; + } + return buf; +} + +void free_partial_read_buffer(struct read_buffer_t *buf) +{ + for (unsigned int i = 0; i < buf->nchunks; i++) { + buf->chunks[i] = (struct read_bufchunk_t) { + .buf = NULL, + .buflen = 0, + .interval = 0 + }; + } + free(buf->chunks); + buf->chunks = NULL; + buf->nchunks = 0; +} + +void free_read_buffer(struct read_buffer_t *buf) +{ + free_partial_read_buffer(buf); + buf->mode = 0; + free(buf); +} + diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h index 345e4cf..2122dbe 100644 --- a/student/CTester/read_write.h +++ b/student/CTester/read_write.h @@ -104,14 +104,14 @@ * When simulating a partial-return read/recv, this is the fragments of data * that the caller receives sequentially (if it asks for the full fragment). * Fields: - * - interval: time interval (in microseconds) to wait before the chunk can be - * received. Should be no more than 1 million. Relative to the arrival + * - interval: time interval (in milliseconds) to wait before the chunk can be + * received. Should be no more than 1 thousand. Relative to the arrival * of the previous chunk, and thus relative. * - buf : chunk of data to be read. * - buflen: length of this buffer. */ struct read_bufchunk_t { - uint32_t interval; + int interval; const void *buf; size_t buflen; }; @@ -161,6 +161,38 @@ struct read_buffer_t { */ int set_read_data(int fd, const struct read_buffer_t *buf); +/** + * Fills in buf with a read_buffer_t structure, from data (a continuous area + * of memory), the list of offsets and the list of intervals. Allocates all + * the structures needed. + * Returns 0 if success, -1 in case of error (memory). + */ +int create_partial_read_buffer(void *data, size_t n, off_t *offsets, int *intervals, struct read_buffer_t *buf); + +/** + * Creates a read_buffer_t structure from data (a continuous area of memory), + * the provided mode, the list of offsets and the list of intervals. Allocates + * all the structures needed. + * Returns the created buffer, or NULL in case of error. + */ +struct read_buffer_t *create_read_buffer(void *data, size_t n, off_t *offsets, int *intervals, int mode); + +/** + * Deallocates the provided read buffer. This doesn't free the actual data + * returned by successive calls of recv and read, doesn't free buf itself + * (so that a stack-allocated read_buffer_t can be passed on) and doesn't + * reset the mode of the read_buffer_t. In short, it only frees up buf->chunks. + * This is the inverse of create_partial_read_buffer. + */ +void free_partial_read_buffer(struct read_buffer_t *buf); + +/** + * Deallocates the provided read buffer. This doesn't free the actual data + * returned by successive calls of recv and read. Also frees buf itself, + * so no `free(buf)` is needed after this call. + */ +void free_read_buffer(struct read_buffer_t *buf); + /** * Chunk of buffer. The write operation waits for {interval} before allowing * the write/send to use this buffer, if the previous chunk has been filled. From b0915a3cc6dd66f2fc2f74eac017e3e738f50f3a Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Thu, 20 Sep 2018 17:23:46 +0200 Subject: [PATCH 28/53] Remove write_buffer_t, as it is not yet available nor used --- student/CTester/read_write.h | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h index 2122dbe..39ac571 100644 --- a/student/CTester/read_write.h +++ b/student/CTester/read_write.h @@ -193,38 +193,6 @@ void free_partial_read_buffer(struct read_buffer_t *buf); */ void free_read_buffer(struct read_buffer_t *buf); -/** - * Chunk of buffer. The write operation waits for {interval} before allowing - * the write/send to use this buffer, if the previous chunk has been filled. - * The fields are similar to the read_bufchunk_t structure. - */ -struct write_bufchunk_t { - uint32_t interval; - void *buf; - size_t buflen; -}; - -/** - * Buffer for the write/send operations. It allows for partial write and send - * of data on the provided file descriptor, as would happen with send over - * a socket, or with write over a buffered file (e.g. a pipe). The structure - * and its fields are pretty similar to the read_buffer_t structure. The only - * difference is that currently, the only planned mode supported is - * READ_WRITE_BEFORE_INTERVAL. - */ -struct write_buffer_t { - int mode; // The mode of interpretation of interval - size_t nchunks; // Number of chunks - const struct write_bufchunk_t *chunks; // Table of chunks -}; - -/** - * Sets the buffer for the write/send operation, for simulating partial return. - * Currently, it does nothing other than returning zero; functionnalities - * will be added when needed. - */ -//int set_write_buffer(int fd, const struct write_buffer_t *buf); - /** * Removes all the association between fd's and read_buffer_t's that have been * previously set. It doesn't free the structures, however. From df4d54e773583262c0ac4b7583552a3d627d59b6 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Thu, 20 Sep 2018 17:28:23 +0200 Subject: [PATCH 29/53] Add reinit_network_inet_stats --- student/CTester/wrap_network_inet.c | 12 ++++++++++++ student/CTester/wrap_network_inet.h | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/student/CTester/wrap_network_inet.c b/student/CTester/wrap_network_inet.c index f356d2b..571a61a 100644 --- a/student/CTester/wrap_network_inet.c +++ b/student/CTester/wrap_network_inet.c @@ -13,6 +13,8 @@ * along with this program. If not, see . */ +#include + #include "wrap.h" uint16_t __real_htons(uint16_t hostshort); @@ -68,3 +70,13 @@ uint32_t __wrap_ntohl(uint32_t netlong) return ret; } +// Additionnal functions + +void reinit_network_inet_stats() +{ + memset(&(stats.htons), 0, sizeof(stats.htons)); + memset(&(stats.ntohs), 0, sizeof(stats.ntohs)); + memset(&(stats.htonl), 0, sizeof(stats.htonl)); + memset(&(stats.ntohl), 0, sizeof(stats.ntohl)); +} + diff --git a/student/CTester/wrap_network_inet.h b/student/CTester/wrap_network_inet.h index c49c4f1..8caf5f1 100644 --- a/student/CTester/wrap_network_inet.h +++ b/student/CTester/wrap_network_inet.h @@ -55,5 +55,11 @@ struct stats_ntohl_t { }; +/** + * Utility functions and structures + */ +void reinit_network_inet_stats(); + + #endif // __WRAP_NETWORK_INET_H_ From 7e0faabd4aaec347f80c147a1156d50b3f5e3a73 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Thu, 20 Sep 2018 17:31:15 +0200 Subject: [PATCH 30/53] Add reinit_file_stats --- student/CTester/wrap_file.c | 14 ++++++++++++++ student/CTester/wrap_file.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/student/CTester/wrap_file.c b/student/CTester/wrap_file.c index f65c1a0..e90d975 100644 --- a/student/CTester/wrap_file.c +++ b/student/CTester/wrap_file.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "wrap.h" #include "read_write.h" @@ -258,3 +259,16 @@ off_t __wrap_lseek(int fd, off_t offset, int whence) { stats.lseek.last_return=ret; return ret; } + +void reinit_file_stats() +{ + memset(&(stats.open), 0, sizeof(stats.open)); + memset(&(stats.creat), 0, sizeof(stats.creat)); + memset(&(stats.close), 0, sizeof(stats.close)); + memset(&(stats.read), 0, sizeof(stats.read)); + memset(&(stats.write), 0, sizeof(stats.write)); + memset(&(stats.stat), 0, sizeof(stats.stat)); + memset(&(stats.fstat), 0, sizeof(stats.fstat)); + memset(&(stats.lseek), 0, sizeof(stats.lseek)); +} + diff --git a/student/CTester/wrap_file.h b/student/CTester/wrap_file.h index 6db20d5..daaace7 100644 --- a/student/CTester/wrap_file.h +++ b/student/CTester/wrap_file.h @@ -123,4 +123,6 @@ struct stats_lseek_t { int last_return; // return value of the last lseek call issued }; +void reinit_file_stats(); + #endif // __WRAP_FILE_H_ From 73d4fb73719720b7791be725116e8a5216c97881 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Fri, 21 Sep 2018 18:44:50 +0200 Subject: [PATCH 31/53] Rename set_read_buffer and add reinit functions for all stats and all monitored --- ci/test_network_recv/tests.c | 32 ++++++++++++++++---------------- student/CTester/CTester.h | 10 ++++++++++ student/CTester/read_write.c | 3 ++- student/CTester/read_write.h | 2 +- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/ci/test_network_recv/tests.c b/ci/test_network_recv/tests.c index 14c3549..0371ea1 100644 --- a/ci/test_network_recv/tests.c +++ b/ci/test_network_recv/tests.c @@ -156,26 +156,26 @@ void test_fragmented_recv() .interval = 0 }; int fd1 = 17, fd2 = 42, fd3 = 0; - CU_ASSERT_EQUAL(set_read_data(fd1, &rbuf1), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd1, &rbuf1), 0); CU_ASSERT_EQUAL(read_fd_table.n, 1); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); - CU_ASSERT_EQUAL(set_read_data(fd2, NULL), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd2, NULL), 0); CU_ASSERT_EQUAL(read_fd_table.n, 1); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); - CU_ASSERT_EQUAL(set_read_data(fd1, NULL), 1); + CU_ASSERT_EQUAL(set_read_buffer(fd1, NULL), 1); CU_ASSERT_EQUAL(read_fd_table.n, 0); CU_ASSERT_FALSE(fd_is_read_buffered(fd1)); CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); struct timespec beforets1, afterts1, beforets2, afterts2, beforets3, afterts3; CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets1), 0); - CU_ASSERT_EQUAL_FATAL(set_read_data(fd1, &rbuf1), 0); + CU_ASSERT_EQUAL_FATAL(set_read_buffer(fd1, &rbuf1), 0); CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &afterts1), 0); CU_ASSERT_EQUAL(read_fd_table.n, 1); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); - CU_ASSERT_EQUAL(set_read_data(fd2, &rbuf1), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd2, &rbuf1), 0); CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); CU_ASSERT_EQUAL(read_fd_table.n, 2); CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); @@ -184,7 +184,7 @@ void test_fragmented_recv() CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd2); CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets3), 0); - CU_ASSERT_EQUAL(set_read_data(fd3, &rbuf3), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd3, &rbuf3), 0); CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &afterts3), 0); CU_ASSERT_EQUAL(read_fd_table.n, 3); CU_ASSERT_TRUE(fd_is_read_buffered(fd3)); @@ -195,17 +195,17 @@ void test_fragmented_recv() CU_ASSERT_EQUAL(read_fd_table.items[2].buf, &rbuf3); CU_ASSERT_EQUAL(read_fd_table.items[2].fd, fd3); - CU_ASSERT_EQUAL(set_read_data(fd2, &rbuf2), 1); + CU_ASSERT_EQUAL(set_read_buffer(fd2, &rbuf2), 1); CU_ASSERT_EQUAL(read_fd_table.n, 3); CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); - CU_ASSERT_EQUAL(set_read_data(fd2, NULL), 1); + CU_ASSERT_EQUAL(set_read_buffer(fd2, NULL), 1); CU_ASSERT_EQUAL(read_fd_table.n, 2); CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); - CU_ASSERT_EQUAL(set_read_data(fd2, NULL), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd2, NULL), 0); CU_ASSERT_EQUAL(read_fd_table.n, 2); CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &beforets2), 0); - CU_ASSERT_EQUAL(set_read_data(fd2, &rbuf2), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd2, &rbuf2), 0); CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &afterts2), 0); CU_ASSERT_EQUAL(read_fd_table.n, 3); CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); @@ -233,7 +233,7 @@ void test_fragmented_recv() fprintf(stderr, "%lld %lld\n", (long long)read_fd_table.items[1].interval, (long long)rbuf3.chunks[0].interval); CU_ASSERT_EQUAL(read_fd_table.items[1].interval, rbuf3.chunks[0].interval * MILLION); CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[1].last_time), &beforets3, &afterts3)); - CU_ASSERT_EQUAL(set_read_data(fd3, NULL), 1); // We don't need it + CU_ASSERT_EQUAL(set_read_buffer(fd3, NULL), 1); // We don't need it CU_ASSERT_EQUAL(read_fd_table.n, 2); // Correctly set up of the tests: done // Now, all we have to do is recv the data :-) @@ -273,7 +273,7 @@ void test_fragmented_recv_before() CU_FAIL_FATAL("Memory"); } int fd1 = 42; - int r = set_read_data(fd1, &rbuf1); + int r = set_read_buffer(fd1, &rbuf1); if (r < 0) { free(tab1); free_partial_read_buffer(&rbuf1); @@ -398,7 +398,7 @@ void test_fragmented_recv_after() CU_FAIL_FATAL("Memory"); } int fd1 = 42; - int r = set_read_data(fd1, &rbuf1); + int r = set_read_buffer(fd1, &rbuf1); if (r < 0) { free(tab1); free_partial_read_buffer(&rbuf1); @@ -521,7 +521,7 @@ void test_fragmented_recv_realtime() CU_FAIL_FATAL("Memory"); } int fd1 = 42; - int r = set_read_data(fd1, rbuf1); + int r = set_read_buffer(fd1, rbuf1); if (r < 0) { free(tab1); free_read_buffer(rbuf1); @@ -673,7 +673,7 @@ void test_fragmented_recv_realtime() free_read_buffer(rbuf1); CU_FAIL_FATAL("Memory"); } - r = set_read_data(fd1, rbuf1); // Again + r = set_read_buffer(fd1, rbuf1); // Again CU_ASSERT_EQUAL(r, 1); TS_ZERO(ts0); TS_ZERO(ts1); @@ -827,7 +827,7 @@ void test_fragmented_recv_realtime() free(tab1); free(rbuf1); } - r = set_read_data(fd1, rbuf1); + r = set_read_buffer(fd1, rbuf1); CU_ASSERT_EQUAL_FATAL(r, 1); // TODO check time interval TS_ZERO(tss1); diff --git a/student/CTester/CTester.h b/student/CTester/CTester.h index 4bb3101..6aef289 100644 --- a/student/CTester/CTester.h +++ b/student/CTester/CTester.h @@ -41,6 +41,16 @@ struct wrap_monitor_t monitored; struct wrap_fail_t failures; struct wrap_log_t logs; +void reinit_all_stats() +{ + memset(&stats, 0, sizeof(stats)); +} + +void reinit_all_monitored() +{ + memset(&monitored, 0, sizeof(monitored)); +} + /** * Readable file descriptors that contain the student's code outputs. */ diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index ba30bc7..6df8562 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -241,6 +241,7 @@ size_t read_handle_buffer_rt(struct read_item *cur, void *buf, size_t len, int f ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags) { + fprintf(stderr, "read_handle %d %p %u %d\n", fd, buf, (unsigned)len, flags); // TODO add support for MSG_OOB, MSG_PEEK and MSG_WAITALL to flags struct read_item *cur = read_get_entry(fd); if (cur == NULL) { @@ -293,7 +294,7 @@ int enable_pipe_monitoring(bool active) */ -int set_read_data(int fd, const struct read_buffer_t *buf) +int set_read_buffer(int fd, const struct read_buffer_t *buf) { if (buf == NULL) { return read_remove_entry(fd); diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h index 39ac571..3c03ced 100644 --- a/student/CTester/read_write.h +++ b/student/CTester/read_write.h @@ -159,7 +159,7 @@ struct read_buffer_t { * - -1 if malloc error * - -2 if argument error (typically buf->mode) */ -int set_read_data(int fd, const struct read_buffer_t *buf); +int set_read_buffer(int fd, const struct read_buffer_t *buf); /** * Fills in buf with a read_buffer_t structure, from data (a continuous area From 6b1c968bb2a5fbc07afc2b747aa92eaacebdbbed Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 22 Sep 2018 18:16:11 +0200 Subject: [PATCH 32/53] Small fix to run file --- run | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/run b/run index 7f9051d..cb3c5ab 100644 --- a/run +++ b/run @@ -4,7 +4,8 @@ # Auteurs : Jean-Martin Vlaeminck # Licence : GPLv3 -import subprocess, os +from __future__ import print_function +import subprocess, os, shlex, sys # Get a copy of CTester in the student/ directory, as well as the run.py file # From CTester, we need: @@ -27,16 +28,17 @@ def copy_CTester(): p = subprocess.Popen(shlex.split("cp -r /common/CTester/* ."), stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (cp_output, cp_errput) = p.communicate() if p.returncode: - print("Unable to move CTester") + print("Unable to move CTester", file=sys.stderr) return 1 return 0 else: - print("Unable to locate CTester") + print("Unable to locate CTester", file=sys.stderr) return 1 def run(): """Call the subprocess""" p = subprocess.Popen(shlex.split("/bin/python3 run.py")) + p.wait() return p.returncode if __name__ == "__main__": From 6fbb737453fb848c0b3dd32b930515ae53b5429b Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 22 Sep 2018 18:30:18 +0200 Subject: [PATCH 33/53] Fix to run file: by default, do not give a sucess status if score is above 50% --- run | 2 +- run.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/run b/run index cb3c5ab..11da6a4 100644 --- a/run +++ b/run @@ -37,7 +37,7 @@ def copy_CTester(): def run(): """Call the subprocess""" - p = subprocess.Popen(shlex.split("/bin/python3 run.py")) + p = subprocess.Popen(shlex.split("/bin/python3 run.py --no-use-fifty")) p.wait() return p.returncode diff --git a/run.py b/run.py index 01fb6d2..0d4fcbc 100644 --- a/run.py +++ b/run.py @@ -4,10 +4,11 @@ # Auteurs : Mathieu Xhonneux, Anthony Gégo # Licence : GPLv3 -import subprocess, shlex, re, os, yaml +import subprocess, shlex, re, os, yaml, sys from inginious import feedback, rst, input # Switch working directory to student/ +use_fifty = True if len(sys.argv) == 1 or (len(sys.argv) > 1 and "--use-fifty" in sys.argv) else False os.chdir("student") # Fetch and save the student code into a file for compilation @@ -132,4 +133,5 @@ score = 100*score/(total if not total == 0 else 1) feedback.set_grade(score) -feedback.set_global_result("success" if score >= 50 else "failed") +global_result = "success" if (score >= 50 and use_fifty) or (score == 100 and not use_fifty) else "failed" +feedback.set_global_result(global_result) From 76334538269a778062be270b3c86399e1e207c10 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 22 Sep 2018 18:57:15 +0200 Subject: [PATCH 34/53] Remove useless fprintf --- student/CTester/read_write.c | 1 - 1 file changed, 1 deletion(-) diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index 6df8562..5406c58 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -241,7 +241,6 @@ size_t read_handle_buffer_rt(struct read_item *cur, void *buf, size_t len, int f ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags) { - fprintf(stderr, "read_handle %d %p %u %d\n", fd, buf, (unsigned)len, flags); // TODO add support for MSG_OOB, MSG_PEEK and MSG_WAITALL to flags struct read_item *cur = read_get_entry(fd); if (cur == NULL) { From 43c23a38372a2c500cd9c66fb84bbed6b2d99583 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 22 Sep 2018 19:40:21 +0200 Subject: [PATCH 35/53] Add functions to get the number of bytes read from read_buffer --- student/CTester/read_write.c | 25 +++++++++++++++++++++++++ student/CTester/read_write.h | 14 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index 5406c58..7df34dd 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -325,6 +325,31 @@ int set_read_buffer(int fd, const struct read_buffer_t *buf) return (already_there ? 1 : 0); } +ssize_t get_bytes_read(int fd) +{ + //return -1; + struct read_item *cur = read_get_entry(fd); + if (cur == NULL) + return -1; + size_t bread = 0; + for (unsigned int i = 0; i < cur->chunk_id; i++) { + bread += cur->buf->chunks[i].buflen; + } + if (cur->chunk_id < cur->buf->nchunks) { + bread += cur->bytes_read; + } + return bread; +} + +int get_current_chunk_id(int fd) +{ + return -1; + struct read_item *cur = read_get_entry(fd); + if (cur == NULL) + return -1; + return cur->chunk_id; +} + int create_partial_read_buffer(void *data, size_t n, off_t *offsets, int *intervals, struct read_buffer_t *buf) { buf->nchunks = n; diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h index 3c03ced..ce8f997 100644 --- a/student/CTester/read_write.h +++ b/student/CTester/read_write.h @@ -161,6 +161,20 @@ struct read_buffer_t { */ int set_read_buffer(int fd, const struct read_buffer_t *buf); +/** + * Returns the amount of bytes read from the previously provided buffer + * for this file descriptor, or -1 if the file descriptor is not associated + * with a read_buffer. + */ +ssize_t get_bytes_read(int fd); + +/** + * Returns the chunk id currently set up for reading of the previously provided + * read buffer for this file descriptor, of -1 if there is no read_buffer + * associated with this file descriptor. + */ +int get_current_chunk_id(int fd); + /** * Fills in buf with a read_buffer_t structure, from data (a continuous area * of memory), the list of offsets and the list of intervals. Allocates all From d34f3716e8034ae6788bc55415f88da84a107ca3 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Mon, 24 Sep 2018 00:51:17 +0200 Subject: [PATCH 36/53] Correcting a few bugs by Valgrind and add a new function to utils-socket The function allows to connect a UDP socket to a remote client. Also, a confusion has been fixed --- student/CTester/CTester.c | 46 +++++++++++++++++++++++++--------- student/CTester/read_write.c | 4 +++ student/CTester/util_sockets.c | 29 ++++++++++++++++++++- student/CTester/util_sockets.h | 9 +++++++ 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index 14f0b55..5c3f7b6 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -197,7 +197,8 @@ void sandbox_end() // ... and looking for a double free warning char buf[BUFSIZ]; - int n; + memset(buf, 0, sizeof(buf)); + ssize_t n = 0; while ((n = read(pipe_stdout[0], buf, BUFSIZ)) > 0) { write(usr_pipe_stdout[1], buf, n); write(STDOUT_FILENO, buf, n); @@ -286,7 +287,7 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { mallopt(M_CHECK_ACTION, 1); // don't abort if double free true_stderr = dup(STDERR_FILENO); // preparing a non-blocking pipe for stderr true_stdout = dup(STDOUT_FILENO); // preparing a non-blocking pipe for stderr - fstdout = fdopen(true_stdout, "w"); // We can't just copy-paster stdout and stderr + fstdout = fdopen(true_stdout, "w"); // We can't just copy-paste stdout and stderr fstderr = fdopen(true_stderr, "w"); // as these structures use the file descriptor int *pipes[] = {pipe_stderr, pipe_stdout, usr_pipe_stdout, usr_pipe_stderr}; @@ -338,24 +339,34 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { /* initialize the CUnit test registry */ - if (CUE_SUCCESS != CU_initialize_registry()) + if (CUE_SUCCESS != CU_initialize_registry()) { + fprintf(stderr, "Error when initializing registry\n"); + fclose(f_out); return CU_get_error(); + } /* add a suite to the registry */ pSuite = CU_add_suite("Suite_1", init_suite1, clean_suite1); if (NULL == pSuite) { CU_cleanup_registry(); + fprintf(stderr, "Error when adding suite\n"); + fclose(f_out); return CU_get_error(); } for (int i=0; i < nb_tests; i++) { Dl_info DlInfo; - if (dladdr(tests[i], &DlInfo) == 0) + if (dladdr(tests[i], &DlInfo) == 0) { + fprintf(stderr, "Error when preparing test (dladdr)\n"); + fclose(f_out); return -EFAULT; + } CU_pTest pTest; if ((pTest = CU_add_test(pSuite, DlInfo.dli_sname, tests[i])) == NULL) { CU_cleanup_registry(); + fprintf(stderr, "Error when adding test\n"); + fclose(f_out); return CU_get_error(); } @@ -363,11 +374,17 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { start_test(); - if (CU_basic_run_test(pSuite,pTest) != CUE_SUCCESS) + if (CU_basic_run_test(pSuite,pTest) != CUE_SUCCESS) { + fclose(f_out); + fprintf(stderr, "Error when executing tests: CU_basic_run_test\n"); return CU_get_error(); + } - if (test_metadata.err) + if (test_metadata.err) { + fclose(f_out); + fprintf(stderr, "Error when executing tests: metadata\n"); return test_metadata.err; + } int nb = CU_get_number_of_tests_failed(); if (nb > 0) @@ -378,17 +395,17 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { ret = fprintf(f_out, "%s#SUCCESS#%s#%d#", test_metadata.problem, test_metadata.descr, test_metadata.weight); if (ret < 0) - return ret; + goto error_exit; for(int j=0; j < test_metadata.nb_tags; j++) { ret = fprintf(f_out, "%s", test_metadata.tags[j]); if (ret < 0) - return ret; + goto error_exit; if (j != test_metadata.nb_tags - 1) { ret = fprintf(f_out, ","); if (ret < 0) - return ret; + goto error_exit; } } @@ -403,21 +420,26 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { free(head); if (ret < 0) - return ret; + goto error_exit; } test_metadata.fifo_out = NULL; ret = fprintf(f_out, "\n"); if (ret < 0) - return ret; - + goto error_exit; } fclose(f_out); + fclose(fstdout); + fclose(fstderr); /* Run all tests using the CUnit Basic interface */ //CU_basic_run_tests(); //CU_automated_run_tests(); CU_cleanup_registry(); return CU_get_error(); +error_exit: + fclose(f_out); + fprintf(stderr, "Error when printing the results.\n"); + return ret; } diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index 7df34dd..54930cc 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -112,6 +112,10 @@ int read_remove_entry(int fd) read_fd_table.n--; } } + if (read_fd_table.n == 0) { + free(read_fd_table.items); + read_fd_table.items = NULL; + } return (found ? 1 : 0); } diff --git a/student/CTester/util_sockets.c b/student/CTester/util_sockets.c index 24afdc2..dce58c7 100644 --- a/student/CTester/util_sockets.c +++ b/student/CTester/util_sockets.c @@ -44,6 +44,10 @@ int create_socket(const char *host, const char *serv, int domain, int type, int return -1; } if (do_bind) { + int yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) { + perror("setsockopt"); + } if (bind(fd, rep->ai_addr, rep->ai_addrlen)) { perror("bind"); close(fd); @@ -52,7 +56,7 @@ int create_socket(const char *host, const char *serv, int domain, int type, int } } else { if (connect(fd, rep->ai_addr, rep->ai_addrlen)) { - perror("bind"); + perror("connect"); close(fd); freeaddrinfo(rep); return -1; @@ -91,6 +95,29 @@ int create_udp_client_socket(const char *host, const char *serv, int domain) return clientfd; } +int connect_udp_server_to_client(int sfd, int cfd) +{ + // send from cfd to recvfrom sfd. + char dumb = 'A'; + ssize_t s = send(cfd, &dumb, sizeof(dumb), 0); + if (s != sizeof(dumb)) { + perror("send"); + return -1; + } + struct sockaddr_storage client_addr; + socklen_t client_addrlen = sizeof(client_addr); + ssize_t r = recvfrom(sfd, &dumb, sizeof(dumb), 0, (struct sockaddr*)&client_addr, &client_addrlen); + if (r != sizeof(dumb)) { + perror("recvfrom"); + return -1; + } + if (connect(sfd, (struct sockaddr*)&client_addr, client_addrlen)) { + perror("connect"); + return -1; + } + return 0; +} + #define PIPE_AND_FORK_BEGIN() \ if (pipe(c2s)) { \ perror("pipe"); \ diff --git a/student/CTester/util_sockets.h b/student/CTester/util_sockets.h index c14f6d6..f5136e9 100644 --- a/student/CTester/util_sockets.h +++ b/student/CTester/util_sockets.h @@ -67,6 +67,15 @@ int create_udp_server_socket(const char *serv, int domain); */ int create_udp_client_socket(const char *host, const char *serv, int domain); +/** + * Connects the provided sfd UDP server socket to the address of the provided + * cfd client socket, such that calls to send on sfd will correctly send + * to the client and will be receivable from cfd. cfd should be "connected" + * to the server (i.e., its default address should be that of the server). + * Returns 0 in case of success, or -1 if there was an error. + */ +int connect_udp_server_to_client(int sfd, int cfd); + #define RECV_CHUNK 1 #define SEND_CHUNK 2 From fa5ba4a5223b39040fe589e13c9103af3d69e62a Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Mon, 24 Sep 2018 18:00:32 +0200 Subject: [PATCH 37/53] Preparing the PR --- student/CTester/read_write.c | 17 ----------------- student/CTester/wrap_network_dns.c | 1 - student/CTester/wrap_network_socket.h | 1 - student/Makefile | 9 ++++----- 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index 54930cc..db77a1d 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -279,23 +279,6 @@ void reinit_read_fd_table() read_fd_table.n = 0; } -/*int enable_socket_recv_send_monitoring(bool active) -{ - // TODO -}*/ - -/*int enable_socket_all_monitoring(bool active) -{ - // TODO -}*/ - -/* -int enable_pipe_monitoring(bool active) -{ - // TODO -} -*/ - int set_read_buffer(int fd, const struct read_buffer_t *buf) { diff --git a/student/CTester/wrap_network_dns.c b/student/CTester/wrap_network_dns.c index 4471f1f..6833bb4 100644 --- a/student/CTester/wrap_network_dns.c +++ b/student/CTester/wrap_network_dns.c @@ -39,7 +39,6 @@ gai_strerror_method_t gai_strerror_method = NULL; freeaddrinfo_badarg_report_t freeaddrinfo_badarg_reporter = NULL; // Used to record the addrinfo lists "returned" by getaddrinfo, in order to check for their deallocation via freeaddrinfo. -// TODO maybe it would be better placed in the .c file ? struct addrinfo_node_t { struct addrinfo *addr_list; struct addrinfo_node_t *next; diff --git a/student/CTester/wrap_network_socket.h b/student/CTester/wrap_network_socket.h index 61836e7..8b23d17 100644 --- a/student/CTester/wrap_network_socket.h +++ b/student/CTester/wrap_network_socket.h @@ -140,7 +140,6 @@ struct return_recvfrom_t { socklen_t addrlen; }; -// TODO see if we can merge last_returns.ret and last_return as only one variable within the structure, and with only this simplified access. struct stats_recv_t { int called; struct params_recv_t last_params; diff --git a/student/Makefile b/student/Makefile index a59ee2e..4f2382c 100644 --- a/student/Makefile +++ b/student/Makefile @@ -4,14 +4,13 @@ LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic SRC=$(sort $(wildcard *.c)) $(sort $(wildcard CTester/*.c)) OBJ=$(SRC:.c=.o) CFLAGS = -Wall -Werror -Wextra -Wshadow -CFLAGS += -std=gnu99 # was gnu99 FIXME -#CFLAGS += -DC99 # Don't know why TODO +CFLAGS += -std=gnu99 +#CFLAGS += -DC99 CFLAGS += -ICTester # to include the folder. -#CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE DEBUGFLAGS = -g -Wno-unused-variable -Wno-unused-parameter OTHERFLAGS = -fstack-protector-all -D_FORTIFY_SOURCE=2 -fno-omit-frame-pointer -#CFLAGS += $(DEBUGFLAGS) -#CFLAGS += $(OTHERFLAGS) +#CFLAGS += $(DEBUGFLAGS) # Uncomment to activate +#CFLAGS += $(OTHERFLAGS) # Uncomment to activate WRAP += -Wl,-wrap=exit WRAP += -Wl,-wrap=open -Wl,-wrap=creat -Wl,-wrap=close -Wl,-wrap=read -Wl,-wrap=write -Wl,-wrap=stat -Wl,-wrap=fstat -Wl,-wrap=lseek WRAP += -Wl,-wrap=getpid From acc0d8e42033a41d325b7b4dc7f3ad048969c67e Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:33:11 +0200 Subject: [PATCH 38/53] Update gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index e82bf44..d9a3f85 100644 --- a/.gitignore +++ b/.gitignore @@ -39,11 +39,17 @@ *.idb *.pdb +# Vi/Vim +*.vim + # Emacs *~ \#*\# .\#* +# VS Code +.vscode/ + # vi [._]*.s[a-v][a-z] [._]*.sw[a-p] From 49e4e20afc80ed9a2dd3dcc621ffc94d02e1e638 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 31 Jul 2019 01:07:26 +0200 Subject: [PATCH 39/53] CTester.c: fix missing cleanup of read_write Needs to be applied after read_write changes --- student/CTester/CTester.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index 5c3f7b6..be68a30 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -19,6 +19,7 @@ #include #include "wrap.h" +#include "read_write.h" #define TAGS_NB_MAX 20 #define TAGS_LEN_MAX 30 @@ -242,6 +243,8 @@ void start_test() memset(&failures, 0, sizeof(failures)); memset(&monitored, 0, sizeof(monitored)); memset(&logs, 0, sizeof(logs)); + reset_gai_fai_gstr_gni_methods(); + reinit_read_fd_table(); } /** From 8508162a0d1bb08540b893602c911db37ea85588 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:35:19 +0200 Subject: [PATCH 40/53] Add real tests for DNS --- ci/test_network_dns/expected_results.txt | 4 +- ci/test_network_dns/tests.c | 1030 ++++++++++++++++++---- ci/test_network_recv/tests.c | 5 + 3 files changed, 876 insertions(+), 163 deletions(-) diff --git a/ci/test_network_dns/expected_results.txt b/ci/test_network_dns/expected_results.txt index 9637098..d05ee31 100644 --- a/ci/test_network_dns/expected_results.txt +++ b/ci/test_network_dns/expected_results.txt @@ -1 +1,3 @@ -wrapper_stats#SUCCESS#Tests that the wrapper functions correctly remembers the stats#1# +substitute_gai#SUCCESS#Test if the substitution functions etc work correctly#1# +checked_freeaddrinfo#SUCCESS#Tests the checked freeaddrinfo#1# +simple_gai_fai#SUCCESS#Tests simple_gai and simple_fai#1# diff --git a/ci/test_network_dns/tests.c b/ci/test_network_dns/tests.c index 814d98c..16a11ea 100644 --- a/ci/test_network_dns/tests.c +++ b/ci/test_network_dns/tests.c @@ -2,183 +2,889 @@ #include #include #include +#include #include "CTester/CTester.h" #include "student_code.h" -void __real_exit(int status); // Needed as otherwise we'll get a segfault +int __real_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +int __real_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); +void __real_freeaddrinfo(struct addrinfo *res); +const char *__real_gai_strerror(int errcode); -void test_wrapper_stats() +const char gai1_cname[] = "CGAI1"; +const char gai2_cname[] = "CGAI2"; +struct custom_gai1_stats_t { + int called; + const char *node; + const char *serv; + const struct addrinfo *hints; + struct addrinfo **res; +} custom_gai1_stats; +int custom_gai1(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res) { - // Let's enable the stats for all functions - set_test_metadata("wrapper_stats", _("Tests that the wrapper functions correctly remembers the stats"), 1); - CU_ASSERT_EQUAL(stats.accept.called, 0); - CU_ASSERT_EQUAL(stats.bind.called, 0); - CU_ASSERT_EQUAL(stats.connect.called, 0); - CU_ASSERT_EQUAL(stats.listen.called, 0); - CU_ASSERT_EQUAL(stats.recv.called, 0); - CU_ASSERT_EQUAL(stats.recvfrom.called, 0); - CU_ASSERT_EQUAL(stats.recvmsg.called, 0); - CU_ASSERT_EQUAL(stats.recv_all.called, 0); - CU_ASSERT_EQUAL(stats.send.called, 0); - CU_ASSERT_EQUAL(stats.sendto.called, 0); - CU_ASSERT_EQUAL(stats.sendmsg.called, 0); - CU_ASSERT_EQUAL(stats.send_all.called, 0); - CU_ASSERT_EQUAL(stats.socket.called, 0); - - CU_ASSERT_EQUAL(stats.accept.last_return, 0); - CU_ASSERT_EQUAL(stats.bind.last_return, 0); - CU_ASSERT_EQUAL(stats.connect.last_return, 0); - CU_ASSERT_EQUAL(stats.listen.last_return, 0); - CU_ASSERT_EQUAL(stats.recv.last_return, 0); - CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); - CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); - CU_ASSERT_EQUAL(stats.send.last_return, 0); - CU_ASSERT_EQUAL(stats.sendto.last_return, 0); - CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); - CU_ASSERT_EQUAL(stats.socket.last_return, 0); - - monitored.getaddrinfo = monitored.freeaddrinfo = true; - monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = true; - MONITOR_ALL_RECV(monitored, true); - MONITOR_ALL_SEND(monitored, true); - CU_ASSERT_EQUAL(monitored.recv, true); - CU_ASSERT_EQUAL(monitored.recvfrom, true); - CU_ASSERT_EQUAL(monitored.recvmsg, true); - CU_ASSERT_EQUAL(monitored.send, true); - CU_ASSERT_EQUAL(monitored.sendto, true); - CU_ASSERT_EQUAL(monitored.sendmsg, true); - - // wrap_monitoring is set to true inside the sandbox, and set to false at the end of it. - struct f1_stats stats1; - SANDBOX_BEGIN; - run_student_tests_wrapper_stats_1(&stats1); - SANDBOX_END; - - CU_ASSERT_EQUAL(stats.accept.called, 0); - CU_ASSERT_EQUAL(stats.bind.called, 0); - CU_ASSERT_EQUAL(stats.connect.called, stats1.nb_connect); - CU_ASSERT_EQUAL(stats.listen.called, 0); - CU_ASSERT_EQUAL(stats.recv.called, 0); - CU_ASSERT_EQUAL(stats.recvfrom.called, 0); - CU_ASSERT_EQUAL(stats.recvmsg.called, 0); - CU_ASSERT_EQUAL(stats.recv_all.called, 0); - CU_ASSERT_EQUAL(stats.send.called, stats1.nb_send); - CU_ASSERT_EQUAL(stats.sendto.called, 0); - CU_ASSERT_EQUAL(stats.sendmsg.called, 0); - CU_ASSERT_EQUAL(stats.send_all.called, stats1.nb_send); - CU_ASSERT_EQUAL(stats.socket.called, stats1.nb_addr_tried); - - reinit_stats_network_dns(); - reinit_network_socket_stats(); // Simpler for the next calls - - int pid; - pid = fork(); - if (pid == 0) { - struct stats2_server statss; - SANDBOX_BEGIN; - run_student_tests_wrapper_stats_2_server(&statss); - SANDBOX_END; - // I must exit to let the other process terminate. No test here ! - __real_exit(0); - } else if (pid != -1) { - struct stats2_client statss; - memset(&statss, 0, sizeof(statss)); - sleep(1); // Strangely extremely important FIXME - SANDBOX_BEGIN; - run_student_tests_wrapper_stats_2_client(&statss); - SANDBOX_END; - int status = 0; - waitpid(pid, &status, 0); - // Let's have a look at the statistics for the client - CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); - CU_ASSERT_EQUAL(stats.connect.called, statss.nconnect); - CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); - CU_ASSERT_EQUAL(stats.freeaddrinfo.called, (statss.ngai > 0 && statss.nsocket > 0)); - CU_ASSERT_EQUAL(stats.sendmsg.called, statss.nsend); - CU_ASSERT_EQUAL(stats.send_all.called, statss.nsend); - CU_ASSERT_EQUAL(stats.recv.called, statss.nrecv); - CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecv); + custom_gai1_stats.called++; + custom_gai1_stats.node = node; + custom_gai1_stats.serv = serv; + custom_gai1_stats.hints = hints; + custom_gai1_stats.res = res; + *res = calloc(1, sizeof(struct addrinfo)); + (*res)->ai_family = AF_INET; + (*res)->ai_socktype = SOCK_DGRAM; + (*res)->ai_protocol = 0; + (*res)->ai_addrlen = 0; + (*res)->ai_addr = NULL; + (*res)->ai_canonname = NULL; + (*res)->ai_next = NULL; + if (node != NULL) { + return 0; + } else if (serv == NULL) { + return 1; } else { - CU_FAIL(); + return 2; } +} - reinit_stats_network_dns(); - reinit_network_socket_stats(); - - pid = fork(); - if (pid == 0) { - struct stats2_client statss; - SANDBOX_BEGIN; - run_student_tests_wrapper_stats_2_client(&statss); - SANDBOX_END; - // I must exit to let the other process terminate. No test here ! - __real_exit(0); - } else if (pid != -1) { - struct stats2_server statss; - memset(&statss, 0, sizeof(statss)); - SANDBOX_BEGIN; - run_student_tests_wrapper_stats_2_server(&statss); - SANDBOX_END; - int status = 0; - waitpid(pid, &status, 0); - // Let's have a look at the statistics for the server - CU_ASSERT_EQUAL(stats.bind.called, statss.nbind); - CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); - CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); - CU_ASSERT_EQUAL(stats.sendto.called, statss.nsendto); - CU_ASSERT_EQUAL(stats.recvfrom.called, statss.nrecvfrom); - CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecvfrom); - CU_ASSERT_EQUAL(stats.send_all.called, statss.nsendto); - } else { - CU_FAIL(); +struct custom_gai2_stats_t { + int called; + const char *node; + const char *serv; + const struct addrinfo *hints; + struct addrinfo **res; +} custom_gai2_stats; +int custom_gai2(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res) +{ + custom_gai2_stats.called++; + custom_gai2_stats.node = node; + custom_gai2_stats.serv = serv; + custom_gai2_stats.hints = hints; + custom_gai2_stats.res = res; + return __real_getaddrinfo(node, serv, hints, res); +} + +struct custom_fai1_stats_t { + int called; + struct addrinfo *res; + struct addrinfo *no_free; +} custom_fai1_stats; +void custom_fai1(struct addrinfo *res) +{ + custom_fai1_stats.called++; + custom_fai1_stats.res = res; + if (custom_fai1_stats.no_free != res) { + free(res); } +} +void custom_fai1_prevent_free(struct addrinfo *res) +{ + custom_fai1_stats.no_free = res; +} + +struct custom_fai2_stats_t { + int called; + struct addrinfo *res; +} custom_fai2_stats; +void custom_fai2(struct addrinfo *res) +{ + custom_fai2_stats.called++; + custom_fai2_stats.res = res; + __real_freeaddrinfo(res); +} +struct custom_gni1_stats_t { + int called; + const struct sockaddr *addr; + socklen_t addrlen; + socklen_t hostlen; + socklen_t servlen; + int flags; + char *host; + char *serv; +} custom_gni1_stats; +int custom_gn1(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) +{ + custom_gni1_stats.called++; + custom_gni1_stats.addr = addr; + custom_gni1_stats.addrlen = addrlen; + custom_gni1_stats.host = host; + custom_gni1_stats.hostlen = hostlen; + custom_gni1_stats.serv = serv; + custom_gni1_stats.servlen = servlen; + custom_gni1_stats.flags = flags; + char *s1 = "Hello"; + char *s2 = "world"; + memcpy(host, s1, 6); + memcpy(serv, s2, 6); + return 0; +} + +struct custom_gni2_stats_t { + int called; + const struct sockaddr *addr; + socklen_t addrlen; + socklen_t hostlen; + socklen_t servlen; + int flags; + char *host; + char *serv; +} custom_gni2_stats; +int custom_gn2(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) +{ + custom_gni2_stats.called++; + custom_gni2_stats.addr = addr; + custom_gni2_stats.addrlen = addrlen; + custom_gni2_stats.host = host; + custom_gni2_stats.hostlen = hostlen; + custom_gni2_stats.serv = serv; + custom_gni2_stats.servlen = servlen; + custom_gni2_stats.flags = flags; + return __real_getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags); +} + +struct custom_fai_reporter_stats_t { + int called; +} custom_fair_stats; +void custom_checked_fai_reporter() +{ + custom_fair_stats.called++; +} + +struct custom_gse1_stats_t { + int called; + int code; +} custom_gse1_stats; +const char *custom_gai_strerror1(int code) +{ + custom_gse1_stats.called++; + custom_gse1_stats.code = code; + char *s1 = "GSE1"; + return s1; +} + +struct custom_gse2_stats_t { + int called; + int code; +} custom_gse2_stats; +const char *custom_gai_strerror2(int code) +{ + custom_gse2_stats.called++; + custom_gse2_stats.code = code; + char *s2 = "GSE2"; + return s2; +} + +/* +What to test: +- set_gai works, as well as none (from none) +- set_gais all, gai, fai, none (null) work +- set_gai and then reset it works +- set_gais and then reset both works +- set_gni works, both set and reset +- set_gai_strerror should work, as well as reset +- checked freeaddrinfo should work: activate, pass a correct, pass an incorrect, deactivate, pass an incorrect +- the reporter for the checked fai should also work, both set and reset. + */ +void test_gai_substitutes() +{ + set_test_metadata("substitute_gai", _("Test if the substitution functions etc work correctly"), 1); + reinit_all_monitored(); + reinit_all_stats(); + reset_gai_fai_gstr_gni_methods(); + memset(&custom_gai1_stats, 0, sizeof(custom_gai1_stats)); + memset(&custom_gai2_stats, 0, sizeof(custom_gai2_stats)); + memset(&custom_fai1_stats, 0, sizeof(custom_fai1_stats)); + memset(&custom_fai2_stats, 0, sizeof(custom_fai2_stats)); + memset(&custom_gni1_stats, 0, sizeof(custom_gni1_stats)); + memset(&custom_gni2_stats, 0, sizeof(custom_gni2_stats)); + memset(&custom_fair_stats, 0, sizeof(custom_fair_stats)); + memset(&custom_gse1_stats, 0, sizeof(custom_gse1_stats)); + memset(&custom_gse2_stats, 0, sizeof(custom_gse2_stats)); + set_check_freeaddrinfo(false); // Disable it for all tests + + int i1=-1, i2=-1, i3=-1, i4=-1, i5=-1, i6=-1, i7=-1, i8=-1, i9=-1; + struct addrinfo hints1, *res1, *res2, *res3, *res4, *res5, *res6, *res7, *res8, *res9; + memset(&hints1, 0, sizeof(hints1)); + hints1.ai_family = AF_INET; + hints1.ai_socktype = SOCK_DGRAM; + hints1.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + char h1[]="0.0.0.1", h2[]="0.0.0.2", h3[]="0.0.0.3", h4[]="0.0.0.4"; + char h5[]="0.0.0.5", h6[]="0.0.0.6", h7[]="0.0.0.7", h8[]="0.0.0.8"; + (void)h8; + (void)res8; + (void)res9; + (void)i9; monitored.getaddrinfo = monitored.freeaddrinfo = false; - monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = false; - MONITOR_ALL_RECV(monitored, false); - MONITOR_ALL_SEND(monitored, false); - CU_ASSERT_EQUAL(monitored.recv, false); - CU_ASSERT_EQUAL(monitored.recvfrom, false); - CU_ASSERT_EQUAL(monitored.recvmsg, false); - CU_ASSERT_EQUAL(monitored.send, false); - CU_ASSERT_EQUAL(monitored.sendto, false); - CU_ASSERT_EQUAL(monitored.sendmsg, false); - - reinit_stats_network_dns(); - reinit_network_socket_stats(); - CU_ASSERT_EQUAL(stats.accept.called, 0); - CU_ASSERT_EQUAL(stats.bind.called, 0); - CU_ASSERT_EQUAL(stats.connect.called, 0); - CU_ASSERT_EQUAL(stats.listen.called, 0); - CU_ASSERT_EQUAL(stats.recv.called, 0); - CU_ASSERT_EQUAL(stats.recvfrom.called, 0); - CU_ASSERT_EQUAL(stats.recvmsg.called, 0); - CU_ASSERT_EQUAL(stats.recv_all.called, 0); - CU_ASSERT_EQUAL(stats.send.called, 0); - CU_ASSERT_EQUAL(stats.sendto.called, 0); - CU_ASSERT_EQUAL(stats.sendmsg.called, 0); - CU_ASSERT_EQUAL(stats.send_all.called, 0); - CU_ASSERT_EQUAL(stats.socket.called, 0); - - CU_ASSERT_EQUAL(stats.accept.last_return, 0); - CU_ASSERT_EQUAL(stats.bind.last_return, 0); - CU_ASSERT_EQUAL(stats.connect.last_return, 0); - CU_ASSERT_EQUAL(stats.listen.last_return, 0); - CU_ASSERT_EQUAL(stats.recv.last_return, 0); - CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); - CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); - CU_ASSERT_EQUAL(stats.send.last_return, 0); - CU_ASSERT_EQUAL(stats.sendto.last_return, 0); - CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); - CU_ASSERT_EQUAL(stats.socket.last_return, 0); + SANDBOX_BEGIN; + i1 = getaddrinfo(h1, "80", &hints1, &res1); + SANDBOX_END; + CU_ASSERT_EQUAL(i1, 0); + CU_ASSERT_PTR_NOT_NULL(res1); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 0); // Not enabled yet + SANDBOX_BEGIN; + freeaddrinfo(res1); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 0); // Not enabled yet + monitored.getaddrinfo = monitored.freeaddrinfo = true; + i1=-1; + res1 = NULL; + SANDBOX_BEGIN; + i1 = getaddrinfo(h2, "80", &hints1, &res1); + freeaddrinfo(res1); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.res, &res1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.hints, &hints1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.node, h2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 1); + CU_ASSERT_EQUAL(stats.freeaddrinfo.last_param, res1); + // Now, let's set getaddrinfo to someone else + set_getaddrinfo_method(custom_gai2); // That's just a wrapper, compatible with __real_freeaddrinfo + SANDBOX_BEGIN; + i2 = getaddrinfo(h3, "80", &hints1, &res2); + freeaddrinfo(res2); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 2); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.node, h3); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.hints, &hints1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.res, &res2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.last_param, res2); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 1); + CU_ASSERT_EQUAL(custom_gai2_stats.node, h3); + CU_ASSERT_EQUAL(custom_gai2_stats.res, &res2); + res2 = NULL; + CU_ASSERT_EQUAL(getaddrinfo(h3, "80", &hints1, &res2), 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 2); // No change + CU_ASSERT_EQUAL(custom_gai2_stats.called, 1); // Neither + set_getaddrinfo_method(NULL); + SANDBOX_BEGIN; + i3 = getaddrinfo(h4, "80", &hints1, &res3); + freeaddrinfo(res3); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 3); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 1); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 0); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 3); + set_gai_methods(custom_gai1, custom_fai1); + SANDBOX_BEGIN; + i4 = getaddrinfo(h5, "80", &hints1, &res4); + freeaddrinfo(res4); + SANDBOX_END; + CU_ASSERT_EQUAL(i4, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_gai1_stats.called, 1); + CU_ASSERT_EQUAL(custom_gai1_stats.node, h5); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); + CU_ASSERT_EQUAL(custom_fai1_stats.res, res4); + CU_ASSERT_EQUAL(getaddrinfo(h4, "80", &hints1, &res3), 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_gai1_stats.called, 1); + freeaddrinfo(res3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); + set_gai_methods(custom_gai2, custom_fai2); + SANDBOX_BEGIN; + i5 = getaddrinfo(h6, "80", &hints1, &res5); + freeaddrinfo(res5); + SANDBOX_END; + CU_ASSERT_EQUAL(i5, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 5); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 2); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 1); + set_gai_methods(NULL, custom_fai2); + SANDBOX_BEGIN; + i6 = getaddrinfo(h5, "80", &hints1, &res6); + freeaddrinfo(res6); + SANDBOX_END; + CU_ASSERT_EQUAL(i6, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 6); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 6); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 2); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 2); + set_gai_methods(custom_gai2, NULL); + i5=-1; + SANDBOX_BEGIN; + i5 = getaddrinfo(h6, "80", &hints1, &res5); + freeaddrinfo(res5); + SANDBOX_END; + CU_ASSERT_EQUAL(i5, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 2); + set_gai_methods(custom_gai2, custom_fai2); + monitored.getaddrinfo = false; + monitored.freeaddrinfo = true; + SANDBOX_BEGIN; + i6 = getaddrinfo(h5, "80", &hints1, &res6); + freeaddrinfo(res6); + SANDBOX_END; + CU_ASSERT_EQUAL(i6, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 8); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 3); + monitored.getaddrinfo = true; + monitored.freeaddrinfo = false; + SANDBOX_BEGIN; + i7 = getaddrinfo(h7, "80", &hints1, &res7); + freeaddrinfo(res7); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 8); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 8); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 3) ; + // TODO should also test that it doesn't do anything else + + // Testing getnameinfo + CU_ASSERT_EQUAL(stats.getnameinfo.called, 0); + i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = -1; + int flags = NI_NUMERICHOST; + struct sockaddr_in s1, s2, s3, s4; + memset(&s1, 0, sizeof(s1)); + memset(&s2, 0, sizeof(s2)); + memset(&s3, 0, sizeof(s3)); + memset(&s4, 0, sizeof(s4)); + s1.sin_family = s2.sin_family = s3.sin_family = s4.sin_family = AF_INET; + s1.sin_port = s2.sin_port = s3.sin_port = s4.sin_port = htons(80); + inet_pton(AF_INET, "0.0.0.1", &(s1.sin_addr)); + inet_pton(AF_INET, "0.0.0.2", &(s2.sin_addr)); + inet_pton(AF_INET, "0.0.0.3", &(s3.sin_addr)); + inet_pton(AF_INET, "0.0.0.4", &(s4.sin_addr)); + CU_ASSERT_EQUAL(s1.sin_addr.s_addr, htonl(1)); + CU_ASSERT_EQUAL(s2.sin_addr.s_addr, htonl(2)); + CU_ASSERT_EQUAL(s3.sin_addr.s_addr, htonl(3)); + CU_ASSERT_EQUAL(s4.sin_addr.s_addr, htonl(4)); + char rh1[10]={0}, rh2[10]={0}, rh3[10]={0}, rh4[10]={0}; + char rs1[10]={0}, rs2[10]={0}, rs3[10]={0}, rs4[10]={0}; + // Tests + i1 = getnameinfo((struct sockaddr*)&s1, sizeof(s1), rh1, sizeof(rh1), rs1, sizeof(rs1), flags); + CU_ASSERT_EQUAL(stats.getnameinfo.called, 0); + CU_ASSERT_EQUAL(strcmp(rh1, "0.0.0.1"), 0); + SANDBOX_BEGIN; + i2 = getnameinfo((struct sockaddr*)&s2, sizeof(s2), rh2, sizeof(rh2), rs2, sizeof(rs2), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 0); + CU_ASSERT_EQUAL(strcmp(rh2, "0.0.0.2"), 0); + monitored.getnameinfo = true; + SANDBOX_BEGIN; + i3 = getnameinfo((struct sockaddr*)&s3, sizeof(s3), rh3, sizeof(rh3), rh4, sizeof(rh4), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 1); + CU_ASSERT_EQUAL(strcmp(rh3, "0.0.0.3"), 0); + set_getnameinfo_method(custom_gn1); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 0); + SANDBOX_BEGIN; + i4 = getnameinfo((struct sockaddr*)&s4, sizeof(s4), rh4, sizeof(rh4), rs4, sizeof(rs4), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 1); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s4); + CU_ASSERT_EQUAL(strcmp(rh4, "Hello"), 0); + monitored.getnameinfo = false; + SANDBOX_BEGIN; + i5 = getnameinfo((struct sockaddr*)&s1, sizeof(s1), rh1, sizeof(rh1), rs1, sizeof(rs1), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 1); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s4); + monitored.getnameinfo = true; + SANDBOX_BEGIN; + i6 = getnameinfo((struct sockaddr*)&s2, sizeof(s2), rh2, sizeof(rh2), rs2, sizeof(rs2), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 3); + CU_ASSERT_EQUAL(stats.getnameinfo.last_params.addr, &s2); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s2); + set_getnameinfo_method(NULL); + SANDBOX_BEGIN; + i7 = getnameinfo((struct sockaddr*)&s3, sizeof(s3), rh3, sizeof(rh3), rs3, sizeof(rs3), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 4); + CU_ASSERT_EQUAL(stats.getnameinfo.last_params.addr, &s3); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s2); + set_getnameinfo_method(custom_gn2); + CU_ASSERT_EQUAL(custom_gni2_stats.called, 0); + SANDBOX_BEGIN; + i8 = getnameinfo((struct sockaddr*)&s4, sizeof(s4), rh4, sizeof(rh4), rs4, sizeof(rs4), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 5); + CU_ASSERT_EQUAL(custom_gni2_stats.called, 1); + set_getnameinfo_method(custom_gn1); + SANDBOX_BEGIN; + i1 = getnameinfo((struct sockaddr*)&s1, sizeof(s1), rh1, sizeof(rh1), rs1, sizeof(rs1), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 6); + CU_ASSERT_EQUAL(custom_gni2_stats.called, 1); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 3); + // TODO should also test that it doesn't cause any other problem + + // Testing gai_strerror + CU_ASSERT_EQUAL(monitored.gai_strerror, false); + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + const char *r1, *r2, *r3, *r4, *r5; + r1 = gai_strerror(EAI_MEMORY); + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + SANDBOX_BEGIN; + r2 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + CU_ASSERT_EQUAL(strcmp(r1, r2), 0); + monitored.gai_strerror = true; + r3 = gai_strerror(EAI_MEMORY); + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + CU_ASSERT_EQUAL(strcmp(r1, r3), 0); + SANDBOX_BEGIN; + r4 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.gai_strerror.called, 1); + CU_ASSERT_EQUAL(stats.gai_strerror.last_params, EAI_MEMORY); + CU_ASSERT_EQUAL(strcmp(r1, r4), 0); + CU_ASSERT_EQUAL(custom_gse1_stats.called, 0); + CU_ASSERT_EQUAL(custom_gse2_stats.called, 0); + set_gai_strerror_method(custom_gai_strerror1); + r5 = gai_strerror(EAI_MEMORY); + CU_ASSERT_EQUAL(strcmp(r1, r5), 0); // Actually, they are strictly identical... + SANDBOX_BEGIN; + r2 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.gai_strerror.called, 2); + CU_ASSERT_EQUAL(custom_gse1_stats.called, 1); + CU_ASSERT_EQUAL(strcmp(r2, "GSE1"), 0); + set_gai_strerror_method(NULL); + SANDBOX_BEGIN; + r3 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(strcmp(r3, r1), 0); + CU_ASSERT_EQUAL(custom_gse1_stats.called, 1); + set_gai_strerror_method(custom_gai_strerror2); + SANDBOX_BEGIN; + r4 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gse2_stats.called, 1); + CU_ASSERT_EQUAL(strcmp(r4, "GSE2"), 0); + set_gai_strerror_method(custom_gai_strerror1); + SANDBOX_BEGIN; + r5 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gse1_stats.called, 2); + CU_ASSERT_EQUAL(strcmp(r5, "GSE1"), 0); + monitored.gai_strerror = false; + SANDBOX_BEGIN; + r2 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gse1_stats.called, 2); + CU_ASSERT_EQUAL(strcmp(r2, r1), 0); +} + +void test_checked_freeaddrinfo() +{ + set_test_metadata("checked_freeaddrinfo", _("Tests the checked freeaddrinfo"), 1); + reinit_all_monitored(); + reinit_all_stats(); + set_check_freeaddrinfo(false); + reset_gai_fai_gstr_gni_methods(); + memset(&custom_gai1_stats, 0, sizeof(custom_gai1_stats)); + memset(&custom_gai2_stats, 0, sizeof(custom_gai2_stats)); + memset(&custom_fai1_stats, 0, sizeof(custom_fai1_stats)); + memset(&custom_fai2_stats, 0, sizeof(custom_fai2_stats)); + memset(&custom_gni1_stats, 0, sizeof(custom_gni1_stats)); + memset(&custom_gni2_stats, 0, sizeof(custom_gni2_stats)); + memset(&custom_fair_stats, 0, sizeof(custom_fair_stats)); + memset(&custom_gse1_stats, 0, sizeof(custom_gse1_stats)); + memset(&custom_gse2_stats, 0, sizeof(custom_gse2_stats)); + + set_freeaddrinfo_badarg_report(custom_checked_fai_reporter); + set_gai_methods(custom_gai1, custom_fai1); + set_gai_strerror_method(custom_gai_strerror1); + // First, let's check that it is not called if we don't activate the check, + // or if we don't activate monitoring for freeaddrinfo + // TODO check that its results are consistent only if we + int i1=-1, i2=-1, i3=-1, i4=-1; + struct addrinfo hints1, *res1, hints2, *res2, *res3, *res4, *res5, *res6, *res7, *res8; + memset(&hints1, 0, sizeof(hints1)); + memset(&hints2, 0, sizeof(hints2)); + hints2.ai_family = AF_INET; + hints2.ai_flags = AI_NUMERICHOST; + hints2.ai_socktype = SOCK_DGRAM; + monitored.freeaddrinfo = monitored.getaddrinfo = true; + SANDBOX_BEGIN; + i1 = getaddrinfo("127.0.0.1", "80", &hints1, &res1); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gai1_stats.called, 1); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 1); + CU_ASSERT_PTR_NULL(stats.getaddrinfo.addrinfo_list); + CU_ASSERT_EQUAL(i1, 0); + CU_ASSERT_EQUAL(res1->ai_family, AF_INET); + CU_ASSERT_EQUAL(custom_gai1("127.0.0.2", "80", &hints1, &res2), 0); // This shouldn't hurt + CU_ASSERT_EQUAL(custom_gai1_stats.called, 2); + (void)i4; + (void)res7; + (void)res8; + SANDBOX_BEGIN; + freeaddrinfo(res2); // Never seen before, so should call the correct method, but will not + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); // Called + CU_ASSERT_EQUAL(custom_fair_stats.called, 0); // Not called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 1); + monitored.freeaddrinfo = false; + CU_ASSERT_EQUAL(getaddrinfo("127.0.0.3", "80", &hints2, &res3), 0); // Use the real one + set_check_freeaddrinfo(true); + SANDBOX_BEGIN; + freeaddrinfo(res3); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); // Custom not called + CU_ASSERT_EQUAL(custom_fair_stats.called, 0); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 1); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 0); + // Now, let's see what happens if we activate it. + set_check_freeaddrinfo(true); + monitored.getaddrinfo = monitored.freeaddrinfo = true; + SANDBOX_BEGIN; + i2 = getaddrinfo("127.0.0.4", "80", &hints1, &res4); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gai1_stats.called, 3); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 2); + CU_ASSERT_PTR_NOT_NULL(stats.getaddrinfo.addrinfo_list); + if (stats.getaddrinfo.addrinfo_list) { + struct addrinfo_node_t *list = stats.getaddrinfo.addrinfo_list; + CU_ASSERT_PTR_EQUAL(list->addr_list, res4); + CU_ASSERT_PTR_NULL(list->next); + } + CU_ASSERT_EQUAL(i2, 0); + CU_ASSERT_EQUAL(custom_gai1("127.0.0.5", "80", &hints1, &res5), 0); + SANDBOX_BEGIN; + freeaddrinfo(res4); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 0); // Not called because OK + CU_ASSERT_EQUAL(custom_fai1_stats.called, 2); // Called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 0); + CU_ASSERT_PTR_NULL(stats.getaddrinfo.addrinfo_list); + SANDBOX_BEGIN; + freeaddrinfo(res5); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 1); // Called because fail + CU_ASSERT_EQUAL(custom_fai1_stats.called, 3); // Called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 1); + custom_fai1_prevent_free(res4); + SANDBOX_BEGIN; + freeaddrinfo(res4); // Double free + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 2); // Called because fail + CU_ASSERT_EQUAL(custom_fai1_stats.called, 4); // Called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 1); + // Disabling getaddrinfo should also cause problems... + // Warning, we need to set gais to default as gai will be library + set_gai_methods(NULL, NULL); + monitored.getaddrinfo = false; + SANDBOX_BEGIN; + i3 = getaddrinfo("127.0.0.6", "80", &hints2, &res6); + SANDBOX_END; + CU_ASSERT_EQUAL(i3, 0); + SANDBOX_BEGIN; + freeaddrinfo(res6); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 3); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 5); + set_check_freeaddrinfo(true); + SANDBOX_BEGIN; + freeaddrinfo(res1); // Should still disappear, but with error + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 6); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 4); + CU_ASSERT_EQUAL(custom_gai1("127.0.0.7", "80", &hints1, &res7), 0); + CU_ASSERT_PTR_NULL(stats.getaddrinfo.addrinfo_list); + set_gai_methods(NULL, custom_fai1); + set_freeaddrinfo_badarg_report(NULL); // Disable it + SANDBOX_BEGIN; + freeaddrinfo(res7); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fai1_stats.called, 5); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_fair_stats.called, 4); // Disabled, so not called +} + +void test_simple_getaddrinfo() +{ + set_test_metadata("simple_gai_fai", _("Tests simple_gai and simple_fai"), 1); + reinit_all_monitored(); + reinit_all_stats(); + set_check_freeaddrinfo(false); + reset_gai_fai_gstr_gni_methods(); + set_gai_methods(simple_getaddrinfo, simple_freeaddrinfo); + + int i1=1, i2=1, i3=1, i4=1, i5=1, i6=1, i7=1, i8=1, i9=1; + int i10=0, i11=0, i12=0, i13=0, i14=0, i15=0; + struct addrinfo hints1, *res1, hints2, *res2, hints3, *res3, hints4, *res4; + struct addrinfo hints5, *res5, hints6, *res6, hints7, *res7, hints8, *res8, *res9; + struct addrinfo hints10, *res10=NULL, hints11, *res11=NULL, hints12, *res12=NULL; + struct addrinfo hints13, *res13=NULL, hints14, *res14=NULL, hints15, *res15=NULL; + memset(&hints1, 0, sizeof(hints1)); + memset(&hints2, 0, sizeof(hints2)); + memset(&hints3, 0, sizeof(hints3)); + memset(&hints4, 0, sizeof(hints4)); + memset(&hints5, 0, sizeof(hints5)); + memset(&hints6, 0, sizeof(hints6)); + memset(&hints7, 0, sizeof(hints7)); + memset(&hints8, 0, sizeof(hints8)); + memset(&hints10, 0, sizeof(hints10)); + + hints1.ai_flags = AI_NUMERICHOST | AI_CANONNAME; + hints1.ai_family = AF_INET; + hints1.ai_socktype = SOCK_STREAM; + hints2.ai_flags = AI_NUMERICHOST | AI_PASSIVE; // Should ignore the AI_PASSIVE + hints2.ai_family = AF_INET; + hints2.ai_socktype = SOCK_DGRAM; + hints3.ai_flags = AI_NUMERICHOST; + hints3.ai_family = AF_INET; + hints3.ai_socktype = SOCK_DGRAM; + hints4.ai_flags = AI_PASSIVE; + hints4.ai_family = AF_INET; + hints4.ai_socktype = SOCK_DGRAM; + hints5.ai_flags = AI_PASSIVE; + hints5.ai_family = AF_INET6; + hints5.ai_socktype = 0; + hints6.ai_flags = AI_CANONNAME; + hints6.ai_family = AF_UNSPEC; + hints6.ai_socktype = SOCK_STREAM; + hints7.ai_flags = AI_NUMERICHOST; + hints7.ai_family = AF_INET6; + hints7.ai_socktype = SOCK_DGRAM; + hints8.ai_flags = AI_NUMERICHOST; + hints8.ai_family = AF_INET6; + hints8.ai_socktype = SOCK_STREAM; + + hints10.ai_flags = AI_NUMERICHOST; + hints10.ai_family = AF_INET6; + hints10.ai_socktype = SOCK_DGRAM; + hints11.ai_flags = AI_NUMERICHOST; + hints11.ai_family = AF_UNSPEC; + hints11.ai_socktype = 0; + hints12.ai_flags = AI_NUMERICHOST; + hints12.ai_family = AF_UNSPEC; + hints12.ai_socktype = 0; + hints13.ai_flags = AI_NUMERICHOST; + hints13.ai_family = AF_INET; + hints13.ai_socktype = 0; + hints14.ai_flags = AI_NUMERICHOST; + hints14.ai_family = AF_INET; + hints14.ai_socktype = SOCK_DGRAM; + hints15.ai_flags = AI_NUMERICHOST | AI_CANONNAME; + hints15.ai_family = AF_INET; + hints15.ai_socktype = SOCK_DGRAM; + + monitored.getaddrinfo = true; + monitored.freeaddrinfo = true; + + SANDBOX_BEGIN; + i1 = getaddrinfo("127.0.0.1", "80", &hints1, &res1); // ACTIVE, loopback, IPv4, canon name + i2 = getaddrinfo("192.168.1.1", "443", &hints2, &res2); // ACTIVE, IPv4, false passive + i3 = getaddrinfo(NULL, "80", &hints3, &res3); // ACTIVE, implicit loopback, IPv4 + i4 = getaddrinfo(NULL, "80", &hints4, &res4); // PASSIVE, IPv4 + i5 = getaddrinfo(NULL, "22", &hints5, &res5); // PASSIVE, IPv6 + i6 = getaddrinfo("::1", "22", &hints6, &res6); // ACTIVE, loopback, IPv6 + i7 = getaddrinfo("::2", NULL, &hints7, &res7); // ACTIVE, IPv6, no port + i8 = getaddrinfo(NULL, "443", &hints8, &res8); // ACTIVE, implicit loopback, IPv6 + i9 = getaddrinfo("1.2.3.4", "80", NULL, &res9); // ACTIVE, IPv4, no hint + // Errors + i10 = getaddrinfo("123.45.67.89", "80", &hints10, &res10); // IPv4 vs IPv6 + i11 = getaddrinfo("www.example.com", "80", &hints11, &res11); // non-numeric host + i12 = getaddrinfo("::1", "http", &hints12, &res12); // non-numeric serv + i13 = getaddrinfo("1::1", "80", &hints13, &res13); // IPv6 vs IPv4 + i14 = getaddrinfo(NULL, NULL, &hints14, &res14); // both NULL + i15 = getaddrinfo(NULL, "80", &hints15, &res15); // canon name without host + SANDBOX_END; + + CU_ASSERT_EQUAL(i1, 0); + CU_ASSERT_PTR_NOT_NULL(res1); + if (res1) { + CU_ASSERT_EQUAL(res1->ai_family, AF_INET); + CU_ASSERT_EQUAL(res1->ai_socktype, SOCK_STREAM); + CU_ASSERT_EQUAL(res1->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res1->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + struct in_addr ipaddr; + inet_pton(AF_INET, "127.0.0.1", &ipaddr); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, ipaddr.s_addr); + } + CU_ASSERT_PTR_NOT_NULL(res1->ai_canonname); + if (res1->ai_canonname) { + CU_ASSERT_EQUAL(strcmp(res1->ai_canonname, "C127.0.0.1"), 0); + } + } + CU_ASSERT_EQUAL(i2, 0); + CU_ASSERT_PTR_NOT_NULL(res2); + if (res2) { + CU_ASSERT_EQUAL(res2->ai_family, AF_INET); + CU_ASSERT_EQUAL(res2->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res2->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res2->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(443)); + struct in_addr ipaddr; + inet_pton(AF_INET, "192.168.1.1", &ipaddr); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, ipaddr.s_addr); + } + CU_ASSERT_PTR_NULL(res2->ai_canonname); + } + CU_ASSERT_EQUAL(i3, 0); + CU_ASSERT_PTR_NOT_NULL(res3); + if (res3) { + CU_ASSERT_EQUAL(res3->ai_family, AF_INET); + CU_ASSERT_EQUAL(res3->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res3->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res3->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + struct in_addr ipaddr; + inet_pton(AF_INET, "127.0.0.1", &ipaddr); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, ipaddr.s_addr); + } + CU_ASSERT_PTR_NULL(res3->ai_canonname); + } + CU_ASSERT_EQUAL(i4, 0); + CU_ASSERT_PTR_NOT_NULL(res4); + if (res4) { + CU_ASSERT_EQUAL(res4->ai_family, AF_INET); + CU_ASSERT_EQUAL(res4->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res4->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res4->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, INADDR_ANY); + } + CU_ASSERT_PTR_NULL(res4->ai_canonname); + } + CU_ASSERT_EQUAL(i5, 0); + CU_ASSERT_PTR_NOT_NULL(res5); + if (res5) { + CU_ASSERT_EQUAL(res5->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res5->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res5->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + CU_ASSERT_EQUAL(addr->sin6_port, htons(22)); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(in6addr_any), sizeof(struct in6_addr)), 0); + } + CU_ASSERT_PTR_NULL(res5->ai_canonname); + } + CU_ASSERT_EQUAL(i6, 0); + CU_ASSERT_PTR_NOT_NULL(res6); + if (res6) { + CU_ASSERT_EQUAL(res6->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res6->ai_socktype, SOCK_STREAM); + CU_ASSERT_EQUAL(res6->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res6->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + CU_ASSERT_EQUAL(addr->sin6_port, htons(22)); + struct in6_addr addr6; + inet_pton(AF_INET6, "::1", &addr6); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(addr6), sizeof(addr6)), 0); + } + CU_ASSERT_PTR_NOT_NULL(res6->ai_canonname); + if (res6->ai_canonname) { + CU_ASSERT_EQUAL(strcmp(res6->ai_canonname, "C::1"), 0); + } + } + CU_ASSERT_EQUAL(i7, 0); + CU_ASSERT_PTR_NOT_NULL(res7); + if (res7) { + CU_ASSERT_EQUAL(res7->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res7->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res7->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res7->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + struct in6_addr addr6; + inet_pton(AF_INET6, "::2", &addr6); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(addr6), sizeof(addr6)), 0); + } + CU_ASSERT_PTR_NULL(res7->ai_canonname); + } + CU_ASSERT_EQUAL(i8, 0); + CU_ASSERT_PTR_NOT_NULL(res8); + if (res8) { + CU_ASSERT_EQUAL(res8->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res8->ai_socktype, SOCK_STREAM); + CU_ASSERT_EQUAL(res8->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res8->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + CU_ASSERT_EQUAL(addr->sin6_port, htons(443)); + struct in6_addr addr6; + inet_pton(AF_INET6, "::1", &addr6); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(addr6), sizeof(addr6)), 0); + } + CU_ASSERT_PTR_NULL(res8->ai_canonname); + } + CU_ASSERT_EQUAL(i9, 0); + CU_ASSERT_PTR_NOT_NULL(res9); + if (res9) { + CU_ASSERT_EQUAL(res9->ai_family, AF_INET); + CU_ASSERT_EQUAL(res9->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res9->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res9->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + struct in_addr addr4; + inet_pton(AF_INET, "1.2.3.4", &addr4); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, addr4.s_addr); + } + CU_ASSERT_PTR_NULL(res9->ai_canonname); + } + // The failed ones + CU_ASSERT_EQUAL(i10, EAI_FAMILY); + CU_ASSERT_EQUAL(i11, EAI_NONAME); + CU_ASSERT_EQUAL(i12, EAI_NONAME); + CU_ASSERT_EQUAL(i13, EAI_FAMILY); + CU_ASSERT_EQUAL(i14, EAI_NONAME); + CU_ASSERT_EQUAL(i15, EAI_BADFLAGS); + + SANDBOX_BEGIN; + // Check freeaddrinfo + freeaddrinfo(res1); + freeaddrinfo(res2); + freeaddrinfo(res3); + freeaddrinfo(res4); + freeaddrinfo(res5); + freeaddrinfo(res6); + freeaddrinfo(res7); + freeaddrinfo(res8); + freeaddrinfo(res9); + // The following should still work + freeaddrinfo(res10); + freeaddrinfo(res11); + freeaddrinfo(res12); + freeaddrinfo(res13); + freeaddrinfo(res14); + SANDBOX_END; } -// Check failures: -// Recall we must use the failures struct, and assign its (call), (call)_ret and (call)_errno fields. -// And we have the fileds FAIL_ALWAYS, FAIL_NEVER, FAIL_FIRST, FAIL_SECOND, FAIL_THIRD and FAIL_TWICE int main(int argc, char **argv) { - RUN(test_wrapper_stats); + RUN(test_gai_substitutes, test_checked_freeaddrinfo, test_simple_getaddrinfo); } diff --git a/ci/test_network_recv/tests.c b/ci/test_network_recv/tests.c index 0371ea1..0dec8b9 100644 --- a/ci/test_network_recv/tests.c +++ b/ci/test_network_recv/tests.c @@ -498,6 +498,11 @@ void test_fragmented_recv_after() free(tab1); } +/* + * FIXME this test can sometimes fail, as it depends on the real-time property + * of the machine on which it is run. + * TODO Make this independent of the machine. Maybe remove the API... + */ void test_fragmented_recv_realtime() { set_test_metadata("fragmented_recv_realtime", _("Tests the use of real-time-intervals"), 1); From fb12d77c4fa18977263ccba00882acd0b5854138 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:18:52 +0200 Subject: [PATCH 41/53] Fix bugs in DNS wrappers - monitored.gai_strerror - memory leak when reset - missing implementations of setters of fai_report and check_fai - Fix simple_getaddrinfo so that it is more compliant to the true one - Add reset for all alternative methods --- student/CTester/wrap_network_dns.c | 77 +++++++++++++++++++++++------- student/CTester/wrap_network_dns.h | 16 ++++++- 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/student/CTester/wrap_network_dns.c b/student/CTester/wrap_network_dns.c index 6833bb4..378320a 100644 --- a/student/CTester/wrap_network_dns.c +++ b/student/CTester/wrap_network_dns.c @@ -38,11 +38,6 @@ getnameinfo_method_t getnameinfo_method = NULL; gai_strerror_method_t gai_strerror_method = NULL; freeaddrinfo_badarg_report_t freeaddrinfo_badarg_reporter = NULL; -// Used to record the addrinfo lists "returned" by getaddrinfo, in order to check for their deallocation via freeaddrinfo. -struct addrinfo_node_t { - struct addrinfo *addr_list; - struct addrinfo_node_t *next; -}; void add_result(struct addrinfo *res) { @@ -188,7 +183,7 @@ void __wrap_freeaddrinfo(struct addrinfo *res) const char * __wrap_gai_strerror(int ecode) { - if (! (wrap_monitoring && monitored.getaddrinfo)) { + if (! (wrap_monitoring && monitored.gai_strerror)) { return __real_gai_strerror(ecode); } stats.gai_strerror.called++; @@ -202,12 +197,30 @@ const char * __wrap_gai_strerror(int ecode) void reinit_stats_network_dns() { + // First, clean up the list of getaddrinfo returned lists + struct addrinfo_node_t *runner = stats.getaddrinfo.addrinfo_list; + while (runner != NULL) { + struct addrinfo_node_t *old = runner; + runner = runner->next; + /* Ideally, we should also free old->addr_list by using freeaddrinfo, + but as it is possible that the list should be deallocated + by a different function than the current freeaddrinfo, + it is safer to not attempt it and leave it as-is. + TODO: add the dedicated freeaddrinfo as a field of the nodes ;-) + */ + free(old); + } memset(&(stats.getaddrinfo), 0, sizeof(stats.getaddrinfo)); memset(&(stats.freeaddrinfo), 0, sizeof(stats.freeaddrinfo)); memset(&(stats.getnameinfo), 0, sizeof(stats.getnameinfo)); memset(&(stats.gai_strerror), 0, sizeof(stats.gai_strerror)); } +void set_check_freeaddrinfo(bool check) +{ + check_freeaddrinfo = check; +} + void set_getaddrinfo_method(getaddrinfo_method_t method) { @@ -231,16 +244,42 @@ void set_gai_strerror_method(gai_strerror_method_t method) } +void set_freeaddrinfo_badarg_report(freeaddrinfo_badarg_report_t reporter) +{ + freeaddrinfo_badarg_reporter = reporter; +} + +void reset_gai_fai_gstr_gni_methods() +{ + set_gai_methods(NULL, NULL); + set_getnameinfo_method(NULL); + set_gai_strerror_method(NULL); +} + int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res) { + if (node == NULL && hints != NULL && hints && (hints->ai_flags & AI_CANONNAME) != 0) { + return EAI_BADFLAGS; + } + if (node == NULL && serv == NULL) { + return EAI_NONAME; + } uint8_t buf[sizeof(struct in6_addr)]; memset(buf, 0, sizeof(buf)); - int family = hints->ai_family; + int family = hints ? hints->ai_family : AF_UNSPEC; if (node == NULL) { - if (family == AF_INET) { - node = "127.0.0.1"; + if (hints && (hints->ai_flags & AI_PASSIVE)) { + if (family == AF_INET) { + node = "0.0.0.0"; + } else { + node = "::"; + } } else { - node = "::1"; + if (family == AF_INET) { + node = "127.0.0.1"; + } else { + node = "::1"; + } } } // Let's try IPv4 @@ -260,7 +299,13 @@ int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo return EAI_NONAME; } } - if (hints->ai_family != AF_UNSPEC && family != hints->ai_family) + char *endservptr = NULL; + in_port_t port = (serv == NULL) ? 0 : htons((uint16_t)(strtol(serv, &endservptr, 10))); + if (serv != NULL && (endservptr == NULL || *endservptr != '\0')) { + // As we assume that we only deal with numeric hosts and service numbers, this is invalid + return EAI_NONAME; + } + if (hints && hints->ai_family != AF_UNSPEC && family != hints->ai_family) return EAI_FAMILY; // Converted, and family identified *res = malloc(sizeof(struct addrinfo)); @@ -269,9 +314,9 @@ int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo return EAI_MEMORY; rp->ai_flags = 0; rp->ai_family = family; - rp->ai_socktype = hints->ai_socktype; - rp->ai_protocol = hints->ai_protocol; - if ((hints->ai_flags & AI_CANONNAME) != 0) { + rp->ai_socktype = hints ? hints->ai_socktype : SOCK_DGRAM; + rp->ai_protocol = hints ? hints->ai_protocol : 0; + if (hints && (hints->ai_flags & AI_CANONNAME) != 0) { rp->ai_canonname = malloc(strlen(node) + 2); if (rp->ai_canonname == NULL) { free(rp); @@ -293,7 +338,7 @@ int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo return EAI_MEMORY; } addr->sin_family = AF_INET; - addr->sin_port = htons((uint16_t)(strtol(serv, NULL, 10))); + addr->sin_port = port; memcpy(&(addr->sin_addr), buf, sizeof(struct in_addr)); rp->ai_addr = (struct sockaddr*)addr; rp->ai_addrlen = sizeof(struct sockaddr_in); @@ -307,7 +352,7 @@ int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo } memset(addr, 0, sizeof(*addr)); addr->sin6_family = AF_INET6; - addr->sin6_port = htons((uint16_t)(strtol(serv, NULL, 10))); + addr->sin6_port = port; memcpy(&(addr->sin6_addr), buf, sizeof(struct in6_addr)); rp->ai_addr = (struct sockaddr*)addr; rp->ai_addrlen = sizeof(struct sockaddr_in6); diff --git a/student/CTester/wrap_network_dns.h b/student/CTester/wrap_network_dns.h index eba1dc9..39d13f4 100644 --- a/student/CTester/wrap_network_dns.h +++ b/student/CTester/wrap_network_dns.h @@ -28,7 +28,16 @@ struct params_getaddrinfo_t { struct addrinfo **res; }; -struct addrinfo_node_t; +// Used to record the addrinfo lists "returned" by getaddrinfo, in order to check for their deallocation via freeaddrinfo. +/** + * Node for a list of all returned lists of struct addrinfo + * returned by getaddrinfo. + * This is used to check that they are deallocated via freeaddrinfo correctly. + */ +struct addrinfo_node_t { + struct addrinfo *addr_list; + struct addrinfo_node_t *next; +}; struct stats_getaddrinfo_t { int called; // number of times the getaddrinfo call has been issued @@ -160,6 +169,11 @@ typedef void (*freeaddrinfo_badarg_report_t)(); void set_freeaddrinfo_badarg_report(freeaddrinfo_badarg_report_t reporter); +/** + * Resets all methods to the default, library-provided ones. + */ +void reset_gai_fai_gstr_gni_methods(); + /** * Replaces getaddrinfo by a version which doesn't call DNS. * node should be a numerical address, which will be converted using inet_pton From eddc70a9a4886be165047e4fd490409d1d43e5bc Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:22:41 +0200 Subject: [PATCH 42/53] Fix bug in sockets + member name changes for ptr arguments --- student/CTester/wrap_network_socket.c | 10 +++++----- student/CTester/wrap_network_socket.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/student/CTester/wrap_network_socket.c b/student/CTester/wrap_network_socket.c index e09b1ce..3deba20 100644 --- a/student/CTester/wrap_network_socket.c +++ b/student/CTester/wrap_network_socket.c @@ -79,7 +79,7 @@ int __wrap_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) stats.accept.last_params = (struct params_accept_t) { .sockfd = sockfd, .addr = addr, - .addrlen_ptr = addrlen + .addrlen = addrlen }; // Reinit stats stats.accept.last_returns.addrlen = 0; @@ -199,7 +199,7 @@ int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) if (! (ret == -1 && errno == EFAULT)) { struct pollfd *tmp = malloc(nfds * sizeof(struct pollfd)); if (tmp) { - memcpy(fds, tmp, nfds * sizeof(struct pollfd)); + memcpy(tmp, fds, nfds * sizeof(struct pollfd)); stats.poll.last_params.fds_copy = tmp; } } @@ -248,7 +248,7 @@ ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct soc .len = len, .flags = flags, .src_addr = src_addr, - .addrlen_ptr = addrlen + .addrlen = addrlen }; stats.recvfrom.last_returned_addr.addrlen = 0; memset(&(stats.recvfrom.last_returned_addr.src_addr), 0, sizeof(struct sockaddr_storage)); @@ -365,7 +365,7 @@ ssize_t __wrap_sendto(int sockfd, const void *buf, size_t len, int flags, const .buf = buf, .len = len, .flags = flags, - .dest_addr_ptr = dest_addr, + .dest_addr = dest_addr, //.dest_addr = (struct sockaddr_storage)0, .addrlen = addrlen }; @@ -392,7 +392,7 @@ ssize_t __wrap_sendmsg(int sockfd, const struct msghdr *msg, int flags) stats.send_all.called++; stats.sendmsg.last_params = (struct params_sendmsg_t) { .sockfd = sockfd, - .msg_ptr = msg, + .msg = msg, //.msg = (struct msghdr)0, .flags = flags }; diff --git a/student/CTester/wrap_network_socket.h b/student/CTester/wrap_network_socket.h index 8b23d17..2f35eb4 100644 --- a/student/CTester/wrap_network_socket.h +++ b/student/CTester/wrap_network_socket.h @@ -27,7 +27,7 @@ struct params_accept_t { int sockfd; struct sockaddr *addr; - socklen_t *addrlen_ptr; + socklen_t *addrlen; }; /** @@ -127,7 +127,7 @@ struct params_recvfrom_t { size_t len; int flags; struct sockaddr *src_addr; - socklen_t *addrlen_ptr; + socklen_t *addrlen; }; struct params_recvmsg_t { @@ -199,13 +199,13 @@ struct params_sendto_t { const void *buf; size_t len; int flags; - const struct sockaddr *dest_addr_ptr; + const struct sockaddr *dest_addr; //struct sockaddr_storage dest_addr; socklen_t addrlen; }; struct params_sendmsg_t { int sockfd; - const struct msghdr *msg_ptr; + const struct msghdr *msg; //struct msghdr msg; int flags; }; From 8fa48fd6d0dcde24e6c6752a343553abb4ca168d Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:26:03 +0200 Subject: [PATCH 43/53] Add init, cleanup methods for all mutex wrappers --- student/CTester/wrap_mutex.c | 68 +++++++++++++++++++++++++++++++----- student/CTester/wrap_mutex.h | 14 +++++--- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/student/CTester/wrap_mutex.c b/student/CTester/wrap_mutex.c index 7b19c17..643e421 100644 --- a/student/CTester/wrap_mutex.c +++ b/student/CTester/wrap_mutex.c @@ -7,12 +7,12 @@ //int pthread_mutex_lock(pthread_mutex_t *mutex); //int pthread_mutex_trylock(pthread_mutex_t *mutex); -//int pthread_mutex_unlock(pthread_mutex_t *mutex); +//int pthread_mutex_unlock(pthread_mutex_t *mutex); int __real_pthread_mutex_lock(pthread_mutex_t *mutex); int __real_pthread_mutex_trylock(pthread_mutex_t *mutex); -int __real_pthread_mutex_unlock(pthread_mutex_t *mutex); +int __real_pthread_mutex_unlock(pthread_mutex_t *mutex); int __real_pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int __real_pthread_mutex_destroy(pthread_mutex_t *mutex); @@ -22,27 +22,79 @@ extern struct wrap_monitor_t monitored; extern struct wrap_fail_t failures; -void init_mutex() { +void init_pthread_mutex_init() { // nothing to do } - -void clean_mutex() { +void init_pthread_mutex_lock() { + // nothing to do +} +void init_pthread_mutex_trylock() { + // nothing to do +} +void init_pthread_mutex_unlock() { + // nothing to do +} +void init_pthread_mutex_destroy() { // nothing to do } +void init_mutex() { + init_pthread_mutex_init(); + init_pthread_mutex_lock(); + init_pthread_mutex_trylock(); + init_pthread_mutex_unlock(); + init_pthread_mutex_destroy(); +} -void resetstats_mutex() { +void clean_pthread_mutex_init() { + // nothing to do +} +void clean_pthread_mutex_lock() { + // nothing to do +} +void clean_pthread_mutex_trylock() { + // nothing to do +} +void clean_pthread_mutex_unlock() { + // nothing to do +} +void clean_pthread_mutex_destroy() { + // nothing to do +} +void clean_mutex() { + clean_pthread_mutex_init(); + clean_pthread_mutex_lock(); + clean_pthread_mutex_trylock(); + clean_pthread_mutex_unlock(); + clean_pthread_mutex_destroy(); +} +void resetstats_pthread_mutex_init() { + stats.pthread_mutex_init.called=0; + stats.pthread_mutex_init.last_return=0; +} +void resetstats_pthread_mutex_lock() { stats.pthread_mutex_lock.called=0; stats.pthread_mutex_lock.last_return=0; +} +void resetstats_pthread_mutex_trylock() { stats.pthread_mutex_trylock.called=0; stats.pthread_mutex_trylock.last_return=0; +} +void resetstats_pthread_mutex_unlock() { stats.pthread_mutex_unlock.called=0; stats.pthread_mutex_unlock.last_return=0; - stats.pthread_mutex_init.called=0; - stats.pthread_mutex_init.last_return=0; +} +void resetstats_pthread_mutex_destroy() { stats.pthread_mutex_destroy.called=0; stats.pthread_mutex_destroy.last_return=0; } +void resetstats_mutex() { + resetstats_pthread_mutex_init(); + resetstats_pthread_mutex_lock(); + resetstats_pthread_mutex_trylock(); + resetstats_pthread_mutex_unlock(); + resetstats_pthread_mutex_destroy(); +} int __wrap_pthread_mutex_destroy(pthread_mutex_t *mutex) { if(!wrap_monitoring || !monitored.pthread_mutex_destroy) { diff --git a/student/CTester/wrap_mutex.h b/student/CTester/wrap_mutex.h index 15f2f72..a111b43 100644 --- a/student/CTester/wrap_mutex.h +++ b/student/CTester/wrap_mutex.h @@ -11,7 +11,7 @@ struct stats_pthread_mutex_lock_t { int called; // number of times the system call has been called - pid_t last_return; // last return value + pid_t last_return; // last return value pthread_mutex_t *last_arg; // last mutex passed as argument }; @@ -22,7 +22,7 @@ void resetstats_pthread_mutex_lock(); struct stats_pthread_mutex_trylock_t { int called; // number of times the system call has been called - pid_t last_return; // last return value + pid_t last_return; // last return value pthread_mutex_t *last_arg; // last mutex passed as argument }; @@ -33,7 +33,7 @@ void resetstats_pthread_mutex_trylock(); struct stats_pthread_mutex_unlock_t { int called; // number of times the system call has been called - pid_t last_return; // last return value + pid_t last_return; // last return value pthread_mutex_t *last_arg; // last mutex passed as argument }; @@ -44,7 +44,7 @@ void resetstats_pthread_mutex_unlock(); struct stats_pthread_mutex_init_t { int called; // number of times the system call has been called - pid_t last_return; // last return value + pid_t last_return; // last return value pthread_mutex_t *last_arg; // last mutex passed as argument }; @@ -55,7 +55,7 @@ void resetstats_pthread_mutex_init(); struct stats_pthread_mutex_destroy_t { int called; // number of times the system call has been called - pid_t last_return; // last return value + pid_t last_return; // last return value pthread_mutex_t *last_arg; // last mutex passed as argument }; @@ -64,5 +64,9 @@ void init_pthread_mutex_destroy(); void clean_pthread_mutex_destroy(); void resetstats_pthread_mutex_destroy(); +void init_mutex(); +void clean_mutex(); +void resetstats_mutex(); + #endif // __WRAP_MUTEX_H_ From 2d4d4f0226bd4de74fb8b5a099069c8c2097ceb6 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:29:38 +0200 Subject: [PATCH 44/53] Move fd_is_read_buffered + clean whitespace --- student/CTester/read_write.h | 5 ++ student/CTester/wrap_file.c | 90 ++++++++++++++------------- student/CTester/wrap_malloc.c | 4 +- student/CTester/wrap_network_socket.c | 2 - 4 files changed, 53 insertions(+), 48 deletions(-) diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h index ce8f997..b53d23b 100644 --- a/student/CTester/read_write.h +++ b/student/CTester/read_write.h @@ -161,6 +161,11 @@ struct read_buffer_t { */ int set_read_buffer(int fd, const struct read_buffer_t *buf); +/** + * Is the file descriptor fd associated to some read buffer? + */ +bool fd_is_read_buffered(int fd); + /** * Returns the amount of bytes read from the previously provided buffer * for this file descriptor, or -1 if the file descriptor is not associated diff --git a/student/CTester/wrap_file.c b/student/CTester/wrap_file.c index e90d975..1bddbea 100644 --- a/student/CTester/wrap_file.c +++ b/student/CTester/wrap_file.c @@ -25,14 +25,12 @@ extern bool wrap_monitoring; extern struct wrap_stats_t stats; extern struct wrap_monitor_t monitored; extern struct wrap_fail_t failures; -extern struct wrap_log_t logs; +extern struct wrap_log_t logs; // Unused for now... /** * Auxiliary functions and structures for implementing partial-returns in read/write. */ -extern bool fd_is_read_buffered(int fd); - extern ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags); extern struct read_fd_table_t read_fd_table; @@ -45,13 +43,13 @@ extern struct read_fd_table_t read_fd_table; int __wrap_open(char *pathname, int flags, mode_t mode) { if(!wrap_monitoring || !monitored.open) { - return __real_open(pathname,flags,mode); + return __real_open(pathname,flags,mode); } stats.open.called++; stats.open.last_params.pathname=pathname; stats.open.last_params.flags=flags; stats.open.last_params.mode=mode; - + if (FAIL(failures.open)) { failures.open=NEXT(failures.open); errno=failures.open_errno; @@ -70,12 +68,12 @@ int __wrap_creat(char *pathname, mode_t mode) { if(!wrap_monitoring || !monitored.creat) { - return __real_creat(pathname,mode); + return __real_creat(pathname,mode); } stats.creat.called++; stats.creat.last_params.pathname=pathname; stats.creat.last_params.mode=mode; - + if (FAIL(failures.creat)) { failures.creat=NEXT(failures.creat); errno=failures.creat_errno; @@ -93,11 +91,11 @@ int __wrap_creat(char *pathname, mode_t mode) { int __wrap_close(int fd){ if(!wrap_monitoring || !monitored.close) { - return __real_close(fd); + return __real_close(fd); } stats.close.called++; stats.close.last_params.fd=fd; - + if (FAIL(failures.close)) { failures.close=NEXT(failures.close); errno=failures.close_errno; @@ -115,13 +113,13 @@ int __wrap_close(int fd){ ssize_t __wrap_read(int fd, void *buf, size_t count){ if(!wrap_monitoring || !monitored.read) { - return __real_read(fd,buf,count); + return __real_read(fd,buf,count); } stats.read.called++; stats.read.last_params.fd=fd; stats.read.last_params.buf=buf; stats.read.last_params.count=count; - + if (FAIL(failures.read)) { failures.read=NEXT(failures.read); errno=failures.read_errno; @@ -145,13 +143,13 @@ ssize_t __wrap_read(int fd, void *buf, size_t count){ ssize_t __wrap_write(int fd, void *buf, size_t count){ if(!wrap_monitoring || !monitored.write) { - return __real_write(fd,buf,count); + return __real_write(fd,buf,count); } stats.write.called++; stats.write.last_params.fd=fd; stats.write.last_params.buf=buf; stats.write.last_params.count=count; - + if (FAIL(failures.write)) { failures.write=NEXT(failures.write); errno=failures.write_errno; @@ -167,14 +165,14 @@ ssize_t __wrap_write(int fd, void *buf, size_t count){ } int __wrap_stat(char *path, struct stat *buf) { - + if(!wrap_monitoring || !monitored.stat) { -return __real_stat(path,buf); + return __real_stat(path,buf); } stats.stat.called++; stats.stat.last_params.path=path; stats.stat.last_params.buf=buf; - + if (FAIL(failures.stat)) { failures.stat=NEXT(failures.stat); errno=failures.stat_errno; @@ -184,19 +182,21 @@ return __real_stat(path,buf); failures.stat=NEXT(failures.stat); // did not fail int ret=__real_stat(path,buf); - stats.stat.returned_stat.st_dev=buf->st_dev; - stats.stat.returned_stat.st_ino=buf->st_ino; - stats.stat.returned_stat.st_mode=buf->st_mode; - stats.stat.returned_stat.st_nlink=buf->st_nlink; - stats.stat.returned_stat.st_uid=buf->st_uid; - stats.stat.returned_stat.st_gid=buf->st_gid; - stats.stat.returned_stat.st_rdev=buf->st_rdev; - stats.stat.returned_stat.st_size=buf->st_size; - stats.stat.returned_stat.st_blksize=buf->st_blksize; - stats.stat.returned_stat.st_blocks=buf->st_blocks; - stats.stat.returned_stat.st_atime=buf->st_atime; - stats.stat.returned_stat.st_mtime=buf->st_mtime; - stats.stat.returned_stat.st_ctime=buf->st_ctime; + if (buf) { + stats.stat.returned_stat.st_dev = buf->st_dev; + stats.stat.returned_stat.st_ino = buf->st_ino; + stats.stat.returned_stat.st_mode = buf->st_mode; + stats.stat.returned_stat.st_nlink = buf->st_nlink; + stats.stat.returned_stat.st_uid = buf->st_uid; + stats.stat.returned_stat.st_gid = buf->st_gid; + stats.stat.returned_stat.st_rdev = buf->st_rdev; + stats.stat.returned_stat.st_size = buf->st_size; + stats.stat.returned_stat.st_blksize = buf->st_blksize; + stats.stat.returned_stat.st_blocks = buf->st_blocks; + stats.stat.returned_stat.st_atime = buf->st_atime; + stats.stat.returned_stat.st_mtime = buf->st_mtime; + stats.stat.returned_stat.st_ctime = buf->st_ctime; + } stats.stat.last_return=ret; return ret; @@ -210,7 +210,7 @@ int __wrap_fstat(int fd, struct stat *buf) { stats.fstat.called++; stats.fstat.last_params.fd=fd; stats.fstat.last_params.buf=buf; - + if (FAIL(failures.fstat)) { failures.fstat=NEXT(failures.fstat); errno=failures.fstat_errno; @@ -219,19 +219,21 @@ int __wrap_fstat(int fd, struct stat *buf) { failures.fstat=NEXT(failures.fstat); // did not fail int ret=__real_fstat(fd,buf); - stats.fstat.returned_stat.st_dev=buf->st_dev; - stats.fstat.returned_stat.st_ino=buf->st_ino; - stats.fstat.returned_stat.st_mode=buf->st_mode; - stats.fstat.returned_stat.st_nlink=buf->st_nlink; - stats.fstat.returned_stat.st_uid=buf->st_uid; - stats.fstat.returned_stat.st_gid=buf->st_gid; - stats.fstat.returned_stat.st_rdev=buf->st_rdev; - stats.fstat.returned_stat.st_size=buf->st_size; - stats.fstat.returned_stat.st_blksize=buf->st_blksize; - stats.fstat.returned_stat.st_blocks=buf->st_blocks; - stats.fstat.returned_stat.st_atime=buf->st_atime; - stats.fstat.returned_stat.st_mtime=buf->st_mtime; - stats.fstat.returned_stat.st_ctime=buf->st_ctime; + if (buf) { + stats.fstat.returned_stat.st_dev = buf->st_dev; + stats.fstat.returned_stat.st_ino = buf->st_ino; + stats.fstat.returned_stat.st_mode = buf->st_mode; + stats.fstat.returned_stat.st_nlink = buf->st_nlink; + stats.fstat.returned_stat.st_uid = buf->st_uid; + stats.fstat.returned_stat.st_gid = buf->st_gid; + stats.fstat.returned_stat.st_rdev = buf->st_rdev; + stats.fstat.returned_stat.st_size = buf->st_size; + stats.fstat.returned_stat.st_blksize = buf->st_blksize; + stats.fstat.returned_stat.st_blocks = buf->st_blocks; + stats.fstat.returned_stat.st_atime = buf->st_atime; + stats.fstat.returned_stat.st_mtime = buf->st_mtime; + stats.fstat.returned_stat.st_ctime = buf->st_ctime; + } stats.fstat.last_return=ret; return ret; @@ -239,7 +241,7 @@ int __wrap_fstat(int fd, struct stat *buf) { } off_t __wrap_lseek(int fd, off_t offset, int whence) { - + if(!wrap_monitoring || !monitored.lseek) { return __real_lseek(fd,offset,whence); } diff --git a/student/CTester/wrap_malloc.c b/student/CTester/wrap_malloc.c index eb345d1..cd5e954 100644 --- a/student/CTester/wrap_malloc.c +++ b/student/CTester/wrap_malloc.c @@ -1,4 +1,4 @@ -/* +/* * Wrapper for malloc, free and calloc * * This program is free software: you can redistribute it and/or modify @@ -80,7 +80,7 @@ void * __wrap_malloc(size_t size) { failures.malloc=NEXT(failures.malloc); return failures.malloc_ret; } - stats.memory.used+=size; + stats.memory.used+=size; // In theory, we shouldn't update it if the call fails... But anyway. failures.malloc=NEXT(failures.malloc); void *ptr=__real_malloc(size); stats.malloc.last_return=ptr; diff --git a/student/CTester/wrap_network_socket.c b/student/CTester/wrap_network_socket.c index 3deba20..ffc356f 100644 --- a/student/CTester/wrap_network_socket.c +++ b/student/CTester/wrap_network_socket.c @@ -49,8 +49,6 @@ extern struct wrap_fail_t failures; /** * Auxiliary functions and structures for use by implementers for read/write */ -extern bool fd_is_read_buffered(int fd); - extern ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags); extern struct read_fd_table_t read_fd_table; From 080cc6f4339f55d81d3e20c28ef894fee7bb7bdc Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:31:18 +0200 Subject: [PATCH 45/53] Add support for autotest (attempt) --- run.py | 17 ++++++++++++++--- student/Makefile | 12 ++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/run.py b/run.py index 0d4fcbc..f8555f3 100644 --- a/run.py +++ b/run.py @@ -25,8 +25,18 @@ feedback.set_global_feedback(rst.get_codeblock('', make_output), True) exit(0) else: - feedback.set_global_result("success") - feedback.set_global_feedback("- Votre code compile.\n") + # Cppcheck + p = subprocess.Popen(shlex.split("make check"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + cppcheck_output = p.communicate()[0].decode('utf-8') + if p.returncode: + feedback.set_tag("cppcheck", True) + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code avec ``cppcheck`` a échoué. Voici le message de sortie de la commande ``make check`` :") + feedback.set_global_feedback(rst.get_codeblock('', cppcheck_output), True) + exit(0) + else: + feedback.set_global_result("success") + feedback.set_global_feedback("- Votre code compile.\n") # Parse banned functions try: @@ -53,7 +63,8 @@ # Run the code in a parallel container p = subprocess.Popen(shlex.split("run_student --time 20 --hard-time 60 ./tests LANGUAGE={}".format(LANG)), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) -p.communicate() +o, e = p.communicate() +print(o.decode("utf-8")) # If run failed, exit with "failed" result if p.returncode: diff --git a/student/Makefile b/student/Makefile index 4f2382c..9d571c9 100644 --- a/student/Makefile +++ b/student/Makefile @@ -1,10 +1,13 @@ +# Makefile for tasks using CTester CC=gcc +CPP=cppcheck EXEC=tests +CPPFLAGS=--error-exitcode=1 LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic SRC=$(sort $(wildcard *.c)) $(sort $(wildcard CTester/*.c)) OBJ=$(SRC:.c=.o) CFLAGS = -Wall -Werror -Wextra -Wshadow -CFLAGS += -std=gnu99 +CFLAGS += -std=gnu99 -pedantic #CFLAGS += -DC99 CFLAGS += -ICTester # to include the folder. DEBUGFLAGS = -g -Wno-unused-variable -Wno-unused-parameter @@ -27,7 +30,7 @@ all: $(EXEC) $(CC) $(CFLAGS) -c -o $@ $< $(EXEC): $(OBJ) - $(CC) $(WRAP) -o $@ $(OBJ) $(LDFLAGS) + $(CC) $(CFLAGS) $(WRAP) -o $@ $(OBJ) $(LDFLAGS) create-po: mkdir -p po/fr/ @@ -43,10 +46,15 @@ compile-mo: mkdir -p fr/LC_MESSAGES/ cp po/fr/tests.mo fr/LC_MESSAGES/tests.mo +check: $(SRC) + $(CPP) $(CPPFLAGS) $< + clean: rm -f $(EXEC) $(OBJ) + #clr-aux: # rm -f $(INC) $(ASM) + rebuild: clean all From 75be8f103a33f0a92ccc6760e110c79114662782 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sun, 4 Aug 2019 00:42:30 +0200 Subject: [PATCH 46/53] Removing -pedantic as it clashes with RUN's void* and arithmetic --- student/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/student/Makefile b/student/Makefile index 9d571c9..636783f 100644 --- a/student/Makefile +++ b/student/Makefile @@ -7,7 +7,7 @@ LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic SRC=$(sort $(wildcard *.c)) $(sort $(wildcard CTester/*.c)) OBJ=$(SRC:.c=.o) CFLAGS = -Wall -Werror -Wextra -Wshadow -CFLAGS += -std=gnu99 -pedantic +CFLAGS += -std=gnu99 #CFLAGS += -DC99 CFLAGS += -ICTester # to include the folder. DEBUGFLAGS = -g -Wno-unused-variable -Wno-unused-parameter From c82b71a0a9553d929f7c142845337fb36d0e80f4 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:32:20 +0200 Subject: [PATCH 47/53] When copying CTester, don't overwrite existing files --- run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run b/run index 11da6a4..6e2bd10 100644 --- a/run +++ b/run @@ -25,7 +25,7 @@ def copy_CTester(): return 0 # Use it right here elif has_CTester("/common/CTester"): # Copy it to here - p = subprocess.Popen(shlex.split("cp -r /common/CTester/* ."), stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(shlex.split("cp -n -r /common/CTester/* ."), stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (cp_output, cp_errput) = p.communicate() if p.returncode: print("Unable to move CTester", file=sys.stderr) From 7ddb2f61fd1c83d2bbc35d72d489e4473143716a Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Wed, 31 Jul 2019 01:29:45 +0200 Subject: [PATCH 48/53] Add documentation in source files, and license headers. Should be split. --- student/CTester/CTester.c | 17 ++- student/CTester/CTester.h | 17 +++ student/CTester/read_write.c | 16 +++ student/CTester/read_write.h | 21 ++-- student/CTester/trap.c | 15 +++ student/CTester/trap.h | 39 ++++++- student/CTester/util_inet.c | 15 +++ student/CTester/util_inet.h | 32 +++++- student/CTester/util_sockets.c | 16 +++ student/CTester/util_sockets.h | 18 ++++ student/CTester/wrap.h | 18 ++++ student/CTester/wrap_file.c | 15 ++- student/CTester/wrap_file.h | 17 +++ student/CTester/wrap_getpid.c | 14 +++ student/CTester/wrap_getpid.h | 35 +++++- student/CTester/wrap_malloc.c | 1 - student/CTester/wrap_malloc.h | 6 +- student/CTester/wrap_mutex.c | 14 +++ student/CTester/wrap_mutex.h | 16 +++ student/CTester/wrap_network_dns.h | 146 ++++++++++++++++++++++---- student/CTester/wrap_network_inet.h | 14 ++- student/CTester/wrap_network_socket.h | 64 ++++++----- student/CTester/wrap_sleep.c | 15 +++ student/CTester/wrap_sleep.h | 37 ++++++- 24 files changed, 545 insertions(+), 73 deletions(-) diff --git a/student/CTester/CTester.c b/student/CTester/CTester.c index be68a30..dc678de 100644 --- a/student/CTester/CTester.c +++ b/student/CTester/CTester.c @@ -1,3 +1,18 @@ +/* + * Main CTester file, containing all base functions and structures. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #define _GNU_SOURCE #include @@ -289,7 +304,7 @@ int run_tests(int argc, char *argv[], void *tests[], int nb_tests) { // Code for detecting properly double free errors mallopt(M_CHECK_ACTION, 1); // don't abort if double free true_stderr = dup(STDERR_FILENO); // preparing a non-blocking pipe for stderr - true_stdout = dup(STDOUT_FILENO); // preparing a non-blocking pipe for stderr + true_stdout = dup(STDOUT_FILENO); // preparing a non-blocking pipe for stdout fstdout = fdopen(true_stdout, "w"); // We can't just copy-paste stdout and stderr fstderr = fdopen(true_stderr, "w"); // as these structures use the file descriptor diff --git a/student/CTester/CTester.h b/student/CTester/CTester.h index 6aef289..4322b68 100644 --- a/student/CTester/CTester.h +++ b/student/CTester/CTester.h @@ -1,3 +1,20 @@ +/** + * @file wrap.h + * Main CTester file, containing all base functions and structures. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef __CTESTER_MAIN_H__ #define __CTESTER_MAIN_H__ diff --git a/student/CTester/read_write.c b/student/CTester/read_write.c index db77a1d..87d4503 100644 --- a/student/CTester/read_write.c +++ b/student/CTester/read_write.c @@ -1,3 +1,19 @@ +/* + * Functions and structure for manipulating read and write system calls, + * as well as recv and send calls, in the presence of buffered data. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include diff --git a/student/CTester/read_write.h b/student/CTester/read_write.h index b53d23b..823e369 100644 --- a/student/CTester/read_write.h +++ b/student/CTester/read_write.h @@ -1,7 +1,9 @@ -/* +/** + * @file read_write.h * Functions and structure for manipulating read and write system calls, * as well as recv and send calls, in the presence of buffered data. - * + */ +/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -21,7 +23,8 @@ #include /** - * Functions and structures for manipulating read and write system calls, + * @class Read_Write + * Functions and structures for manipulating read and write system calls,s * as well as recv and send calls, in the presence of buffered data. * * An example use of these functions is with the recv system call on a socket. @@ -68,7 +71,7 @@ */ -/** +/* * Enable globally the socket monitoring functionnalities. A value of true * activates it, a value of false deactivates it. * This means socket will add the returned fd to the list of sockets, @@ -77,13 +80,13 @@ */ //int enable_socket_recv_send_monitoring(bool active); -/** +/* * In addition to enable_socket_recv_send_monitoring, the bind, connect, * listen, accept and shutdown calls check whether it is a socket. */ //int enable_socket_all_monitoring(bool active); -/** +/* * Enable globally the pipe monitoring functionnalities. * Currently it is not supported. Maybe in a future version... */ @@ -151,9 +154,9 @@ struct read_buffer_t { /** * Sets the data to be retrieved from read/recv, for partial-return, * when simulating fragmented arrival of data. - * - fd: file descriptor. - * - buf: the data to be received. - * Returns + * @param fd file descriptor. + * @param buf the data to be received. + * @return * - 0 if fd was not previously set to have a read_buffer_t * - 1 if fd has been previously set to have a read_buffer_t * - -1 if malloc error diff --git a/student/CTester/trap.c b/student/CTester/trap.c index 4df5371..9b416c3 100644 --- a/student/CTester/trap.c +++ b/student/CTester/trap.c @@ -1,3 +1,18 @@ +/* + * Functions for creating and destroying trapped buffers. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include diff --git a/student/CTester/trap.h b/student/CTester/trap.h index 8f1417f..e4de9bf 100644 --- a/student/CTester/trap.h +++ b/student/CTester/trap.h @@ -1,13 +1,48 @@ +/** + * @file trap.h + * Functions for creating and destroying trapped buffers. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef __CTESTER_TRAP_H__ #define __CTESTER_TRAP_H__ +/** + * Defines the type of the trapped buffer, i.e. which end of the buffer + * is trapped. + */ enum { - TRAP_LEFT, - TRAP_RIGHT + TRAP_LEFT, ///< Defines that the left (lower address) page of the buffer is trapped + TRAP_RIGHT ///< Defines that the right (upper address) page of the buffer is trapped }; +/** + * Creates a trapped buffer, of specified size, of the specified type, with + * the specified data optionally pre-filled, and with the specified + * access rights flag (from mprotect) applied to the buffer. The trapped page + * has PROT_NONE permissions. Returns the start address of the useful + * part of the buffer (i.e., not the trapped page, if TRAP_LEFT). + */ void *trap_buffer(size_t size, int type, int flags, void *data); + +/** + * Frees the trapped buffer referenced to by ptr. Note that for TRAP_LEFT, + * this is not the same pointer as the one returned by trap_buffer; rather, + * this is the address of the trapped page. Also, the size is not the size + * of the buffer itself, but of all the pages of the buffer+the trapped page. + */ int free_trap(void *ptr, size_t size); #endif // __CTESTER_TRAP_H__ diff --git a/student/CTester/util_inet.c b/student/CTester/util_inet.c index bb9c903..99c7578 100644 --- a/student/CTester/util_inet.c +++ b/student/CTester/util_inet.c @@ -1,3 +1,18 @@ +/* + * Utility functions related to htons, ntohs, htonl, ntohl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include "util_inet.h" diff --git a/student/CTester/util_inet.h b/student/CTester/util_inet.h index c5ea5f1..052a95d 100644 --- a/student/CTester/util_inet.h +++ b/student/CTester/util_inet.h @@ -1,6 +1,8 @@ /** + * @file util_inet.h * Utility functions related to htons, ntohs, htonl, ntohl - * + */ +/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -18,12 +20,40 @@ #include +/** + * Converts each short integer of the src array from host byte order + * to network byte order, to the dest array. + * @param src source array of short integer in host byte order + * @param dest destination array of will-be short ints in network byte order + * @param len length of each array + */ void htons_tab(uint16_t *src, uint16_t *dest, size_t len); +/** + * Converts each short integer of the src array from network byte order + * to host byte order, to the dest array. + * @param src source array of short integer in network byte order + * @param dest destination array of will-be short integers in host byte order + * @param len length of each array + */ void ntohs_tab(uint16_t *src, uint16_t *dest, size_t len); +/** + * Converts each integer of the src array from host byte order + * to network byte order, to the dest array. + * @param src source array of integer in host byte order + * @param dest destination array of will-be integers in network byte order + * @param len length of each array + */ void htonl_tab(uint32_t *src, uint32_t *dest, size_t len); +/** + * Converts each integer of the src array from network byte order + * to host byte order, to the dest array. + * @param src source array of integer in network byte order + * @param dest destination array of will-be integers in host byte order + * @param len length of each array + */ void ntohl_tab(uint32_t *src, uint32_t *dest, size_t len); #endif // __CTESTER_UTIL_INET_H__ diff --git a/student/CTester/util_sockets.c b/student/CTester/util_sockets.c index dce58c7..3e39b2d 100644 --- a/student/CTester/util_sockets.c +++ b/student/CTester/util_sockets.c @@ -1,3 +1,19 @@ +/* + * Utility functions and structure for creating and manipulating sockets, + * for creating and launching test servers and clients. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include diff --git a/student/CTester/util_sockets.h b/student/CTester/util_sockets.h index f5136e9..2f3707f 100644 --- a/student/CTester/util_sockets.h +++ b/student/CTester/util_sockets.h @@ -1,3 +1,21 @@ +/** + * @file util_sockets.h + * Utility functions and structure for creating and manipulating sockets, + * for creating and launching test servers and clients. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef __CTESTER_UTIL_SOCKETS_H__ #define __CTESTER_UTIL_SOCKETS_H__ diff --git a/student/CTester/wrap.h b/student/CTester/wrap.h index 811a19c..5bad9e9 100644 --- a/student/CTester/wrap.h +++ b/student/CTester/wrap.h @@ -1,3 +1,21 @@ +/** + * @file wrap.h + * Definition of all the wrapper structures: monitoring, failures, + * statistics. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef __WRAP_H_ #define __WRAP_H_ diff --git a/student/CTester/wrap_file.c b/student/CTester/wrap_file.c index 1bddbea..7efb462 100644 --- a/student/CTester/wrap_file.c +++ b/student/CTester/wrap_file.c @@ -1,4 +1,17 @@ -// wrapper for the file operations, open, read, write +/** + * Wrapper for open, creat, read, write, close, stats, fstat, lseek. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include #include diff --git a/student/CTester/wrap_file.h b/student/CTester/wrap_file.h index daaace7..992236a 100644 --- a/student/CTester/wrap_file.h +++ b/student/CTester/wrap_file.h @@ -1,3 +1,20 @@ +/** + * @file wrap_file.h + * Wrapper for open, creat, read, write, close, stats, fstat, lseek. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef __WRAP_FILE_H_ #define __WRAP_FILE_H_ diff --git a/student/CTester/wrap_getpid.c b/student/CTester/wrap_getpid.c index ff2e525..7c19e0f 100644 --- a/student/CTester/wrap_getpid.c +++ b/student/CTester/wrap_getpid.c @@ -1,3 +1,17 @@ +/* + * Wrapper for getpid. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include #include #include diff --git a/student/CTester/wrap_getpid.h b/student/CTester/wrap_getpid.h index d326cca..3ba9203 100644 --- a/student/CTester/wrap_getpid.h +++ b/student/CTester/wrap_getpid.h @@ -1,3 +1,19 @@ +/** + * @file wrap_getpid.h + * Wrapper for getpid. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef __WRAP_GETPID_H_ #define __WRAP_GETPID_H_ @@ -8,14 +24,29 @@ // used by existing exercices. You might add some additional information // if it can help to validate some exercices +/** + * Statistics structure for the getpid system call + */ struct stats_getpid_t { - int called; // number of times the system call has been called - pid_t last_return; // last return value for getpid + int called; ///< number of times the system call has been called + pid_t last_return; ///< last return value for getpid }; +/** + * Initializes the structures needed for the wrapper of the getpid system call + */ void init_getpid(); + +/** + * Frees up any structure that was needed for the wrapper + * of the getpid system call + */ void clean_getpid(); + +/** + * Resets the statistics associated with the getpid system call + */ void resetstats_getpid(); #endif // __WRAP_GETPID_H_ diff --git a/student/CTester/wrap_malloc.c b/student/CTester/wrap_malloc.c index cd5e954..ce3f9e2 100644 --- a/student/CTester/wrap_malloc.c +++ b/student/CTester/wrap_malloc.c @@ -13,7 +13,6 @@ * along with this program. If not, see . */ - #include #include diff --git a/student/CTester/wrap_malloc.h b/student/CTester/wrap_malloc.h index e2898e3..f00d142 100644 --- a/student/CTester/wrap_malloc.h +++ b/student/CTester/wrap_malloc.h @@ -1,6 +1,8 @@ -/* +/** + * @file wrap_malloc.h * Wrapper for malloc, free and calloc - * + */ +/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or diff --git a/student/CTester/wrap_mutex.c b/student/CTester/wrap_mutex.c index 643e421..63b7c49 100644 --- a/student/CTester/wrap_mutex.c +++ b/student/CTester/wrap_mutex.c @@ -1,3 +1,17 @@ +/* + * Wrapper for the POSIX thread mutex functions. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include #include #include diff --git a/student/CTester/wrap_mutex.h b/student/CTester/wrap_mutex.h index a111b43..6ab6048 100644 --- a/student/CTester/wrap_mutex.h +++ b/student/CTester/wrap_mutex.h @@ -1,3 +1,19 @@ +/** + * @file wrap_mutex.h + * Wrapper for the POSIX thread mutex functions. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef __WRAP_MUTEX_H_ #define __WRAP_MUTEX_H_ diff --git a/student/CTester/wrap_network_dns.h b/student/CTester/wrap_network_dns.h index 39d13f4..59e545a 100644 --- a/student/CTester/wrap_network_dns.h +++ b/student/CTester/wrap_network_dns.h @@ -1,6 +1,9 @@ +/** + * @file wrap_network_dns.h + * Wrappers for getaddrinfo, freeaddrinfo, gai_strerror and getnameinfo, + * and other miscellaneous helper functions. + */ /* - * Wrapper for getaddrinfo, freeaddrinfo, gai_strerror and getnameinfo - * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -19,8 +22,13 @@ #include #include + // getaddrinfo parameters structures and statistics structure +/** + * Parameters structure for getaddrinfo +*/ + struct params_getaddrinfo_t { const char *node; const char *service; @@ -39,6 +47,9 @@ struct addrinfo_node_t { struct addrinfo_node_t *next; }; +/** + * Statistics structure for getaddrinfo + */ struct stats_getaddrinfo_t { int called; // number of times the getaddrinfo call has been issued struct params_getaddrinfo_t last_params; // parameters for the last monitored call @@ -46,8 +57,12 @@ struct stats_getaddrinfo_t { int last_return; // return value of the last monitored call issued }; + // getnameinfo parameters structures and statistics structure +/** + * Parameters structure for getnameinfo + */ struct params_getnameinfo_t { const struct sockaddr *addr; socklen_t addrlen; @@ -58,18 +73,32 @@ struct params_getnameinfo_t { int flags; }; +/** + * Statistics structure for getnameinfo + */ struct stats_getnameinfo_t { int called; // number of calls struct params_getnameinfo_t last_params; // parameters of the last monitored call int last_return; // return value of the last monitored call issued }; + // freeaddrinfo parameters structures and statistics structure +/** + * @struct params_freeaddrinfo_t + * Parameter structure for freeaddrinfo. + * There is no such structure, as there is only one parameter. + */ + +/** + * Statistics structure for freeaddrinfo + */ struct stats_freeaddrinfo_t { - int called; // number of calls - struct addrinfo *last_param; // parameter of the last monitored call issued - /** status of the last monitored call: + int called; /**< number of calls */ + struct addrinfo *last_param; /**< parameter of the last monitored call issued */ + /** + * Status of the last monitored call: * if check_freeaddrinfo is true * and last_param was not returned by getaddrinfo during the monitoring, * then status is 1; @@ -78,93 +107,152 @@ struct stats_freeaddrinfo_t { int status; }; + // gai_strerror parameters structures and statistics structure +/** + * @struct params_gai_strerror_t + * Parameter structure for gai_strerror. + * There is no such structure, as there is only one parameter. + */ + +/** + * Statistics structure for gai_strerror + */ struct stats_gai_strerror_t { - int called; // number of calls - int last_params; // parameter of the last monitored call - const char *last_return; // return value of the last monitored call + int called; ///< number of calls + int last_params; ///< parameter of the last monitored call + const char *last_return; ///< return value of the last monitored call }; // TODO Maybe add some init, clean and resetstats methods to these calls ? +/** + * Resets the statistics of the DNS-related methods. + */ void reinit_stats_network_dns(); /** - * When check is true, freeaddrinfo will check if its argument has been "returned" by an earlier call to getaddrinfo directly, and will report if it is not the case. + * Enforces checking that the passed structure has been previously returned by getaddrinfo. + * When check is true, freeaddrinfo will check if its argument has been "returned" + * by an earlier call to getaddrinfo directly, and will report if it is not the case. + * @see freeaddrinfo_badarg_report_t + * + * @param check do we enforce checking? */ void set_check_freeaddrinfo(bool check); /** - * Functions of this type should return in *res a valid list of struct addrinfo, - * suitable to be freed by a freeaddrinfo_method_t. + * Functions of this type should return in *res a valid list of + * struct addrinfo, in a manner compatible with getaddrinfo, + * suitable to be freed by a function of type freeaddrinfo_method_t. */ typedef int (*getaddrinfo_method_t)(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); + /** * Functions of this type should be able to free structures allocated - * by a method of type getaddrinfo_method_t. + * by a function of type getaddrinfo_method_t. */ typedef void (*freeaddrinfo_method_t)(struct addrinfo *res); /** - * if method is not NULL, replaces __real_getaddrinfo by the function "method" + * Specifies the actual function called by the getaddrinfo wrapper. + * + * If method is not NULL, replaces __real_getaddrinfo by the function "method" * as the real function executed by the wrapper. * If method is NULL, resets the method to __real_getaddrinfo. * This method only sets the substitute for the getaddrinfo function, * and not the substitute for the freeaddrinfo function; * incompatible definitions of these functions may result in memleaks * or double free if care is not taken. + * @see set_gai_methods() + * + * @param method the function called by the wrapper. */ void set_getaddrinfo_method(getaddrinfo_method_t method); /** - * Sets the getaddrinfo and freeaddrinfo methods to be used. + * Sets the getaddrinfo and freeaddrinfo replacement functions + * to be used in the sandbox. + * * Specifying NULL for a parameter means that the system will use * the default, system-defined method (__real_getaddrinfo or * __real_freeaddrinfo). * This is especially needed as the POSIX standard doesn't specify the way - * the list of struct addrinfo in generated: glibc allocates a single block + * the list of struct addrinfo is generated: glibc allocates a single block * of memory for both the struct addrinfo and its struct sockaddr, - * as well as one block of memory for the canonical name. Therefore, - * calling __real_freeaddrinfo on a custom-built list may result in memleaks, - * and using a wrong freeaddrinfo_method_t to free a list generated by - * __real_getaddrinfo may cause a double free. + * as well as one block of memory for the canonical name (if asked for). + * Therefore, calling __real_freeaddrinfo on a custom-built list may result + * in memleaks, and using a wrong freeaddrinfo_method_t to free a list + * generated by __real_getaddrinfo may cause a double free. + * @see set_getaddrinfo_method() + * + * @param gai_method the function called by the getaddrinfo wrapper. + * @param fai_method the function called by the freeaddrinfo wrapper. */ void set_gai_methods(getaddrinfo_method_t gai_method, freeaddrinfo_method_t fai_method); +/** + * Functions of this type should return in host and serv valid names for a host + * and service given the address addr, in manner compatible with getnameinfo. + */ typedef int (*getnameinfo_method_t)(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); /** + * Sets the getnameinfo replacement function to be used in the sandbox. + * * If method is not NULL, replaces __real_getnameinfo by the function "method" * as the real function executed by the wrapper. * If method is NULL, resets the method to __real_getnameinfo. + * @see set_getaddrinfo_method(). + * + * @param method the function called by the getnameinfo wrapper. */ void set_getnameinfo_method(getnameinfo_method_t method); +/** + * Functions of this type should return a string given an error code + * for the getaddrinfo function call. + * The string should not be freed by the caller. + */ typedef const char *(*gai_strerror_method_t)(int); /** + * Sets the gai_strerror replacement function to be used in the sandbox. + * * If method is not NULL, replaces __real_gai_strerror by the function "method" * as the real function called by the wrapper. * If method is NULL, resets the method to __real_gai_strerror. * This allows specifying other error messages than the standard ones. + * + * @param method the function called by the gai_strerror wrapper. */ void set_gai_strerror_method(gai_strerror_method_t method); +/** + * Functions of this type will be called when freeaddrinfo is called with + * an argument that has never been returned by the getaddrinfo wrapper, + * when freeaddrinfo is configured to check the validity of its argument. + * @see set_check_freeaddrinfo() + */ typedef void (*freeaddrinfo_badarg_report_t)(); /** * Sets the method to be called if check_freeaddrinfo is true * and the argument passed to freeaddrinfo was not returned by getaddrinfo. + * * This is useful if you want to use the push_info_msg method * for your feedback, for example. Default is NULL. + * + * @param reporter the function to be called when a bad argument is provided + * to freeaddrinfo and detected. */ void set_freeaddrinfo_badarg_report(freeaddrinfo_badarg_report_t reporter); @@ -176,12 +264,22 @@ void reset_gai_fai_gstr_gni_methods(); /** * Replaces getaddrinfo by a version which doesn't call DNS. - * node should be a numerical address, which will be converted using inet_pton - * and stored inside the answer. - * serv should be a positive short integer, representing the port. + * node should be a numerical address (like 192.168.1.1), + * which will be converted using inet_pton and stored inside the answer. + * serv should be a positive short integer as a string, representing the port. * Returns only one addrinfo in *res, with appropriate values taken from * node, serv and the fields from hints. Also, the canonical name is set, - * if specified, by 'C' followed by node. + * if specified, by 'C' followed by the content of node. + * If node is NULL and hints->ai_flags contains AI_PASSIVE, then IPv4 (0.0.0.0) + * is used if hints->ai_family is AF_INET, otherwise IPv6 (::) is used. + * If hints is NULL, an address with SOCK_DGRAM as its socket type is returned. + * @see simple_freeaddrinfo for the corresponding freeaddrinfo_method_t + * @warning This function is probably only minimally tested, and would benefit + * from more testng. + * @param node See the standard getaddrinfo + * @param serv See the standard getaddrinfo + * @param hints See the standard getaddrinfo + * @param res See the standard getaddrinfo */ int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res); @@ -189,6 +287,8 @@ int simple_getaddrinfo(const char *node, const char *serv, const struct addrinfo * Necessary for cross-library compatibility: should be used in place * of the default freeaddrinfo to free the list returned by simple_getaddrinfo; * see set_gai_methods for more information. + * @see simple_getaddrinfo for the corresponding creator function. + * @param res See the standard freeaddrinfo */ void simple_freeaddrinfo(struct addrinfo *res); diff --git a/student/CTester/wrap_network_inet.h b/student/CTester/wrap_network_inet.h index 8caf5f1..c44f177 100644 --- a/student/CTester/wrap_network_inet.h +++ b/student/CTester/wrap_network_inet.h @@ -1,6 +1,8 @@ -/* +/** + * @file wrap_network_inet.h * Wrapper for htons, ntohs, htonl, ntohl, and other future functions. - * + */ +/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -16,7 +18,7 @@ #ifndef __WRAP_NETWORK_INET_H_ #define __WRAP_NETWORK_INET_H_ -/** +/* * Structures for htons, ntohs, htonl, ntohl. */ @@ -55,9 +57,13 @@ struct stats_ntohl_t { }; -/** +/* * Utility functions and structures */ + +/** + * Resets the statistics for the functions htons, ntohs, htonl, ntohl. + */ void reinit_network_inet_stats(); diff --git a/student/CTester/wrap_network_socket.h b/student/CTester/wrap_network_socket.h index 2f35eb4..53ec3f6 100644 --- a/student/CTester/wrap_network_socket.h +++ b/student/CTester/wrap_network_socket.h @@ -1,7 +1,9 @@ -/* +/** + * @file wrap_network_socket.h * Wrapper for accept, bind, connect, listen, poll, select, * recv, recvfrom, recvmsg, send, sendto, sendmsg, socket. - * + */ +/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -17,11 +19,11 @@ #ifndef __WRAP_NETWORK_SOCKET_H_ #define __WRAP_NETWORK_SOCKET_H_ -/** +/* * Structures for accept. * In addition to the common stats structures, * there is an additional structure called return_accept_t, - * which will retain the returned contained, in the case that the student + * which will retain the returned content, in the case that the student * frees the allocated memory before we have the possibility to check it. */ struct params_accept_t { @@ -31,24 +33,26 @@ struct params_accept_t { }; /** - * Structure to remember the returned value. - * ret: return value of the call - * addr: sockaddr filled in *addr in the call. Set to 0 if failure. - * addrlen: size of the sockaddr filled in *addr, or zero if failure. + * Structure to remember the returned value for accept. */ struct return_accept_t { + /** + * sockaddr filled in *addr in the call. Set to 0 if failure. + * @note This is not a pointer, but the actual content. + */ struct sockaddr_storage addr; // We use a sockaddr_storage as it is guaranteed to always be big enough to contain sockaddresses. + /** size of the sockaddr filled in *addr, or zero if failure. */ socklen_t addrlen; }; struct stats_accept_t { - int called; - struct params_accept_t last_params; + int called; ///< Number of times the system call has been called. + struct params_accept_t last_params; ///< Parameters of the last call int last_return; - struct return_accept_t last_returns; + struct return_accept_t last_returns; ///< Return values of the call }; -/** +/* * Structures for bind */ struct params_bind_t { @@ -64,7 +68,7 @@ struct stats_bind_t { }; -/** +/* * Structures for connect */ struct params_connect_t { @@ -80,7 +84,7 @@ struct stats_connect_t { }; -/** +/* * Structures for listen */ struct params_listen_t { @@ -95,7 +99,7 @@ struct stats_listen_t { }; -/** +/* * Structures for poll */ struct params_poll_t { @@ -112,7 +116,7 @@ struct stats_poll_t { }; -/** +/* * Structures for recv, recvfrom, recvmsg. */ struct params_recv_t { @@ -129,17 +133,22 @@ struct params_recvfrom_t { struct sockaddr *src_addr; socklen_t *addrlen; }; - struct params_recvmsg_t { int sockfd; struct msghdr *msg; int flags; }; +/** + * Structure for recording the return values of recvfrom + */ struct return_recvfrom_t { + /** Source address */ struct sockaddr_storage src_addr; + /** Source address length */ socklen_t addrlen; }; + struct stats_recv_t { int called; struct params_recv_t last_params; @@ -157,15 +166,18 @@ struct stats_recvmsg_t { ssize_t last_return; struct msghdr last_returned_msg; }; +/** + * Structure that records total statistics for recv, recvfrom and recvmsg combined. + */ struct stats_recv_all_t { + /** Number of times any of recv, recvfrom or recvmsg has been called */ int called; // Should be incremented each time we increment another one }; -/** +/* * Structures for select. */ - struct params_select_t { int nfds; fd_set *readfds_ptr; @@ -185,7 +197,7 @@ struct stats_select_t { }; -/** +/* * Structures for send, sendto and sendmsg. */ struct params_send_t { @@ -210,6 +222,7 @@ struct params_sendmsg_t { int flags; }; + struct stats_send_t { int called; struct params_send_t last_params; @@ -230,10 +243,9 @@ struct stats_send_all_t { }; -/** +/* * Structures for shutdown. */ - struct params_shutdown_t { int sockfd; int how; @@ -246,7 +258,7 @@ struct stats_shutdown_t { }; -/** +/* * Structures for socket */ struct params_socket_t { @@ -262,9 +274,13 @@ struct stats_socket_t { }; -/** +/* * Utility functions and structures */ + +/** + * Resets the statistics for the socket-related functions. + */ void reinit_network_socket_stats(); diff --git a/student/CTester/wrap_sleep.c b/student/CTester/wrap_sleep.c index b74a2a6..2140636 100644 --- a/student/CTester/wrap_sleep.c +++ b/student/CTester/wrap_sleep.c @@ -1,3 +1,18 @@ +/* + * Wrapper for sleep. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include diff --git a/student/CTester/wrap_sleep.h b/student/CTester/wrap_sleep.h index a0fa6c9..6424dae 100644 --- a/student/CTester/wrap_sleep.h +++ b/student/CTester/wrap_sleep.h @@ -1,3 +1,20 @@ +/** + * @file wrap_sleep.h + * Wrapper for sleep. + */ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef __WRAP_SLEEP_H_ #define __WRAP_SLEEP_H_ @@ -8,14 +25,28 @@ // used by existing exercices. You might add some additional information // if it can help to validate some exercices +/** + * Statistics structure for the sleep function. + */ struct stats_sleep_t { - int called; // number of times the system call has been called - unsigned int last_return; // last return value for sleep - unsigned int last_arg; // last return value for sleep + int called; ///< number of times the system call has been called + unsigned int last_return; ///< last return value for sleep + unsigned int last_arg; ///< last argument value for sleep }; +/** + * Initializes the wrapper and statistics for the sleep function. + */ void init_sleep(); + +/** + * Cleans the wrapper for the sleep function. + */ void clean_sleep(); + +/** + * Resets the statistics for the sleep function. + */ void resetstats_sleep(); #endif // __WRAP_SLEEP_H_ From 1642bbb394c6cf205620e6acc05caf5a426ec227 Mon Sep 17 00:00:00 2001 From: Jean-Martin Vlaeminck Date: Sat, 3 Aug 2019 17:42:15 +0200 Subject: [PATCH 49/53] Add Sphinx documentation --- doc/.gitignore | 3 + doc/Doxyfile | 1451 ++++++++++++++++++++++++++++++++++++ doc/Makefile | 20 + doc/README.md | 8 + doc/api.rst | 29 + doc/api/read_write.rst | 478 ++++++++++++ doc/api/trap.rst | 36 + doc/api/util_sockets.rst | 199 +++++ doc/api/wrap_file.rst | 56 ++ doc/api/wrap_mallocs.rst | 104 +++ doc/api/wrap_misc.rst | 67 ++ doc/api/wrap_mutexs.rst | 26 + doc/api/wrap_sockets.rst | 137 ++++ doc/api/wrap_util_dns.rst | 217 ++++++ doc/api/wrap_util_inet.rst | 60 ++ doc/conf.py | 62 ++ doc/index.rst | 27 + doc/introduction.rst | 35 + doc/limitations.rst | 160 ++++ doc/make.bat | 35 + doc/usage.rst | 531 +++++++++++++ doc/wrap.rst | 494 ++++++++++++ 22 files changed, 4235 insertions(+) create mode 100644 doc/.gitignore create mode 100644 doc/Doxyfile create mode 100644 doc/Makefile create mode 100644 doc/README.md create mode 100644 doc/api.rst create mode 100644 doc/api/read_write.rst create mode 100644 doc/api/trap.rst create mode 100644 doc/api/util_sockets.rst create mode 100644 doc/api/wrap_file.rst create mode 100644 doc/api/wrap_mallocs.rst create mode 100644 doc/api/wrap_misc.rst create mode 100644 doc/api/wrap_mutexs.rst create mode 100644 doc/api/wrap_sockets.rst create mode 100644 doc/api/wrap_util_dns.rst create mode 100644 doc/api/wrap_util_inet.rst create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 doc/introduction.rst create mode 100644 doc/limitations.rst create mode 100644 doc/make.bat create mode 100644 doc/usage.rst create mode 100644 doc/wrap.rst diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..f70f32b --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,3 @@ +_build/ +doxygen_output/ +_build/ diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 0000000..dc6106c --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,1451 @@ +# Doxyfile 1.8.5 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +DOXYFILE_ENCODING = UTF-8 + +PROJECT_NAME = "CTester" + +PROJECT_NUMBER = "1.0.0" + +PROJECT_BRIEF = + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = "./doxygen_output" + +CREATE_SUBDIRS = NO + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +OPTIMIZE_OUTPUT_JAVA = NO + +OPTIMIZE_FOR_FORTRAN = NO + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +QUIET = NO + +WARNINGS = YES + +WARN_IF_UNDOCUMENTED = YES + +WARN_IF_DOC_ERROR = YES + +WARN_NO_PARAMDOC = NO + +WARN_FORMAT = "$file:$line: $text" + +# Writes to stdout by default +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = "../student" + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = "*.h" + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = "*.c" + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = "./html" + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = YES + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /