From: Lennart Poettering Date: Tue, 17 Nov 2009 23:42:52 +0000 (+0100) Subject: initial commit X-Git-Tag: v1~861 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=6091827530d6dd43479d6709fb6e9f745c11e900 initial commit --- 6091827530d6dd43479d6709fb6e9f745c11e900 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6169dd062 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +systemd +*.o diff --git a/CODING_STYLE b/CODING_STYLE new file mode 100644 index 000000000..1b8c8cf3c --- /dev/null +++ b/CODING_STYLE @@ -0,0 +1,16 @@ + +- 8ch indent, no tabs + +- structs in MixedCase, variables, functions in lower_case + +- the destructors always unregister the object from the next bigger + object, not the other way around + +- to minimize strict aliasing violations we prefer unions over casting + +- for robustness reasons destructors should be able to destruct + half-initialized objects, too + +- error codes are returned as negative Exxx. i.e. return EINVAL. There + are some exceptions: for constructors its is OK to return NULL on + OOM. For lookup functions NULL is fine too for "not found". diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..ee144a378 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +CFLAGS=-Wall -Wextra -O0 -g -pipe +LIBS=-lrt + +systemd: main.o name.o util.o set.o hashmap.o strv.o job.o manager.o + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +clean: + rm -f *.o systemd diff --git a/hashmap.c b/hashmap.c new file mode 100644 index 000000000..1b2e059dd --- /dev/null +++ b/hashmap.c @@ -0,0 +1,325 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "util.h" +#include "hashmap.h" +#include "macro.h" + +#define NBUCKETS 127 + +struct hashmap_entry { + const void *key; + void *value; + + struct hashmap_entry *bucket_next, *bucket_previous; + struct hashmap_entry *iterate_next, *iterate_previous; +}; + +struct Hashmap { + hash_func_t hash_func; + compare_func_t compare_func; + + struct hashmap_entry *iterate_list_head, *iterate_list_tail; + unsigned n_entries; +}; + +#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap)))) + +unsigned string_hash_func(const void *p) { + unsigned hash = 0; + const char *c; + + for (c = p; *c; c++) + hash = 31 * hash + (unsigned) *c; + + return hash; +} + +int string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +unsigned trivial_hash_func(const void *p) { + return PTR_TO_UINT(p); +} + +int trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) { + Hashmap *h; + + if (!(h = malloc0(ALIGN(sizeof(Hashmap)) + NBUCKETS * ALIGN(sizeof(struct hashmap_entry*))))) + return NULL; + + h->hash_func = hash_func ? hash_func : trivial_hash_func; + h->compare_func = compare_func ? compare_func : trivial_compare_func; + + h->n_entries = 0; + h->iterate_list_head = h->iterate_list_tail = NULL; + + return h; +} + +static void remove_entry(Hashmap *h, struct hashmap_entry *e) { + assert(h); + assert(e); + + /* Remove from iteration list */ + if (e->iterate_next) + e->iterate_next->iterate_previous = e->iterate_previous; + else + h->iterate_list_tail = e->iterate_previous; + + if (e->iterate_previous) + e->iterate_previous->iterate_next = e->iterate_next; + else + h->iterate_list_head = e->iterate_next; + + /* Remove from hash table bucket list */ + if (e->bucket_next) + e->bucket_next->bucket_previous = e->bucket_previous; + + if (e->bucket_previous) + e->bucket_previous->bucket_next = e->bucket_next; + else { + unsigned hash = h->hash_func(e->key) % NBUCKETS; + BY_HASH(h)[hash] = e->bucket_next; + } + + free(e); + + assert(h->n_entries >= 1); + h->n_entries--; +} + +void hashmap_free(Hashmap*h) { + + if (!h) + return; + + while (h->iterate_list_head) + remove_entry(h, h->iterate_list_head); + + free(h); +} + +static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) { + struct hashmap_entry *e; + assert(h); + assert(hash < NBUCKETS); + + for (e = BY_HASH(h)[hash]; e; e = e->bucket_next) + if (h->compare_func(e->key, key) == 0) + return e; + + return NULL; +} + +int hashmap_put(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + assert(h); + + hash = h->hash_func(key) % NBUCKETS; + + if (hash_scan(h, hash, key)) + return -EEXIST; + + if (!(e = new(struct hashmap_entry, 1))) + return -ENOMEM; + + e->key = key; + e->value = value; + + /* Insert into hash table */ + e->bucket_next = BY_HASH(h)[hash]; + e->bucket_previous = NULL; + if (BY_HASH(h)[hash]) + BY_HASH(h)[hash]->bucket_previous = e; + BY_HASH(h)[hash] = e; + + /* Insert into iteration list */ + e->iterate_previous = h->iterate_list_tail; + e->iterate_next = NULL; + if (h->iterate_list_tail) { + assert(h->iterate_list_head); + h->iterate_list_tail->iterate_next = e; + } else { + assert(!h->iterate_list_head); + h->iterate_list_head = e; + } + h->iterate_list_tail = e; + + h->n_entries++; + assert(h->n_entries >= 1); + + return 0; +} + +void* hashmap_get(Hashmap *h, const void *key) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + return e->value; +} + +void* hashmap_remove(Hashmap *h, const void *key) { + struct hashmap_entry *e; + unsigned hash; + void *data; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + data = e->value; + remove_entry(h, e); + + return data; +} + +void *hashmap_iterate(Hashmap *h, void **state, const void **key) { + struct hashmap_entry *e; + + assert(state); + + if (!h) + goto at_end; + + if (*state == (void*) -1) + goto at_end; + + if (!*state && !h->iterate_list_head) + goto at_end; + + e = *state ? *state : h->iterate_list_head; + + if (e->iterate_next) + *state = e->iterate_next; + else + *state = (void*) -1; + + if (key) + *key = e->key; + + return e->value; + +at_end: + *state = (void *) -1; + + if (key) + *key = NULL; + + return NULL; +} + +void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key) { + struct hashmap_entry *e; + + assert(state); + + if (!h) + goto at_beginning; + + if (*state == (void*) -1) + goto at_beginning; + + if (!*state && !h->iterate_list_tail) + goto at_beginning; + + e = *state ? *state : h->iterate_list_tail; + + if (e->iterate_previous) + *state = e->iterate_previous; + else + *state = (void*) -1; + + if (key) + *key = e->key; + + return e->value; + +at_beginning: + *state = (void *) -1; + + if (key) + *key = NULL; + + return NULL; +} + +void* hashmap_first(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + return h->iterate_list_head->value; +} + +void* hashmap_last(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_tail) + return NULL; + + return h->iterate_list_tail->value; +} + +void* hashmap_steal_first(Hashmap *h) { + void *data; + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + data = h->iterate_list_head->value; + remove_entry(h, h->iterate_list_head); + + return data; +} + +unsigned hashmap_size(Hashmap *h) { + + if (!h) + return 0; + + return h->n_entries; +} + +bool hashmap_isempty(Hashmap *h) { + + if (!h) + return true; + + return h->n_entries == 0; +} diff --git a/hashmap.h b/hashmap.h new file mode 100644 index 000000000..4c946e35b --- /dev/null +++ b/hashmap.h @@ -0,0 +1,47 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foohashmaphfoo +#define foohashmaphfoo + +#include + +/* Pretty straightforward hash table implementation. As a minor + * optimization a NULL hashmap object will be treated as empty hashmap + * for all read operations. That way it is not necessary to + * instantiate an object for each Hashmap use. */ + +typedef struct Hashmap Hashmap; + +typedef unsigned (*hash_func_t)(const void *p); +typedef int (*compare_func_t)(const void *a, const void *b); + +unsigned string_hash_func(const void *p); +int string_compare_func(const void *a, const void *b); + +unsigned trivial_hash_func(const void *p); +int trivial_compare_func(const void *a, const void *b); + +Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func); +void hashmap_free(Hashmap*); + +int hashmap_put(Hashmap *h, const void *key, void *value); +void* hashmap_get(Hashmap *h, const void *key); +void* hashmap_remove(Hashmap *h, const void *key); + +unsigned hashmap_size(Hashmap *h); +bool hashmap_isempty(Hashmap *h); + +void *hashmap_iterate(Hashmap *h, void **state, const void **key); +void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key); + +void *hashmap_steal_first(Hashmap *h); +void* hashmap_first(Hashmap *h); +void* hashmap_last(Hashmap *h); + +#define HASHMAP_FOREACH(e, h, state) \ + for ((state) = NULL, (e) = hashmap_iterate((h), &(state), NULL); (e); (e) = hashmap_iterate((h), &(state), NULL)) + +#define HASHMAP_FOREACH_BACKWARDS(e, h, state) \ + for ((state) = NULL, (e) = hashmap_iterate_backwards((h), &(state), NULL); (e); (e) = hashmap_iterate_backwards((h), &(state), NULL)) + +#endif diff --git a/job.c b/job.c new file mode 100644 index 000000000..5cd8f73a9 --- /dev/null +++ b/job.c @@ -0,0 +1,59 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include + +#include "macro.h" +#include "job.h" + +Job* job_new(Manager *m, JobType type, Name *name) { + Job *j; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + + if (!(j = new0(Job, 1))) + return NULL; + + j->manager = m; + j->id = m->current_job_id++; + j->type = type; + j->name = name; + + /* We don't link it here, that's what job_link() is for */ + + return j; +} + +int job_link(Job *j) { + int r; + + assert(j); + assert(!j->linked); + + if ((r = hashmap_put(j->manager->jobs, UINT32_TO_PTR(j->id), j)) < 0) + return r; + + j->name->meta.job = j; + + j->linked = true; + + return 0; +} + +void job_free(Job *j) { + assert(j); + + /* Detach from next 'bigger' objects */ + + if (j->linked) { + if (j->name && j->name->meta.job == j) + j->name->meta.job = NULL; + + hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); + } + + /* Free data and next 'smaller' objects */ + + free(j); +} diff --git a/job.h b/job.h new file mode 100644 index 000000000..d19f747db --- /dev/null +++ b/job.h @@ -0,0 +1,57 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foojobhfoo +#define foojobhfoo + +#include +#include + +typedef struct Job Job; +typedef enum JobType JobType; +typedef enum JobMode JobMode; + +#include "manager.h" +#include "name.h" +#include "hashmap.h" +#include "list.h" + +enum JobType { + JOB_START, + JOB_STOP, + JOB_VERIFY_STARTED, + JOB_RELOAD, + JOB_RESTART, + JOB_TRY_RESTART, /* restart if running */ + JOB_RESTART_FINISH, /* 2nd part of a restart, i.e. the actual starting */ + _JOB_TYPE_MAX +}; + +typedef enum JobState { + JOB_WAITING, + JOB_RUNNING, + JOB_DONE, + _JOB_STATE_MAX +} JobState; + +enum JobMode { + JOB_FAIL, + JOB_REPLACE, + _JOB_MODE_MAX +}; + +struct Job { + Manager *manager; + uint32_t id; + + JobType type; + JobState state; + Name *name; + + bool linked:1; +}; + +Job* job_new(Manager *m, JobType type, Name *name); +int job_link(Job *job); +void job_free(Job *job); + +#endif diff --git a/list.h b/list.h new file mode 100644 index 000000000..c255e087e --- /dev/null +++ b/list.h @@ -0,0 +1,90 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foolisthfoo +#define foolisthfoo + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define LIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define LIST_FIELDS(t) \ + t *next, *prev + +/* Initialize the list's head */ +#define LIST_HEAD_INIT(t,item) \ + do { \ + (item) = (t*) NULL; } \ + while(false) + +/* Initialize a list item */ +#define LIST_INIT(t,item) \ + do { \ + t *_item = (item); \ + assert(_item); \ + _item->prev = _item->next = NULL; \ + } while(false) + +/* Prepend an item to the list */ +#define LIST_PREPEND(t,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + assert(_item); \ + if ((_item->next = *_head)) \ + _item->next->prev = _item; \ + _item->prev = NULL; \ + *_head = _item; \ + } while(false) + +/* Remove an item from the list */ +#define LIST_REMOVE(t,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + assert(_item); \ + if (_item->next) \ + _item->next->prev = _item->prev; \ + if (_item->prev) \ + _item->prev->next = _item->next; \ + else { \ + assert(*_head == _item); \ + *_head = _item->next; \ + } \ + _item->next = _item->prev = NULL; \ + } while(false) + +/* Find the head of the list */ +#define LIST_FIND_HEAD(t,item,head) \ + do { \ + t **_head = (head), *_item = (item); \ + *_head = _item; \ + assert(_head); \ + while ((*_head)->prev) \ + *_head = (*_head)->prev; \ + } while (false) + +/* Insert an item after another one (a = where, b = what) */ +#define LIST_INSERT_AFTER(t,head,a,b) \ + do { \ + t **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if ((_b->next = *_head)) \ + _b->next->prev = _b; \ + _b->prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->next = _a->next)) \ + _b->next->prev = _b; \ + _b->prev = _a; \ + _a->next = _b; \ + } \ + } while(false) + +#define LIST_FOREACH(i,head) \ + for (i = (head); i; i = i->next) + +#define LIST_FOREACH_SAFE(i,n,head) \ + for (i = (head); i && ((n = i->next), 1); i = n) + +#endif diff --git a/macro.h b/macro.h new file mode 100644 index 000000000..267da83a0 --- /dev/null +++ b/macro.h @@ -0,0 +1,76 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foomacrohfoo +#define foomacrohfoo + +#include +#include + +#define __printf_attr(a,b) __attribute__ ((format (printf, a, b))) +#define __sentinel __attribute__ ((sentinel)) +#define __noreturn __attribute__((noreturn)) +#define __unused __attribute__ ((unused)) +#define __destructor __attribute__ ((destructor)) +#define __pure __attribute__ ((pure)) +#define __const __attribute__ ((const)) +#define __deprecated __attribute__ ((deprecated)) +#define __packed __attribute__ ((packed)) +#define __malloc __attribute__ ((malloc)) + +/* Rounds up */ +static inline size_t ALIGN(size_t l) { + return ((l + sizeof(void*) - 1) & ~(sizeof(void*) - 1)); +} + +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +#define MAX(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) + +#define MIN(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) + +#define CLAMP(x, low, high) \ + __extension__ ({ \ + typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \ + }) + + + +#define assert_not_reached(t) assert(!(t)) + +#define assert_se(x) assert(x) + +#define assert_cc(expr) \ + do { \ + switch (0) { \ + case 0: \ + case !!(expr): \ + ; \ + } \ + } while (false) + +#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) +#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define INT_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define TO_INT32(p) ((int32_t) ((intptr_t) (p))) +#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#endif diff --git a/main.c b/main.c new file mode 100644 index 000000000..08ccd4f8d --- /dev/null +++ b/main.c @@ -0,0 +1,38 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include +#include +#include + +#include "manager.h" + +int main(int argc, char *argv[]) { + Manager *m = NULL; + Name *milestone = NULL; + Job *job = NULL; + int r, retval = 1; + + if (!(m = manager_new()) < 0) { + fprintf(stderr, "Failed to allocate manager object: %s\n", strerror(ENOMEM)); + goto finish; + } + + + if ((r = manager_load_name(m, "default.milestone", &milestone) < 0)) { + fprintf(stderr, "Failed to load default milestone: %s\n", strerror(-r)); + goto finish; + } + + if ((r = manager_add_job(m, JOB_START, milestone, JOB_REPLACE, &job)) < 0) { + fprintf(stderr, "Failed to start default milestone: %s\n", strerror(-r)); + goto finish; + } + + retval = 0; + +finish: + if (m) + manager_free(m); + + return retval; +} diff --git a/manager.c b/manager.c new file mode 100644 index 000000000..d6bc35a28 --- /dev/null +++ b/manager.c @@ -0,0 +1,309 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include +#include + +#include "manager.h" +#include "hashmap.h" +#include "macro.h" +#include "strv.h" + +Manager* manager_new(void) { + Manager *m; + + if (!(m = new0(Manager, 1))) + return NULL; + + if (!(m->names = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + + if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->jobs_to_add = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->jobs_to_remove = set_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + return m; + +fail: + manager_free(m); + return NULL; +} + +void manager_free(Manager *m) { + Name *n; + + assert(m); + + while ((n = hashmap_first(m->names))) + name_free(n); + + hashmap_free(m->names); + hashmap_free(m->jobs); + + /* FIXME: This is incomplete */ + + hashmap_free(m->jobs_to_add); + set_free(m->jobs_to_remove); + + free(m); +} + +int manager_add_job(Manager *m, JobType type, Name *name, JobMode mode, Job **_ret) { + Job *ret, *other; + void *state; + Name *dep; + int r; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + assert(mode < _JOB_MODE_MAX); + assert(_ret); + + /* Check for conflicts, first against the jobs we shall + * create */ + if ((other = hashmap_get(m->jobs_to_add, name))) { + + if (other->type != type) + return -EEXIST; + + } else if (name->meta.job) { + + if (name->meta.job->type != type) { + + if (mode == JOB_FAIL) + return -EEXIST; + + if ((r = set_put(m->jobs_to_remove, name->meta.job)) < 0) + return r; + } + } + + if (!(ret = job_new(m, type, name))) + return -ENOMEM; + + if ((r = hashmap_put(m->jobs_to_add, name, ret)) < 0) + goto fail; + + if (type == JOB_START || type == JOB_VERIFY_STARTED || type == JOB_RESTART_FINISH) { + SET_FOREACH(dep, ret->name->meta.requires, state) + if ((r = manager_add_job(m, type, dep, mode, NULL)) < 0) + goto fail; + SET_FOREACH(dep, ret->name->meta.soft_requires, state) + if ((r = manager_add_job(m, type, dep, JOB_FAIL, NULL)) < 0) + goto fail; + SET_FOREACH(dep, ret->name->meta.wants, state) + if ((r = manager_add_job(m, type, dep, JOB_FAIL, NULL)) < 0) + goto fail; + SET_FOREACH(dep, ret->name->meta.requisite, state) + if ((r = manager_add_job(m, JOB_VERIFY_STARTED, dep, mode, NULL)) < 0) + goto fail; + SET_FOREACH(dep, ret->name->meta.soft_requisite, state) + if ((r = manager_add_job(m, JOB_VERIFY_STARTED, dep, JOB_FAIL, NULL)) < 0) + goto fail; + SET_FOREACH(dep, ret->name->meta.conflicts, state) + if ((r = manager_add_job(m, type, dep, mode, NULL)) < 0) + goto fail; + + } else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) { + + SET_FOREACH(dep, ret->name->meta.required_by, state) + if ((r = manager_add_job(m, type, dep, mode, NULL)) < 0) + goto fail; + } + + if (_ret) + *_ret = ret; + + return 0; + +fail: + job_free(ret); + + return r; +} + + +Job *manager_get_job(Manager *m, uint32_t id) { + assert(m); + + return hashmap_get(m->jobs, UINT32_TO_PTR(id)); +} + +Name *manager_get_name(Manager *m, const char *name) { + assert(m); + assert(name); + + return hashmap_get(m->names, name); +} + +static int detect_type(Name *name) { + char **n; + + assert(name); + + name->meta.type = _NAME_TYPE_INVALID; + + STRV_FOREACH(n, name->meta.names) { + NameType t; + + if ((t = name_type_from_string(*n)) == _NAME_TYPE_INVALID) + return -EINVAL; + + if (name->meta.type == _NAME_TYPE_INVALID) { + name->meta.type = t; + continue; + } + + if (name->meta.type != t) + return -EINVAL; + } + + return 0; +} + +static int fragment_load(Name *n) { + assert(n); + + /*... */ + + return 0; +} + +static int sysv_load(Service *s) { + assert(s); + + /*... */ + + return 0; +} + +static int fstab_load(Name *n) { + assert(n); + assert(n->meta.type == NAME_MOUNT || n->meta.type == NAME_AUTOMOUNT); + + /*... */ + + return 0; +} + +static int snapshot_load(Snapshot *s) { + assert(s); + + /*... */ + + return 0; +} + +static int load(Name *name) { + int r; + + assert(name); + + if (name->meta.state != NAME_STUB) + return 0; + + if ((r = detect_type(name)) < 0) + return r; + + if (name->meta.type == NAME_SERVICE) { + + /* Load a .service file */ + if ((r = fragment_load(name)) == 0) + goto finish; + + /* Load a classic init script */ + if (r == -ENOENT) + if ((r = sysv_load(SERVICE(name))) == 0) + goto finish; + + } else if (name->meta.type == NAME_MOUNT || + name->meta.type == NAME_AUTOMOUNT) { + + if ((r = fstab_load(name)) == 0) + goto finish; + + } else if (name->meta.type == NAME_SNAPSHOT) { + + if ((r = snapshot_load(SNAPSHOT(name))) == 0) + goto finish; + + } else { + if ((r = fragment_load(name)) == 0) + goto finish; + } + + name->meta.state = NAME_FAILED; + return r; + +finish: + name->meta.state = NAME_LOADED; + return 0; +} + +static int dispatch_load_queue(Manager *m) { + Meta *meta; + + assert(m); + + /* Dispatches the load queue. Takes a name from the queue and + * tries to load its data until the queue is empty */ + + while ((meta = m->load_queue)) { + load(NAME(meta)); + LIST_REMOVE(Meta, m->load_queue, meta); + } + + return 0; +} + + + +int manager_load_name(Manager *m, const char *name, Name **_ret) { + Name *ret; + NameType t; + int r; + + assert(m); + assert(name); + assert(_ret); +/* This will load the service information files, but not actually + * start any services or anything */ + + + if ((ret = manager_get_name(m, name))) + goto finish; + + if ((t = name_type_from_string(name)) == _NAME_TYPE_INVALID) + return -EINVAL; + + if (!(ret = name_new(m))) + return -ENOMEM; + + ret->meta.type = t; + + if (!(ret->meta.names = strv_new(name, NULL))) { + name_free(ret); + return -ENOMEM; + } + + if ((r = name_link(ret)) < 0) { + name_free(ret); + return r; + } + + /* At this point the new entry is created and linked. However, + * not loaded. Now load this entry and all its dependencies + * recursively */ + + dispatch_load_queue(m); + +finish: + + *_ret = ret; + return 0; +} diff --git a/manager.h b/manager.h new file mode 100644 index 000000000..53cacdfb2 --- /dev/null +++ b/manager.h @@ -0,0 +1,41 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foomanagerhfoo +#define foomanagerhfoo + +#include +#include + +typedef struct Manager Manager; + +#include "name.h" +#include "job.h" +#include "hashmap.h" +#include "list.h" +#include "set.h" + +struct Manager { + uint32_t current_job_id; + + /* Active jobs and names */ + Hashmap *names; /* name string => Name object n:1 */ + Hashmap *jobs; /* job id => Job object 1:1 */ + + /* Names that need to be loaded */ + LIST_HEAD(Meta, load_queue); /* this is actually more a stack than a queue, but uh. */ + + /* Jobs to be added resp. removed. */ + Hashmap *jobs_to_add; /* Name object => Job object 1:1 */ + Set *jobs_to_remove; +}; + +Manager* manager_new(void); +void manager_free(Manager *m); + +Job *manager_get_job(Manager *m, uint32_t id); +Name *manager_get_name(Manager *m, const char *name); + +int manager_load_name(Manager *m, const char *name, Name **_ret); +int manager_add_job(Manager *m, JobType job, Name *name, JobMode mode, Job **_ret); + +#endif diff --git a/name.c b/name.c new file mode 100644 index 000000000..4b4b0b87d --- /dev/null +++ b/name.c @@ -0,0 +1,267 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include +#include + +#include "set.h" +#include "name.h" +#include "macro.h" +#include "strv.h" + +NameType name_type_from_string(const char *n) { + NameType t; + static const char* suffixes[_NAME_TYPE_MAX] = { + [NAME_SERVICE] = ".service", + [NAME_TIMER] = ".timer", + [NAME_SOCKET] = ".socket", + [NAME_MILESTONE] = ".milestone", + [NAME_DEVICE] = ".device", + [NAME_MOUNT] = ".mount", + [NAME_AUTOMOUNT] = ".automount", + [NAME_SNAPSHOT] = ".snapshot", + }; + + assert(n); + + for (t = 0; t < _NAME_TYPE_MAX; t++) + if (endswith(n, suffixes[t])) + return t; + + return _NAME_TYPE_INVALID; +} + +Name *name_new(Manager *m) { + Name *n; + + assert(m); + + if (!(n = new0(Name, 1))) + return NULL; + + /* Not much initialization happening here at this time */ + n->meta.manager = m; + n->meta.type = _NAME_TYPE_INVALID; + n->meta.state = NAME_STUB; + + /* We don't link the name here, that is left for name_link() */ + + return n; +} + +int name_link(Name *n) { + char **t; + int r; + + assert(n); + assert(!n->meta.linked); + + STRV_FOREACH(t, n->meta.names) + if ((r = hashmap_put(n->meta.manager->names, *t, n)) < 0) + goto fail; + + if (n->meta.state == NAME_STUB) + LIST_PREPEND(Meta, n->meta.manager->load_queue, &n->meta); + + n->meta.linked = true; + + return 0; + +fail: + t--; + STRV_FOREACH_BACKWARDS(t, n->meta.names) + hashmap_remove(n->meta.manager->names, *t); + + return r; +} + +void name_free(Name *name) { + + assert(name); + + /* Detach from next 'bigger' objects */ + + if (name->meta.linked) { + char **t; + + STRV_FOREACH(t, name->meta.names) + hashmap_remove(name->meta.manager->names, *t); + + if (name->meta.job) + job_free(name->meta.job); + } + + /* Free data and next 'smaller' objects */ + + if (name->meta.job) + job_free(name->meta.job); + + /* FIXME: Other names pointing to us should probably drop their refs to us when we get destructed */ + set_free(name->meta.requires); + set_free(name->meta.soft_requires); + set_free(name->meta.wants); + set_free(name->meta.requisite); + set_free(name->meta.soft_requires); + set_free(name->meta.conflicts); + set_free(name->meta.before); + set_free(name->meta.after); + + switch (name->meta.type) { + + case NAME_SOCKET: { + unsigned i; + Socket *s = SOCKET(name); + + for (i = 0; i < s->n_fds; i++) + nointr_close(s->fds[i]); + break; + } + + case NAME_DEVICE: { + Device *d = DEVICE(name); + + free(d->sysfs); + break; + } + + case NAME_MOUNT: { + Mount *m = MOUNT(name); + + free(m->path); + break; + } + + case NAME_AUTOMOUNT: { + Automount *a = AUTOMOUNT(name); + + free(a->path); + break; + } + + default: + ; + } + + free(name->meta.description); + strv_free(name->meta.names); + + free(name); +} + +bool name_is_ready(Name *name) { + + assert(name); + + if (name->meta.state != NAME_LOADED) + return false; + + assert(name->meta.type < _NAME_TYPE_MAX); + + switch (name->meta.type) { + case NAME_SERVICE: { + Service *s = SERVICE(name); + + return + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD_PRE || + s->state == SERVICE_RELOAD || + s->state == SERVICE_RELOAD_POST; + } + + case NAME_TIMER: { + Timer *t = TIMER(name); + + return + t->state == TIMER_WAITING || + t->state == TIMER_RUNNING; + } + + case NAME_SOCKET: { + Socket *s = SOCKET(name); + + return + s->state == SOCKET_LISTENING || + s->state == SOCKET_RUNNING; + } + + case NAME_MILESTONE: + return MILESTONE(name)->state == MILESTONE_ACTIVE; + + case NAME_DEVICE: + return DEVICE(name)->state == DEVICE_AVAILABLE; + + case NAME_MOUNT: + return MOUNT(name)->state == MOUNT_MOUNTED; + + case NAME_AUTOMOUNT: { + Automount *a = AUTOMOUNT(name); + + return + a->state == AUTOMOUNT_WAITING || + a->state == AUTOMOUNT_RUNNING; + } + + case NAME_SNAPSHOT: + return SNAPSHOT(name)->state == SNAPSHOT_ACTIVE; + + + case _NAME_TYPE_MAX: + case _NAME_TYPE_INVALID: + ; + } + + assert_not_reached("Unknown name type."); + return false; +} + +static int ensure_in_set(Set **s, void *data) { + int r; + + assert(s); + assert(data); + + if (!*s) + if (!(*s = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + if ((r = set_put(*s, data) < 0)) + if (r != -EEXIST) + return r; + + return 0; +} + +int name_augment(Name *n) { + int r; + void* state; + Name *other; + + assert(n); + + /* Adds in the missing links to make all dependencies both-ways */ + + SET_FOREACH(other, n->meta.before, state) + if ((r = ensure_in_set(&other->meta.after, n) < 0)) + return r; + SET_FOREACH(other, n->meta.after, state) + if ((r = ensure_in_set(&other->meta.before, n) < 0)) + return r; + + SET_FOREACH(other, n->meta.conflicts, state) + if ((r = ensure_in_set(&other->meta.conflicts, n) < 0)) + return r; + + SET_FOREACH(other, n->meta.requires, state) + if ((r = ensure_in_set(&other->meta.required_by, n) < 0)) + return r; + SET_FOREACH(other, n->meta.soft_requires, state) + if ((r = ensure_in_set(&other->meta.required_by, n) < 0)) + return r; + SET_FOREACH(other, n->meta.requisite, state) + if ((r = ensure_in_set(&other->meta.required_by, n) < 0)) + return r; + SET_FOREACH(other, n->meta.soft_requisite, state) + if ((r = ensure_in_set(&other->meta.required_by, n) < 0)) + return r; + + return r; +} diff --git a/name.h b/name.h new file mode 100644 index 000000000..3b364f00f --- /dev/null +++ b/name.h @@ -0,0 +1,270 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foonamehfoo +#define foonamehfoo + +#include +#include + +typedef union Name Name; +typedef struct Meta Meta; +typedef struct Service Service; +typedef struct Timer Timer; +typedef struct Socket Socket; +typedef struct Milestone Milestone; +typedef struct Device Device; +typedef struct Mount Mount; +typedef struct Automount Automount; +typedef struct Snapshot Snapshot; + +#include "job.h" +#include "manager.h" +#include "set.h" +#include "util.h" +#include "list.h" + +typedef enum NameType { + NAME_SERVICE = 0, + NAME_TIMER, + NAME_SOCKET, + NAME_MILESTONE, + NAME_DEVICE, + NAME_MOUNT, + NAME_AUTOMOUNT, + NAME_SNAPSHOT, + _NAME_TYPE_MAX, + _NAME_TYPE_INVALID = -1, +} NameType; + +typedef enum NameState { + NAME_STUB, + NAME_LOADED, + NAME_FAILED +} NameState; + +struct Meta { + Manager *manager; + NameType type; + NameState state; + + char **names; + + /* Positive dependencies */ + Set *requires, *soft_requires, *wants, *requisite, *soft_requisite; + Set *required_by; /* inverse of 'requires', 'soft_requires', 'requisite' and 'soft_requisite' is 'required_by' */ + + /* Negative dependencies */ + Set *conflicts; /* inverse of 'conflicts' is 'conflicts' */ + + /* Order */ + Set *before, *after; /* inverse of before is after and vice versa */ + + /* Information */ + char *description; + + /* If there is something to do with this name, then this is + * the job for it */ + Job *job; + + bool linked:1; + + /* Load queue */ + LIST_FIELDS(Meta); +}; + +typedef enum ServiceState { + SERVICE_DEAD, + SERVICE_BEFORE, + SERVICE_START_PRE, + SERVICE_START, + SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_RELOAD_PRE, + SERVICE_RELOAD, + SERVICE_RELOAD_POST, + SERVICE_STOP_PRE, + SERVICE_STOP, + SERVICE_SIGTERM, + SERVICE_SIGKILL, + SERVICE_STOP_POST, + SERVICE_HOLDOFF, + SERVICE_MAINTAINANCE +} ServiceState; + +typedef enum ServiceMode { + SERVICE_ONCE, + SERVICE_RESTART +} ServiceMode; + +struct Service { + Meta meta; + + ServiceState state; + ServiceMode mode; +}; + +typedef enum TimerState { + TIMER_DEAD, + TIMER_BEFORE, + TIMER_START_PRE, + TIMER_START, + TIMER_START_POST, + TIMER_WAITING, + TIMER_RUNNING, + TIMER_STOP_PRE, + TIMER_STOP, + TIMER_STOP_POST, + TIMER_MAINTAINANCE +} TimerState; + +struct Timer { + Meta meta; + + TimerState state; + Service *subject; + + clockid_t clock_id; + usec_t next_elapse; +}; + +typedef enum SocketState { + SOCKET_DEAD, + SOCKET_BEFORE, + SOCKET_START_PRE, + SOCKET_START, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP, + SOCKET_STOP_POST, + SOCKET_MAINTAINANCE +} SocketState; + +struct Socket { + Meta meta; + + SocketState state; + int *fds; + unsigned n_fds; + + Service *subject; +}; + +typedef enum MilestoneState { + MILESTONE_DEAD, + MILESTONE_BEFORE, + MILESTONE_ACTIVE +} MilestoneState; + +struct Milestone { + Meta meta; + + MilestoneState state; +}; + +typedef enum DeviceState { + DEVICE_DEAD, + DEVICE_BEFORE, + DEVICE_AVAILABLE +} DeviceState; + +struct Device { + Meta meta; + + DeviceState state; + char *sysfs; +}; + +typedef enum MountState { + MOUNT_DEAD, + MOUNT_BEFORE, + MOUNT_MOUNTED +} MountState; + +struct Mount { + Meta meta; + + MountState state; + char *path; +}; + +typedef enum AutomountState { + AUTOMOUNT_DEAD, + AUTOMOUNT_BEFORE, + AUTOMOUNT_START_PRE, + AUTOMOUNT_START, + AUTOMOUNT_START_POST, + AUTOMOUNT_WAITING, + AUTOMOUNT_RUNNING, + AUTOMOUNT_STOP_PRE, + AUTOMOUNT_STOP, + AUTOMOUNT_STOP_POST, + AUTOMOUNT_MAINTAINANCE +} AutomountState; + +struct Automount { + Meta meta; + + AutomountState state; + char *path; + Mount *subject; +}; + +typedef enum SnapshotState { + SNAPSHOT_DEAD, + SNAPSHOT_BEFORE, + SNAPSHOT_ACTIVE +} SnapshotState; + +struct Snapshot { + Meta meta; + + SnapshotState state; + bool cleanup:1; +}; + +union Name { + Meta meta; + Service service; + Timer timer; + Socket socket; + Milestone milestone; + Device device; + Mount mount; + Automount automount; + Snapshot snapshot; +}; + +/* For casting a name into the various name types */ + +#define DEFINE_CAST(UPPERCASE, MixedCase, lowercase) \ + static inline MixedCase* UPPERCASE(Name *name) { \ + if (name->meta.type != NAME_##UPPERCASE) \ + return NULL; \ + \ + return &name->lowercase; \ + } + +DEFINE_CAST(SERVICE, Service, service); +DEFINE_CAST(TIMER, Timer, timer); +DEFINE_CAST(SOCKET, Socket, socket); +DEFINE_CAST(MILESTONE, Milestone, milestone); +DEFINE_CAST(DEVICE, Device, device); +DEFINE_CAST(MOUNT, Mount, mount); +DEFINE_CAST(AUTOMOUNT, Automount, automount); +DEFINE_CAST(SNAPSHOT, Snapshot, snapshot); + +/* For casting the various name types into a name */ +#define NAME(o) ((Name*) (o)) + +bool name_is_ready(Name *name); +NameType name_type_from_string(const char *n); + +Name *name_new(Manager *m); +void name_free(Name *name); +int name_link(Name *name); + +int name_augment(Name *n); + +#endif diff --git a/set.c b/set.c new file mode 100644 index 000000000..3aa227bbc --- /dev/null +++ b/set.c @@ -0,0 +1,63 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "set.h" +#include "hashmap.h" + +#define MAKE_SET(h) ((Set*) (h)) +#define MAKE_HASHMAP(s) ((Hashmap*) (s)) + +/* For now this is not much more than a wrapper around a hashmap */ + +Set *set_new(hash_func_t hash_func, compare_func_t compare_func) { + return MAKE_SET(hashmap_new(hash_func, compare_func)); +} + +void set_free(Set* s) { + hashmap_free(MAKE_HASHMAP(s)); +} + +int set_put(Set *s, void *value) { + return hashmap_put(MAKE_HASHMAP(s), value, value); +} + +void *set_get(Set *s, void *value) { + return hashmap_get(MAKE_HASHMAP(s), value); +} + +void *set_remove(Set *s, void *value) { + return hashmap_remove(MAKE_HASHMAP(s), value); +} + +unsigned set_size(Set *s) { + return hashmap_size(MAKE_HASHMAP(s)); +} + +bool set_isempty(Set *s) { + return hashmap_isempty(MAKE_HASHMAP(s)); +} + +void *set_iterate(Set *s, void **state) { + return hashmap_iterate(MAKE_HASHMAP(s), state, NULL); +} + +void *set_iterate_backwards(Set *s, void **state) { + return hashmap_iterate_backwards(MAKE_HASHMAP(s), state, NULL); +} + +void *set_steal_first(Set *s) { + return hashmap_steal_first(MAKE_HASHMAP(s)); +} + +void* set_first(Set *s) { + return hashmap_first(MAKE_HASHMAP(s)); +} + +void* set_last(Set *s) { + return hashmap_last(MAKE_HASHMAP(s)); +} diff --git a/set.h b/set.h new file mode 100644 index 000000000..9aefdbcb5 --- /dev/null +++ b/set.h @@ -0,0 +1,39 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foosethfoo +#define foosethfoo + +/* Pretty straightforward set implementation. Internally based on the + * hashmap. That means that as a minor optimization a NULL set + * object will be treated as empty set for all read + * operations. That way it is not necessary to instantiate an object + * for each set use. */ + +#include "hashmap.h" + +typedef struct Set Set; + +Set *set_new(hash_func_t hash_func, compare_func_t compare_func); +void set_free(Set* set); + +int set_put(Set *s, void *value); +void *set_get(Set *s, void *value); +void *set_remove(Set *s, void *value); + +unsigned set_size(Set *s); +bool set_isempty(Set *s); + +void *set_iterate(Set *h, void **state); +void *set_iterate_backwards(Set *h, void **state); + +void *set_steal_first(Set *h); +void* set_first(Set *h); +void* set_last(Set *h); + +#define SET_FOREACH(e, s, state) \ + for ((state) = NULL, (e) = set_iterate((s), &(state)); (e); (e) = set_iterate((s), &(state))) + +#define SET_FOREACH_BACKWARDS(e, s, state) \ + for ((state) = NULL, (e) = set_iterate_backwards((s), &(state)); (e); (e) = set_iterate_backwards((s), &(state))) + +#endif diff --git a/strv.c b/strv.c new file mode 100644 index 000000000..ecad6d598 --- /dev/null +++ b/strv.c @@ -0,0 +1,117 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include +#include +#include +#include + +#include "util.h" +#include "strv.h" + +char *strv_find(char **l, const char *name) { + assert(l); + assert(name); + + for (; *l; l++) + if (streq(*l, name)) + return *l; + + return NULL; +} + +void strv_free(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + free(l); +} + +char **strv_copy(char **l) { + char **r, **k; + + if (!(r = new(char*, strv_length(l)+1))) + return NULL; + + for (k = r; *l; k++, l++) + if (!(*k = strdup(*l))) + goto fail; + + *k = NULL; + return r; + +fail: + for (k--, l--; k >= r; k--, l--) + free(*k); + + return NULL; +} + +unsigned strv_length(char **l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new(const char *x, ...) { + const char *s; + char **a; + unsigned n = 0, i = 0; + va_list ap; + + if (x) { + n = 1; + + va_start(ap, x); + + while (va_arg(ap, const char*)) + n++; + + va_end(ap); + } + + if (!(a = new(char*, n+1))) + return NULL; + + if (x) { + if (!(a[i] = strdup(x))) { + free(a); + return NULL; + } + + i++; + + va_start(ap, x); + + while ((s = va_arg(ap, const char*))) { + if (!(a[i] = strdup(s))) + goto fail; + + i++; + } + + va_end(ap); + } + + a[i] = NULL; + return a; + +fail: + + for (; i > 0; i--) + if (a[i-1]) + free(a[i-1]); + + free(a); + return NULL; +} diff --git a/strv.h b/strv.h new file mode 100644 index 000000000..43a5c5954 --- /dev/null +++ b/strv.h @@ -0,0 +1,21 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foostrvhfoo +#define foostrvhfoo + +#include "macro.h" + +char *strv_find(char **l, const char *name); +void strv_free(char **l); +char **strv_copy(char **l); +unsigned strv_length(char **l); + +char **strv_new(const char *x, ...) __sentinel; + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (l) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + for (; (l) && ((s) >= (l)); (s)--) + +#endif diff --git a/util.c b/util.c new file mode 100644 index 000000000..3ef190fa2 --- /dev/null +++ b/util.c @@ -0,0 +1,95 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include +#include +#include +#include + +#include "macro.h" +#include "util.h" + +usec_t now(clockid_t clock) { + struct timespec ts; + + assert_se(clock_gettime(clock, &ts) == 0); + + return timespec_load(&ts); +} + +usec_t timespec_load(const struct timespec *ts) { + assert(ts); + + return + (usec_t) ts->tv_sec * USEC_PER_SEC + + (usec_t) ts->tv_nsec / NSEC_PER_USEC; +} + +struct timespec *timespec_store(struct timespec *ts, usec_t u) { + assert(ts); + + ts->tv_sec = (time_t) (u / USEC_PER_SEC); + ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); + + return ts; +} + +usec_t timeval_load(const struct timeval *tv) { + assert(tv); + + return + (usec_t) tv->tv_sec * USEC_PER_SEC + + (usec_t) tv->tv_usec; +} + +struct timeval *timeval_store(struct timeval *tv, usec_t u) { + assert(tv); + + tv->tv_sec = (time_t) (u / USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); + + return tv; +} + +bool endswith(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (sl < pl) + return false; + + return memcmp(s + sl - pl, postfix, pl) == 0; +} + +bool startswith(const char *s, const char *prefix) { + size_t sl, pl; + + assert(s); + assert(prefix); + + sl = strlen(s); + pl = strlen(prefix); + + if (sl < pl) + return false; + + return memcmp(s, prefix, pl) == 0; +} + +int nointr_close(int fd) { + assert(fd >= 0); + + for (;;) { + int r; + + if ((r = close(fd)) >= 0) + return r; + + if (errno != EINTR) + return r; + } +} diff --git a/util.h b/util.h new file mode 100644 index 000000000..5d9ef39e0 --- /dev/null +++ b/util.h @@ -0,0 +1,49 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooutilhfoo +#define fooutilhfoo + +#include +#include +#include +#include + +typedef uint64_t usec_t; + +#define USEC_PER_SEC 1000000ULL +#define NSEC_PER_USEC 1000ULL + +usec_t now(clockid_t clock); + +usec_t timespec_load(const struct timespec *ts); +struct timespec *timespec_store(struct timespec *ts, usec_t u); + +usec_t timeval_load(const struct timeval *tv); +struct timeval *timeval_store(struct timeval *tv, usec_t u); + +#define streq(a,b) (strcmp((a),(b)) == 0) + +#define new(t, n) ((t*) malloc(sizeof(t)*(n))) + +#define new0(t, n) ((t*) calloc((n), sizeof(t))) + +#define malloc0(n) (calloc((n), 1)) + +static inline const char* yes_no(bool b) { + return b ? "yes" : "no"; +} + +static inline const char* strempty(const char *s) { + return s ? s : ""; +} + +static inline const char* strnull(const char *s) { + return s ? s : "(null)"; +} + +bool endswith(const char *s, const char *postfix); +bool startswith(const char *s, const char *prefix); + +int nointr_close(int fd); + +#endif