Commit ef01234a authored by Karel Koci's avatar Karel Koci 🤘

Add cleanup functions

These new functions should be used for correct cleanup. Idea is that you
will register your cleanup function that would be called if fatal error
is detected and if no such error happens then you will call cleanup_run
to call and unregister given cleanup function anyway.
parent 249910cd
......@@ -194,6 +194,91 @@ void exec_dir(struct events *events, const char *dir) {
free(namelist);
}
static bool cleanup_registered = false;
static struct {
size_t size, allocated;
struct {
cleanup_t func;
void *data;
} *funcs;
} cleanup;
void cleanup_register(cleanup_t func, void *data) {
if (!cleanup_registered) { // Initialize/register
ASSERT(atexit((void (*)(void))cleanup_run) == 0);
cleanup_registered = true;
cleanup.size = 0;
cleanup.allocated = 1;
cleanup.funcs = malloc(sizeof *cleanup.funcs);
}
if ((cleanup.size + 1) >= cleanup.allocated) { // Allocate more fields
cleanup.allocated *= 2;
cleanup.funcs = realloc(cleanup.funcs, cleanup.allocated * sizeof *cleanup.funcs);
ASSERT(cleanup.funcs);
}
cleanup.funcs[cleanup.size].func = func;
cleanup.funcs[cleanup.size].data = data;
cleanup.size++;
}
// This looks up latest given function in cleanup. Index + 1 is returned. If not
// located then 0 is returned.
static size_t cleanup_lookup(cleanup_t func) {
size_t i = cleanup.size;
for (; i > 0 && cleanup.funcs[i-1].func != func; i--);
return i;
}
// Shift all functions in cleanup stack down by one. (replacing index i-1)
static void cleanup_shift(size_t i) {
for (; i < cleanup.size; i++) // Shift down
cleanup.funcs[i - 1] = cleanup.funcs[i];
cleanup.size--;
}
bool cleanup_unregister(cleanup_t func) {
if (!cleanup_registered)
return false;
size_t loc = cleanup_lookup(func);
if (loc > 0) {
cleanup_shift(loc);
return true;
} else
return false;
}
bool cleanup_unregister_data(cleanup_t func, void *data) {
if (!cleanup_registered)
return false;
size_t i = cleanup.size;
for (; i > 0 && \
!(cleanup.funcs[i-1].func == func && \
cleanup.funcs[i-1].data == data); i--);
if (i > 0) {
cleanup_shift(i);
return true;
} else
return false;
}
void cleanup_run(cleanup_t func) {
if (!cleanup_registered)
return;
size_t loc = cleanup_lookup(func);
if (loc == 0) // Not located
return;
cleanup.funcs[loc-1].func(cleanup.funcs[loc-1].data);
cleanup_shift(loc);
}
void cleanup_run_all(void) {
if (!cleanup_registered)
return;
for (size_t i = cleanup.size; i > 0; i--)
cleanup.funcs[i-1].func(cleanup.funcs[i-1].data);
cleanup.size = 0; // All cleanups called
}
static bool system_reboot_disabled = false;
void system_reboot_disable() {
......
......@@ -26,6 +26,7 @@
#include <stdio.h>
#include <stdbool.h>
#include <alloca.h>
#include "util.h"
enum log_level {
LL_DISABLE,
......@@ -51,7 +52,7 @@ void log_internal(enum log_level level, const char *file, size_t line, const cha
#define INFO(...) LOG(LL_INFO, __VA_ARGS__)
#define DBG(...) LOG(LL_DBG, __VA_ARGS__)
#define TRACE(...) LOG(LL_TRACE, __VA_ARGS__)
#define DIE(...) do { LOG(LL_DIE, __VA_ARGS__); abort(); } while (0)
#define DIE(...) do { LOG(LL_DIE, __VA_ARGS__); cleanup_run_all(); abort(); } while (0)
#define ASSERT_MSG(COND, ...) do { if (!(COND)) DIE(__VA_ARGS__); } while (0)
#define ASSERT(COND) do { if (!(COND)) DIE("Failed assert: " #COND); } while (0)
......@@ -86,6 +87,16 @@ bool dump2file (const char *file, const char *text) __attribute__((nonnull,nonnu
// Executes all executable files in given directory
void exec_dir(struct events *events, const char *dir) __attribute__((nonnull));
// Using these functions you can register/unregister cleanup function. Note that
// they are called in reverse order of insertion. This is implemented using atexit
// function.
typedef void (*cleanup_t)(void *data);
void cleanup_register(cleanup_t func, void *data) __attribute__((nonnull(1)));
bool cleanup_unregister(cleanup_t func) __attribute__((nonnull)); // Note: removes only first occurrence
bool cleanup_unregister_data(cleanup_t func, void *data) __attribute__((nonnull(1))); // Also matches data, not only function
void cleanup_run(cleanup_t func); // Run function and unregister it
void cleanup_run_all(void); // Run all cleanup functions explicitly
// Disable system reboot. If this function is called before system_reboot is than
// system reboot just prints warning about skipped reboot and returns.
void system_reboot_disable();
......
......@@ -8,6 +8,7 @@ locks_LOCAL_LIBS += updater
C_TESTS := \
arguments \
events \
util \
interpreter
LUA_TESTS := \
......
/*
* Copyright 2018, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ctest.h"
#include <stdbool.h>
#include "../src/lib/util.h"
static int cleaned;
static void cleanup_func(void *data) {
int toc = *(int*)data;
ck_assert_int_eq(cleaned, toc);
cleaned--;
}
START_TEST(cleanup_multi) {
// Test cleanup before we initialize it
cleanup_run_all();
// Test cleanup it self
int one = 1, two = 2;
cleaned = 2;
cleanup_register(cleanup_func, &one);
cleanup_register(cleanup_func, &two);
cleanup_run_all();
ck_assert_int_eq(0, cleaned);
// Push them back (they were popped by run_all)
cleanup_register(cleanup_func, &one);
cleanup_register(cleanup_func, &two);
// Now remove 2
cleaned = 1;
ck_assert(cleanup_unregister(cleanup_func));
cleanup_run_all();
ck_assert_int_eq(0, cleaned);
}
END_TEST
START_TEST(cleanup_single) {
// Test cleanup before we initialize it
cleanup_run(cleanup_func);
// Test cleanup it self
int one = 1, two = 2;
cleaned = 2;
cleanup_register(cleanup_func, &one);
cleanup_register(cleanup_func, &two);
cleanup_run(cleanup_func);
ck_assert_int_eq(1, cleaned);
cleanup_run(cleanup_func);
ck_assert_int_eq(0, cleaned);
// Both functions should be unregisterd so this should fail
ck_assert(!cleanup_unregister(cleanup_func));
// Check if we don't fail
cleanup_run(cleanup_func);
}
END_TEST
START_TEST(cleanup_by_data) {
int data1 = 1, data2 = 2; // Note: we don't care about exact value
cleanup_register(cleanup_func, &data1);
cleanup_register(cleanup_func, &data2);
// Remove bottom one
ck_assert(cleanup_unregister_data(cleanup_func, &data1));
// Top one should be still there but nothing else
cleaned = 2;
cleanup_run_all();
}
END_TEST
Suite *gen_test_suite(void) {
Suite *result = suite_create("Util");
TCase *util = tcase_create("util");
tcase_set_timeout(util, 30);
tcase_add_test(util, cleanup_multi);
tcase_add_test(util, cleanup_single);
tcase_add_test(util, cleanup_by_data);
suite_add_tcase(result, util);
return result;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment