From 131bfc5da6301fd8c0af8f7c9394f6d4e4c5d8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20O=2E=20Sel=C3=A5sdal?= Date: Thu, 11 Sep 2014 21:41:14 +0200 Subject: [PATCH] Add uc_restart_counter concept --- include/ucore/restart_counter.h | 81 +++++++++++++++++ src/restart_counter.c | 100 +++++++++++++++++++++ test/test_restart_file.c | 154 ++++++++++++++++++++++++++++++++ test/test_runner.c | 4 +- 4 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 include/ucore/restart_counter.h create mode 100644 src/restart_counter.c create mode 100644 test/test_restart_file.c diff --git a/include/ucore/restart_counter.h b/include/ucore/restart_counter.h new file mode 100644 index 0000000..806251e --- /dev/null +++ b/include/ucore/restart_counter.h @@ -0,0 +1,81 @@ +#ifndef UC_RESTART_COUNTER_H_ +#define UC_RESTART_COUNTER_H_ +/** @file + * Simple support for keeping track of number of restarts. + * + * Certain protocols desire support for comminicating the number + * of times an entity has restarted. + * The counter might also serve as a seed for sequence number generation + * to minimize recycling a recently used sequence number. + * + * The number of times a process has been restarted is simply kept + * track of via a counter in a restart state ile in ascii representation. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct UCRestartCounter { + unsigned int counter; + int fd; +}; + +typedef enum { + UC_RC_OK, + /** accessing the file failed, inspect errno + * for more info */ + UC_RC_ERR_FILE_ACCESS_ERROR, + /* The file existed and is opened but + * no valid number could be read from the file. + * This is a transient error, + * the now new counter is initialized to 0, and + * written to the file */ + UC_RC_ERR_FILE_CONTENT_ERROR, + /* UC_RC_FL_LOCK_FILE was specified but the file + * is already locked by some other process */ + UC_RC_ERR_FILE_LOCKED, + /** unspecified error. Inspect errno. */ + UC_RC_ERR_UNSPECIFIED, + /** Given by uc_restart_counter_close if the + * file never was opened */ + UC_RC_ERR_NOT_OPENED, +} UCRCResult; + +/** Lock the restart state file and keep it open. + * e.g. to prevent several processes from being started + * and using the same restart state file.*/ +#define UC_RC_FL_LOCK_FILE ( 1 << 0) + +/** Read and update the current restart counter. + * This is typically called once at program startup. + * + * @param counter the counter to initialize + * @param filename the filename to use to keep track of the state + * @param flags one of UC_RC_FL_XX + */ +UCRCResult uc_restart_counter_init( + struct UCRestartCounter *counter, + const char *filename, + unsigned int flags); + +/** Close the current restart counter file if it was opened with the + * UC_RC_FL_LOCK_FILE flag. + * + * @counter counter file to close. (uc_restart_counter can still be called + * after * the file is closed) + * @return UC_RC_OK or if the file was not kept open with UC_RC_ERR_FILE_LOCKED + */ +UCRCResult uc_restart_counter_close(struct UCRestartCounter *counter); + +/** Get the current restart counter. + * In case uc_restart_counter_init failed, this will return 0. + */ +unsigned int uc_restart_counter_get(const struct UCRestartCounter *counter); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/restart_counter.c b/src/restart_counter.c new file mode 100644 index 0000000..958ade5 --- /dev/null +++ b/src/restart_counter.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ucore/restart_counter.h" + +static UCRCResult uc_restart_counter_lock(int fd) +{ + int rc = lockf(fd, F_TLOCK, 0); + + if (rc == -1 && (errno == EACCES || errno == EAGAIN)) { + return UC_RC_ERR_FILE_LOCKED; + } else if (rc == -1) { + return UC_RC_ERR_UNSPECIFIED; + } + + return UC_RC_OK; +} + +UCRCResult uc_restart_counter_init( + struct UCRestartCounter *counter, + const char *filename, + unsigned int flags) +{ + int fd; + UCRCResult rc = UC_RC_OK; + char buf[64]; + ssize_t n; + + counter->counter = 0; + counter->fd = -1; + + fd = open(filename, O_CREAT|O_RDWR, 0600); + + if (fd == -1) { + return UC_RC_ERR_FILE_ACCESS_ERROR; + } + if (flags & UC_RC_FL_LOCK_FILE) { + rc = uc_restart_counter_lock(fd); + if (rc != UC_RC_OK) { + close(fd); + return rc; + } + counter->fd = fd; + } + + buf[0] = 0; + n = read(fd, buf, sizeof buf - 1); + if (n > 0) { + buf[n] = 0; + if (sscanf(buf, "%u", &counter->counter) == 1) { + counter->counter++; + rc = UC_RC_OK; + } else { + rc = UC_RC_ERR_FILE_CONTENT_ERROR; + } + + + if (lseek(fd, 0, SEEK_SET) != 0 || + ftruncate(fd,0) != 0) { + rc = UC_RC_ERR_UNSPECIFIED; + } + + + } else if (n < 0) { + rc = UC_RC_ERR_FILE_ACCESS_ERROR; + } //if n is 0, we act as we created the file for the 1. time + + snprintf(buf, sizeof buf, "%u\n", counter->counter); + n = strlen(buf); + if (write(fd, buf, n) != n) { + rc = UC_RC_ERR_UNSPECIFIED; + } + + if ((flags & UC_RC_FL_LOCK_FILE) == 0) { + close(fd); + } + + return rc; +} + +UCRCResult uc_restart_counter_close(struct UCRestartCounter *counter) +{ + if (counter->fd < 0) { + return UC_RC_ERR_NOT_OPENED; + } + + close(counter->fd); //also releases the lock + counter->fd = -1; + + return UC_RC_OK; +} + +unsigned int uc_restart_counter_get(const struct UCRestartCounter *counter) +{ + return counter->counter; +} diff --git a/test/test_restart_file.c b/test/test_restart_file.c new file mode 100644 index 0000000..14792b6 --- /dev/null +++ b/test/test_restart_file.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ucore/restart_counter.h" + +#define FILE_NAME "restart_counter_test" +//Note that these tests are comewhat fragile as they depend on the +//file sizes that are created, which depends on the (relative)paths of this file. +//for cleaning the created log files.. +static void logging_rm_files(void) +{ + remove(FILE_NAME); +} + + +START_TEST (test_restart_counter) + + UCRCResult rc; + struct UCRestartCounter cnt; + + rc = uc_restart_counter_init(&cnt, FILE_NAME, 0); + fail_if(rc != UC_RC_OK); + ck_assert_uint_eq(0, uc_restart_counter_get(&cnt)); + + rc = uc_restart_counter_init(&cnt, FILE_NAME, 0); + fail_if(rc != UC_RC_OK); + + ck_assert_uint_eq(1, uc_restart_counter_get(&cnt)); + +END_TEST + +START_TEST (test_restart_counter_existing) + + UCRCResult rc; + struct UCRestartCounter cnt; + + FILE *f = fopen(FILE_NAME, "w"); + fail_if(f == NULL); + fputs("99999\n", f); + fclose(f); + + + rc = uc_restart_counter_init(&cnt, FILE_NAME, 0); + fail_if(rc != UC_RC_OK); + ck_assert_uint_eq(100000, uc_restart_counter_get(&cnt)); + +END_TEST + +START_TEST (test_restart_counter_inaccessible_file) + + UCRCResult rc; + struct UCRestartCounter cnt; + + rc = uc_restart_counter_init(&cnt, "/should/not/exist/abc", 0); + fail_if(rc != UC_RC_ERR_FILE_ACCESS_ERROR); + +END_TEST +START_TEST (test_restart_counter_garble) + + UCRCResult rc; + struct UCRestartCounter cnt; + FILE *f = fopen(FILE_NAME, "w"); + fail_if(f == NULL); + fputs("Q123456789\n\n\n", f); + fclose(f); + + rc = uc_restart_counter_init(&cnt, FILE_NAME, 0); + fail_if(rc != UC_RC_ERR_FILE_CONTENT_ERROR); + ck_assert_uint_eq(0, uc_restart_counter_get(&cnt)); + + rc = uc_restart_counter_init(&cnt, FILE_NAME, 0); + fail_if(rc != UC_RC_OK, "%d", rc); + ck_assert_uint_eq(1, uc_restart_counter_get(&cnt)); + +END_TEST + +START_TEST (test_restart_counter_full_filesystem) + + UCRCResult rc; + struct UCRestartCounter cnt; + + rc = uc_restart_counter_init(&cnt, "/dev/full", 0); + fail_if(rc != UC_RC_ERR_UNSPECIFIED); + +END_TEST + +START_TEST (test_restart_counter_lock_file) + + UCRCResult rc; + struct UCRestartCounter cnt; + sem_t *sem; + sem = mmap(NULL, 2 * sysconf(_SC_PAGESIZE), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS,-1,0); + fail_if(sem == NULL); + + fail_if(sem_init(&sem[0], 1 , 0) != 0); + fail_if(sem_init(&sem[1], 1 , 0) != 0); + + rc = uc_restart_counter_init(&cnt, FILE_NAME, UC_RC_FL_LOCK_FILE); + fail_if(rc != UC_RC_OK); + ck_assert_uint_eq(0, uc_restart_counter_get(&cnt)); + rc = uc_restart_counter_close(&cnt); + fail_if(rc != UC_RC_OK); + rc = uc_restart_counter_close(&cnt); + fail_if(rc != UC_RC_ERR_NOT_OPENED); + + pid_t pid = fork(); + fail_if(pid < 0); + + if (pid > 0) { + sem_wait(&sem[0]); + rc = uc_restart_counter_init(&cnt, FILE_NAME, UC_RC_FL_LOCK_FILE); + sem_post(&sem[1]); + printf("rc = %d\n", rc); + fail_if(rc != UC_RC_ERR_FILE_LOCKED); + ck_assert_uint_eq(0, uc_restart_counter_get(&cnt)); + } else { + uc_restart_counter_init(&cnt, FILE_NAME, UC_RC_FL_LOCK_FILE); + sem_post(&sem[0]); + sem_wait(&sem[1]); + _exit(0); + } + +END_TEST + + +Suite *restart_counter_suite(void) +{ + Suite *s = suite_create("restart_counter"); + TCase *tc1 = tcase_create("restart_counter"); + + tcase_add_test(tc1, test_restart_counter); + tcase_add_test(tc1, test_restart_counter_existing); + tcase_add_test(tc1, test_restart_counter_inaccessible_file); + tcase_add_test(tc1, test_restart_counter_garble); + if (access("/dev/full", R_OK) == 0) { + tcase_add_test(tc1, test_restart_counter_full_filesystem); + } else { + puts("Cannot access /dev/full. Not testing test_logfile_full_filesystem"); + } + + tcase_add_test(tc1, test_restart_counter_lock_file); + tcase_add_checked_fixture(tc1, logging_rm_files, logging_rm_files); + + suite_add_tcase(s, tc1); + + return s; +} + diff --git a/test/test_runner.c b/test/test_runner.c index c5cedb8..33b1ad4 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -31,6 +31,7 @@ extern Suite *heapsort_suite(void); extern Suite *dstr_suite(void); extern Suite *val_str_suite(void); extern Suite *ticket_lock_suite(void); +extern Suite *restart_counter_suite(void); static suite_func suites[] = { bitvec_suite, @@ -56,7 +57,8 @@ static suite_func suites[] = { heapsort_suite, dstr_suite, val_str_suite, - ticket_lock_suite + ticket_lock_suite, + restart_counter_suite }; int