# HG changeset patch # User Olaf Wintermann # Date 1349521207 -7200 # Node ID 450d2d5f4735ae42d5a6a0606ddd252c49608ec7 # Parent 4417619a9bbdf9f63e6f6a85a3782db22e381ef5 server can reload configuration diff -r 4417619a9bbd -r 450d2d5f4735 src/server/config/conf.c --- a/src/server/config/conf.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/config/conf.c Sat Oct 06 13:00:07 2012 +0200 @@ -143,7 +143,7 @@ } sstr_t line = sstr(buf); - return sstrdub(line); + return sstrdup(line); } sstr_t s; diff -r 4417619a9bbd -r 450d2d5f4735 src/server/config/serverconf.c --- a/src/server/config/serverconf.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/config/serverconf.c Sat Oct 06 13:00:07 2012 +0200 @@ -87,7 +87,7 @@ sstr_t tag = cfg_get_end_tag_name(line); if(sstrcmp(tag, conf->obj->type) != 0) { fprintf(stderr, "syntax error: wrong close tag\n"); - fprintf(stderr, "open tag: %s close tag: %s\n", sstrdub(tag).ptr, sstrdub(conf->obj->type).ptr); + fprintf(stderr, "open tag: %s close tag: %s\n", sstrdup(tag).ptr, sstrdup(conf->obj->type).ptr); exit(-1); } conf->obj = NULL; diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/config.c --- a/src/server/daemon/config.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/config.c Sat Oct 06 13:00:07 2012 +0200 @@ -38,6 +38,7 @@ #include #include "../ucx/string.h" +#include "../ucx/atomic.h" #include "httplistener.h" #include "config.h" @@ -111,6 +112,7 @@ fprintf(stderr, "Cannot load server.conf\n"); } ServerConfiguration *serverconfig = malloc(sizeof(ServerConfiguration)); + serverconfig->ref = 1; serverconfig->pool = pool_create(); serverconfig->listeners = NULL; serverconfig->host_vs = ucx_map_new(16); @@ -184,29 +186,30 @@ cfg_handle_vs(serverconfig, scfgobj); } - /* check event handler config */ + // check event handler config if(check_event_handler_cfg() != 0) { /* critical error */ return NULL; } - /* check thread pool config */ + // check thread pool config if(check_thread_pool_cfg() != 0) { /* critical error */ return NULL; } - /* set VirtualServer for all listeners */ + // set VirtualServer for all listeners UcxList *ls = serverconfig->listeners; while(ls) { HttpListener *listener = ls->data; sstr_t vsname = sstr(listener->default_vs.vs_name); - /* search for VirtualServer */ - int b = 0; + // search for VirtualServer + //int b = 0; UcxMapIterator iter = ucx_map_iterator(serverconfig->host_vs); - UCX_MAP_FOREACH(VirtualServer*, vs, serverconfig->host_vs, iter) { + VirtualServer *vs; + UCX_MAP_FOREACH(vs, iter) { if(!sstrcmp(vsname, vs->name)) { listener->default_vs.vs = vs; break; @@ -219,16 +222,27 @@ return serverconfig; } +void cfg_ref(ServerConfiguration *cfg) { + ucx_atomic_inc_32(&cfg->ref); +} + +void cfg_unref(ServerConfiguration *cfg) { + uint32_t ref = ucx_atomic_dec_32_nv(&cfg->ref); + if(ref == 0) { + // TODO: free configuration + } +} + void init_server_config_parser() { } int cfg_handle_runtime(ServerConfiguration *cfg, ServerConfigObject *obj) { - cfg->user = sstrdub(cfg_directivelist_get_str( + cfg->user = sstrdup(cfg_directivelist_get_str( obj->directives, sstr("User"))); - cfg->tmp = sstrdub(cfg_directivelist_get_str( + cfg->tmp = sstrdup(cfg_directivelist_get_str( obj->directives, sstr("Temp"))); @@ -246,7 +260,7 @@ LogConfig logcfg; logcfg.file = sstrdup(file).ptr; - logcfg.level = sstrdub(lvl).ptr; + logcfg.level = sstrdup(lvl).ptr; /* TODO: stdout, stderr config */ int ret = init_log_file(&logcfg); @@ -289,21 +303,23 @@ int cfg_handle_listener(ServerConfiguration *cfg, ServerConfigObject *obj) { ListenerConfig lc; + lc.cfg = cfg; lc.port = 8080; lc.nacceptors = 1; - - lc.name = sstrdub(cfg_directivelist_get_str( + + // TODO: use sstrdup_pool? + lc.name = sstrdup(cfg_directivelist_get_str( obj->directives, sstr("Name"))); lc.port = atoi(cfg_directivelist_get_str( obj->directives, sstr("Port")).ptr); - lc.vs = sstrdub(cfg_directivelist_get_str( + lc.vs = sstrdup(cfg_directivelist_get_str( obj->directives, sstr("DefaultVS"))); - HttpListener *listener = http_listener_new(&lc); + HttpListener *listener = http_listener_create(&lc); listener->default_vs.vs_name = lc.vs.ptr; cfg->listeners = ucx_list_append(cfg->listeners, listener); @@ -313,13 +329,13 @@ int cfg_handle_vs(ServerConfiguration *cfg, ServerConfigObject *obj) { VirtualServer *vs = vs_new(); - vs->name = sstrdub(cfg_directivelist_get_str( + vs->name = sstrdup(cfg_directivelist_get_str( obj->directives, sstr("Name"))); - vs->host = sstrdub(cfg_directivelist_get_str( + vs->host = sstrdup(cfg_directivelist_get_str( obj->directives, sstr("Host"))); - vs->document_root = sstrdub(cfg_directivelist_get_str( + vs->document_root = sstrdup(cfg_directivelist_get_str( obj->directives, sstr("DocRoot"))); sstr_t objfile = cfg_directivelist_get_str( @@ -335,12 +351,13 @@ ConfigFile *f = cfgmgr_get_file(file); if(f == NULL) { f = malloc(sizeof(ConfigFile)); - f->file = sstrdub(file); + f->file = sstrdup(file); f->reload = object_conf_reload; f->reload(f, cfg); cfgmgr_attach_file(f); } - vs->objects = (HTTPObjectConfig*)f->data; + vs->objectfile = sstrdup(file); // TODO: pool + vs->objects = (HTTPObjectConfig*)f->data; // TODO: ref ucx_map_sstr_put(cfg->host_vs, vs->host, vs); @@ -387,10 +404,10 @@ char *name = NULL; char *ppath = NULL; if(cob->name.length > 0) { - name = sstrdub(cob->name).ptr; + name = sstrdup(cob->name).ptr; } if(cob->ppath.length > 0) { - ppath = sstrdub(cob->ppath).ptr; + ppath = sstrdup(cob->ppath).ptr; } /* create and add object */ diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/config.h --- a/src/server/daemon/config.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/config.h Sat Oct 06 13:00:07 2012 +0200 @@ -29,6 +29,8 @@ #ifndef CONF_H #define CONF_H +#include + #include "../util/object.h" #include "../config/objconf.h" @@ -54,6 +56,7 @@ UcxMap *authdbs; sstr_t tmp; sstr_t user; + uint32_t ref; // reference counter } ServerConfiguration; @@ -86,6 +89,8 @@ int cfg_handle_vs(ServerConfiguration *cfg, ServerConfigObject *obj); ServerConfiguration* load_server_conf(ServerConfiguration *old, char *file); +void cfg_ref(ServerConfiguration *cfg); +void cfg_unref(ServerConfiguration *cfg); int object_conf_reload(ConfigFile *file, ServerConfiguration *cfg); diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/configmanager.c --- a/src/server/daemon/configmanager.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/configmanager.c Sat Oct 06 13:00:07 2012 +0200 @@ -31,6 +31,10 @@ #include "../public/nsapi.h" +#include "../ucx/string.h" + +#include "httplistener.h" +#include "log.h" #include "configmanager.h" ServerConfiguration *current_config = NULL; @@ -53,12 +57,44 @@ return ucx_map_sstr_get(config_files, name); } +// copy functions +static void* copy_listener(HttpListener *ls, ServerConfiguration *cfg) { + /* + * we reuse the old listener, but change the + * ServerConfiguration and VirtualServer + */ + http_listener_ref(ls); + ls->cfg = cfg; + + sstr_t vsname = ls->default_vs.vs->name; + ls->default_vs.vs = ucx_map_sstr_get(cfg->host_vs, vsname); + + return ls; +} + +static void* copy_vs(void *vserver, void *pool) { + VirtualServer *vs = vs_copy(vserver, pool); + + /* + * this function is executed on configuration reload, so some + * VS configs may be changed + * + * vs + * objects + */ + ConfigFile *objectfile = cfgmgr_get_file(vs->objectfile); + vs->objects = objectfile->data; + return vs; +} + + int cfgmgr_load_config() { int cfgreload = 0; /* check config files */ UcxMapIterator iter = ucx_map_iterator(config_files); - UCX_MAP_FOREACH(ConfigFile*, f, config_files, iter) { + ConfigFile *f; + UCX_MAP_FOREACH(f, iter) { struct stat s; if(stat(f->file.ptr, &s) != 0) { fprintf( @@ -70,6 +106,11 @@ if(f->last_modified != s.st_mtim.tv_sec) { /* reload the file */ + //printf("reload: %s\n", sstrdup(f->file).ptr); + log_ereport( + LOG_LEVEL_INFO, + "reload configuration file: %s", + f->file.ptr); f->reload(f, current_config); cfgreload = 1; } @@ -83,6 +124,7 @@ ServerConfiguration *config; if(sc_last_modified != s.st_mtim.tv_sec) { + printf("cfgmgr load server.conf\n"); config = load_server_conf( current_config, "conf/server.conf"); @@ -91,13 +133,35 @@ fprintf(stderr, "Cannot load server.conf\n"); return -1; } + + sc_last_modified = s.st_mtim.tv_sec; } else if(cfgreload) { /* copy configuration */ - + printf("cfgmgr copy server.conf\n"); + /* TODO: copy */ + /* config = load_server_conf( current_config, "conf/server.conf"); + */ + config = malloc(sizeof(ServerConfiguration)); + config->ref = 1; + config->pool = pool_create(); + config->user = sstrdup_pool(config->pool, current_config->user); + config->tmp = sstrdup_pool(config->pool, current_config->tmp); + + // copy configuration + config->host_vs = ucx_map_clone( + current_config->host_vs, + copy_vs, + config->pool); + + config->listeners = ucx_list_clone( + current_config->listeners, + (copy_func)copy_listener, + config); + if(config == NULL) { fprintf(stderr, "Cannot load server.conf\n"); @@ -105,10 +169,11 @@ } } else { + printf("no reconfig required!\n"); config = current_config; } - + current_config = config; return 0; } diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/httplistener.c --- a/src/server/daemon/httplistener.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/httplistener.c Sat Oct 06 13:00:07 2012 +0200 @@ -47,6 +47,7 @@ #include #include "../ucx/map.h" +#include "../ucx/atomic.h" #include "httplistener.h" #include "session.h" @@ -67,8 +68,55 @@ return 0; } +HttpListener* http_listener_create(ListenerConfig *conf) { + if(listener_map == NULL) { + listener_map = ucx_map_new(16); + } + + HttpListener *fl = ucx_map_sstr_get(listener_map, conf->name); + if(fl == NULL) { + return http_listener_new(conf); + } + + HttpListener* newls = malloc(sizeof(HttpListener)); + if(newls == NULL) { + // TODO: error + } + + newls->cfg = conf->cfg; + newls->default_vs.vs_name = conf->vs.ptr; + newls->port = fl->port; + newls->server_socket = fl->server_socket; + newls->session_handler = fl->session_handler; // TODO + newls->ref = 2; // 1 reference is fl->next + + // create acceptor threads + newls->acceptors = calloc(newls->nacceptors, sizeof(void*)); + for (int i=0;inacceptors;i++) { + newls->acceptors[i] = acceptor_new(newls); + } + + // fl hold one reference of newls + fl->next = newls; + + + ucx_map_sstr_put(listener_map, newls->name, newls); + + for (int i=0;inacceptors;i++) { + acceptor_start(newls->acceptors[i]); + } + + // check if a restart is required to apply all changes + + if(newls->port != conf->port) { + // TODO: log + } + + return newls; +} HttpListener* http_listener_new(ListenerConfig *conf) { + // TODO: remove if(listener_map == NULL) { listener_map = ucx_map_new(16); } @@ -77,6 +125,7 @@ if(fl != NULL) { return fl; } + // end remove HttpListener *listener = malloc(sizeof(HttpListener)); listener->name = conf->name; @@ -84,6 +133,8 @@ listener->session_handler = create_event_session_handler(); listener->nacceptors = conf->nacceptors; listener->port = conf->port; + listener->ref = 1; + listener->next = NULL; ucx_map_sstr_put(listener_map, listener->name, listener); struct sockaddr_in servaddr; /* server address */ @@ -140,6 +191,20 @@ return 0; } +void http_listener_ref(HttpListener *listener) { + ucx_atomic_inc_32(&listener->ref); +} + +void http_listener_unref(HttpListener *listener) { + uint32_t ref = ucx_atomic_dec_32_nv(&listener->ref); + if(ref == 0) { + free(listener->acceptors); + // TODO: unref cfg + // TODO: unref session handler + free(listener); + } +} + Acceptor* acceptor_new(HttpListener *listener) { @@ -178,22 +243,33 @@ continue; } + // check listener + HttpListener *ls = listener; + int acceptor_exit = 0; + while(ls->next) { + ls = ls->next; + acceptor_exit = 1; + } + /* create Connection object */ Connection *conn = malloc(sizeof(Connection)); conn->address = ca; conn->fd = clientfd; - conn->listener = listener; + conn->listener = ls; /* enqueue the connection */ - listener->session_handler->enqueue_connection( - listener->session_handler, + ls->session_handler->enqueue_connection( + ls->session_handler, conn); /* ready for new connection */ - if(0) { + if(acceptor_exit) { break; } } + http_listener_unref(listener->next); + http_listener_unref(listener); + return NULL; } diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/httplistener.h --- a/src/server/daemon/httplistener.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/httplistener.h Sat Oct 06 13:00:07 2012 +0200 @@ -31,6 +31,8 @@ #include "sessionhandler.h" +#include "config.h" + #ifdef __cplusplus extern "C" { #endif @@ -45,11 +47,12 @@ char *vs_name; }; struct _listener_config { - sstr_t name; - sstr_t vs; - char *address; - int port; - int nacceptors; + ServerConfiguration *cfg; + sstr_t name; + sstr_t vs; + char *address; + int port; + int nacceptors; }; struct _acceptor { @@ -58,21 +61,30 @@ }; struct _http_listener { - sstr_t name; - union vs default_vs; - int port; - int server_socket; - Acceptor **acceptors; - int nacceptors; - SessionHandler *session_handler; + ServerConfiguration *cfg; + sstr_t name; + union vs default_vs; + int port; + int server_socket; + SessionHandler *session_handler; + HttpListener *next; + Acceptor **acceptors; + int nacceptors; + uint32_t ref; // reference counter }; int start_all_listener(); +HttpListener* http_listener_create(ListenerConfig *conf); + +// used by http_listener_create HttpListener* http_listener_new(ListenerConfig *conf); int http_listener_start(HttpListener *listener); +void http_listener_ref(HttpListener *listener); +void http_listener_unref(HttpListener *listener); + Acceptor* acceptor_new(HttpListener *listener); void acceptor_start(Acceptor *a); diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/httprequest.c --- a/src/server/daemon/httprequest.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/httprequest.c Sat Oct 06 13:00:07 2012 +0200 @@ -451,7 +451,7 @@ } /* if there is a trailing '/', remove it */ if(docroot.ptr[docroot.length - 1] == '/') { - docroot.ptr[docroot.length - 1] = 0; // TODO: can I do this? + //docroot.ptr[docroot.length - 1] = 0; // TODO: can I do this? No!! docroot.length--; } diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/sessionhandler.c --- a/src/server/daemon/sessionhandler.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/sessionhandler.c Sat Oct 06 13:00:07 2012 +0200 @@ -211,5 +211,6 @@ // TODO: critical error } + // process request r = handle_request(request); } diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/vserver.c --- a/src/server/daemon/vserver.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/vserver.c Sat Oct 06 13:00:07 2012 +0200 @@ -32,8 +32,20 @@ VirtualServer *vs = malloc(sizeof(VirtualServer)); vs->objects = NULL; vs->document_root = sstr("docs"); + vs->ref = 1; return vs; } +VirtualServer* vs_copy(VirtualServer *vs, pool_handle_t *pool) { + VirtualServer *newvs = malloc(sizeof(VirtualServer)); + newvs->ref = 1; + newvs->document_root = sstrdup_pool(pool, vs->document_root); + newvs->host = sstrdup_pool(pool, vs->host); + newvs->name = sstrdup_pool(pool, vs->name); + newvs->objectfile = sstrdup_pool(pool, vs->objectfile); + + newvs->objects = vs->objects; + + return newvs; +} - diff -r 4417619a9bbd -r 450d2d5f4735 src/server/daemon/vserver.h --- a/src/server/daemon/vserver.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/daemon/vserver.h Sat Oct 06 13:00:07 2012 +0200 @@ -43,13 +43,16 @@ sstr_t host; // TODO: list of listeners, check listener of vs + sstr_t objectfile; HTTPObjectConfig *objects; sstr_t document_root; + + uint32_t ref; // reference counter }; VirtualServer* vs_new(); - +VirtualServer* vs_copy(VirtualServer *vs, pool_handle_t *pool); #ifdef __cplusplus diff -r 4417619a9bbd -r 450d2d5f4735 src/server/safs/init.c --- a/src/server/safs/init.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/safs/init.c Sat Oct 06 13:00:07 2012 +0200 @@ -72,7 +72,7 @@ } struct FuncStruct fc; fc.func = (FuncPtr)sym; - fc.name = sstrdub(sstr(funcs)).ptr; + fc.name = sstrdup(sstr(funcs)).ptr; add_function(&fc); if(b) { diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/allocator.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/ucx/allocator.c Sat Oct 06 13:00:07 2012 +0200 @@ -0,0 +1,14 @@ +#include +#include "allocator.h" + +void *ucx_default_malloc(void *ignore, size_t n) { + return malloc(n); +} + +void *ucx_default_calloc(void *ignore, size_t n, size_t size) { + return calloc(n, size); +} + +void *ucx_default_realloc(void *ignore, void *data, size_t n) { + return realloc(data, n); +} diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/allocator.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/ucx/allocator.h Sat Oct 06 13:00:07 2012 +0200 @@ -0,0 +1,31 @@ +#ifndef ALLOCATOR_H +#define ALLOCATOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void*(*ucx_allocator_malloc)(void *pool, size_t n); +typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size); +typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n); + +typedef struct { + void *pool; + ucx_allocator_malloc malloc; + ucx_allocator_calloc calloc; + ucx_allocator_realloc realloc; +} UcxAllocator; + +void *ucx_default_malloc(void *ignore, size_t n); +void *ucx_default_calloc(void *ignore, size_t n, size_t size); +void *ucx_default_realloc(void *ignore, void *data, size_t n); + +#define UCX_ALLOCATOR_DEFAULT {NULL, \ + ucx_default_malloc, ucx_default_calloc, ucx_default_realloc} + +#ifdef __cplusplus +} +#endif + +#endif /* ALLOCATOR_H */ + diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/atomic.c --- a/src/server/ucx/atomic.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/atomic.c Sat Oct 06 13:00:07 2012 +0200 @@ -22,4 +22,34 @@ atomic_inc_64(t); } +void ucx_atomic_dec_8(volatile uint8_t *t) { + atomic_dec_8(t); +} +void ucx_atomic_dec_16(volatile uint16_t *t) { + atomic_dec_16(t); +} + +void ucx_atomic_dec_32(volatile uint32_t *t) { + atomic_dec_32(t); +} + +void ucx_atomic_dec_64(volatile uint64_t *t) { + atomic_dec_64(t); +} + +uint8_t ucx_atomic_dec_8_nv(volatile uint8_t *t) { + return atomic_dec_8_nv(t); +} + +uint16_t ucx_atomic_dec_16_nv(volatile uint16_t *t) { + return atomic_dec_16_nv(t); +} + +uint32_t ucx_atomic_dec_32_nv(volatile uint32_t *t) { + return atomic_dec_32_nv(t); +} + +uint64_t ucx_atomic_dec_64_nv(volatile uint64_t *t) { + return atomic_dec_64_nv(t); +} diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/atomic.h --- a/src/server/ucx/atomic.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/atomic.h Sat Oct 06 13:00:07 2012 +0200 @@ -22,6 +22,22 @@ void ucx_atomic_inc_64(volatile uint64_t *t); +void ucx_atomic_dec_8(volatile uint8_t *t); + +void ucx_atomic_dec_16(volatile uint16_t *t); + +void ucx_atomic_dec_32(volatile uint32_t *t); + +void ucx_atomic_dec_64(volatile uint64_t *t); + +uint8_t ucx_atomic_dec_8_nv(volatile uint8_t *t); + +uint16_t ucx_atomic_dec_16_nv(volatile uint16_t *t); + +uint32_t ucx_atomic_dec_32_nv(volatile uint32_t *t); + +uint64_t ucx_atomic_dec_64_nv(volatile uint64_t *t); + #ifdef __cplusplus } diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/dlist.c --- a/src/server/ucx/dlist.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/dlist.c Sat Oct 06 13:00:07 2012 +0200 @@ -112,6 +112,96 @@ return s; } +UcxDlist *ucx_dlist_sort_merge(int length, + UcxDlist* ls, UcxDlist* rs, UcxDlist* le, UcxDlist* re, + cmp_func fnc, void* data) { + UcxDlist *sorted[length]; + UcxDlist *rc, *lc; + + lc = ls; rc = rs; + int n = 0; + while (lc != le && rc != re) { + if (fnc(lc->data, rc->data, data) <= 0) { + sorted[n] = lc; + lc = lc->next; + } else { + sorted[n] = rc; + rc = rc->next; + } + n++; + } + while (lc != le) { + sorted[n] = lc; + lc = lc->next; + n++; + } + while (rc != re) { + sorted[n] = rc; + rc = rc->next; + n++; + } + + // Update pointer + sorted[0]->prev = NULL; + for (int i = 0 ; i < length-1 ; i++) { + sorted[i]->next = sorted[i+1]; + sorted[i+1]->prev = sorted[i]; + } + sorted[length-1]->next = NULL; + + return sorted[0]; +} + +UcxDlist *ucx_dlist_sort(UcxDlist *l, cmp_func fnc, void *data) { + if (l == NULL) { + return NULL; + } + + UcxDlist *lc; + int ln = 1; + + UcxDlist *ls = l, *le; + lc = ls; + while (lc->next != NULL && fnc(lc->next->data, lc->data, data) > 0) { + lc = lc->next; + ln++; + } + le = lc->next; + + UcxDlist *rs = le, *re; + if (rs == NULL) { + return l; // this list is already sorted :) + } else { + UcxDlist *rc; + int rn = 1; + rc = rs; + while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) { + rc = rc->next; + rn++; + } + re = rc->next; + + // Something left? Sort it! + UcxDlist *remainder = re; + size_t remainder_length = ucx_dlist_size(remainder); + if (remainder != NULL) { + remainder = ucx_dlist_sort(remainder, fnc, data); + } + + // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them + UcxDlist *sorted = ucx_dlist_sort_merge(ln+rn, + ls, rs, le, re, + fnc, data); + + // merge sorted list with (also sorted) remainder + l = ucx_dlist_sort_merge(ln+rn+remainder_length, + sorted, remainder, NULL, NULL, + fnc, data); + + return l; + } +} + /* dlist specific functions */ UcxDlist *ucx_dlist_first(UcxDlist *l) { if (l == NULL) return NULL; diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/dlist.h --- a/src/server/ucx/dlist.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/dlist.h Sat Oct 06 13:00:07 2012 +0200 @@ -30,6 +30,8 @@ UcxDlist *ucx_dlist_get(UcxDlist *l, int index); size_t ucx_dlist_size(UcxDlist *l); +UcxDlist *ucx_dlist_sort(UcxDlist *l, cmp_func fnc, void *data); + /* dlist specific functions */ UcxDlist *ucx_dlist_first(UcxDlist *l); UcxDlist *ucx_dlist_remove(UcxDlist *l, UcxDlist *e); diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/list.c --- a/src/server/ucx/list.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/list.c Sat Oct 06 13:00:07 2012 +0200 @@ -25,7 +25,7 @@ l1 = l1->next; l2 = l2->next; } - + return (l1 == NULL && l2 == NULL); } @@ -41,7 +41,7 @@ UcxList *ucx_list_append(UcxList *l, void *data) { UcxList *nl = (UcxList*) malloc(sizeof(UcxList)); if (nl == NULL) return NULL; - + nl->data = data; nl->next = NULL; if (l == NULL) { @@ -56,7 +56,7 @@ UcxList *ucx_list_prepend(UcxList *l, void *data) { UcxList *nl = ucx_list_append(NULL, data); if (nl == NULL) return NULL; - + if (l != NULL) { nl->next = l; } @@ -75,7 +75,7 @@ UcxList *ucx_list_last(UcxList *l) { if (l == NULL) return NULL; - + UcxList *e = l; while (e->next != NULL) { e = e->next; @@ -91,13 +91,13 @@ e = e->next; index--; } - + return index == 0 ? e : NULL; } size_t ucx_list_size(UcxList *l) { if (l == NULL) return 0; - + UcxList *e = l; size_t s = 1; while (e->next != NULL) { @@ -108,13 +108,91 @@ return s; } -void ucx_list_foreach(UcxList *l, ucx_callback fnc, void* data) { - UcxList *e = l; - UcxList *n; - while (e != NULL) { - n = e->next; - fnc(e, data); - e = n; +UcxList *ucx_list_sort_merge(int length, + UcxList* ls, UcxList* rs, UcxList* le, UcxList* re, + cmp_func fnc, void* data) { + UcxList *sorted[length]; + UcxList *rc, *lc; + + lc = ls; rc = rs; + int n = 0; + while (lc != le && rc != re) { + if (fnc(lc->data, rc->data, data) <= 0) { + sorted[n] = lc; + lc = lc->next; + } else { + sorted[n] = rc; + rc = rc->next; + } + n++; + } + while (lc != le) { + sorted[n] = lc; + lc = lc->next; + n++; + } + while (rc != re) { + sorted[n] = rc; + rc = rc->next; + n++; + } + + // Update pointer + for (int i = 0 ; i < length-1 ; i++) { + sorted[i]->next = sorted[i+1]; + } + sorted[length-1]->next = NULL; + + return sorted[0]; +} + +UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data) { + if (l == NULL) { + return NULL; + } + + UcxList *lc; + int ln = 1; + + UcxList *ls = l, *le; + lc = ls; + while (lc->next != NULL && fnc(lc->next->data, lc->data, data) > 0) { + lc = lc->next; + ln++; + } + le = lc->next; + + UcxList *rs = le, *re; + if (rs == NULL) { + return l; // this list is already sorted :) + } else { + UcxList *rc; + int rn = 1; + rc = rs; + while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) { + rc = rc->next; + rn++; + } + re = rc->next; + + // Something left? Sort it! + UcxList *remainder = re; + size_t remainder_length = ucx_list_size(remainder); + if (remainder != NULL) { + remainder = ucx_list_sort(remainder, fnc, data); + } + + // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them + UcxList *sorted = ucx_list_sort_merge(ln+rn, + ls, rs, le, re, + fnc, data); + + // merge sorted list with (also sorted) remainder + l = ucx_list_sort_merge(ln+rn+remainder_length, + sorted, remainder, NULL, NULL, + fnc, data); + + return l; } } @@ -128,7 +206,7 @@ while (f->next != NULL && f->next != e) { f = f->next; } - /* perform remove iff this element is found in this list */ + /* perform remove if this element is found in this list */ if (f->next == e) { f->next = e->next; free(e); diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/list.h --- a/src/server/ucx/list.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/list.h Sat Oct 06 13:00:07 2012 +0200 @@ -1,5 +1,5 @@ /* - * + * */ #ifndef LIST_H @@ -11,7 +11,7 @@ #ifdef __cplusplus extern "C" { #endif - + typedef struct UcxList UcxList; struct UcxList { void *data; @@ -28,7 +28,8 @@ UcxList *ucx_list_last(UcxList *l); UcxList *ucx_list_get(UcxList *l, int index); size_t ucx_list_size(UcxList *l); -void ucx_list_foreach(UcxList *l, ucx_callback fnc, void *data); + +UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data); /* list specific functions */ UcxList *ucx_list_remove(UcxList *l, UcxList *e); diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/map.c --- a/src/server/ucx/map.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/map.c Sat Oct 06 13:00:07 2012 +0200 @@ -8,6 +8,10 @@ #include "map.h" UcxMap *ucx_map_new(size_t size) { + if(size == 0) { + size = 16; + } + UcxMap *map = (UcxMap*)malloc(sizeof(UcxMap)); if(map == NULL) { return NULL; @@ -19,6 +23,7 @@ return NULL; } map->size = size; + map->count = 0; return map; } @@ -39,6 +44,48 @@ free(map); } +int ucx_map_copy(UcxMap *from, UcxMap *to, copy_func fnc, void *data) { + UcxMapIterator i = ucx_map_iterator(from); + void *value; + UCX_MAP_FOREACH(value, i) { + int ret = ucx_map_put(to, i.cur->key, fnc ? fnc(value, data) : value); + if(ret != 0) { + return 1; + } + } + return 0; +} + +UcxMap *ucx_map_clone(UcxMap *map, copy_func fnc, void *data) { + size_t bs = (map->count * 5) >> 1; + UcxMap *newmap = ucx_map_new(bs > map->size ? bs : map->size); + if(newmap == NULL) { + return NULL; + } + ucx_map_copy(map, newmap, fnc, data); + return newmap; +} + +int ucx_map_rehash(UcxMap *map) { + size_t load = (map->size * 3) >> 2; + if (map->count > load) { + UcxMap oldmap; + oldmap.map = map->map; + oldmap.size = map->size; + oldmap.count = map->count; + + map->size = (map->count * 5) >> 1; + map->map = (UcxMapElement**)calloc(map->size, sizeof(UcxMapElement*)); + if(map->map == NULL) { + *map = oldmap; + return 1; + } + map->count = 0; + ucx_map_copy(&oldmap, map, NULL, NULL); + } + return 0; +} + int ucx_map_put(UcxMap *map, UcxKey key, void *data) { if(key.hash == 0) { key.hash = ucx_hash((char*)key.data, key.len); @@ -76,6 +123,7 @@ memcpy(kd, key.data, key.len); key.data = kd; elm->key = key; + map->count++; } elm->data = data; @@ -138,8 +186,11 @@ switch (len) { case 3: h ^= (data[i + 2] & 0xFF) << 16; + /* no break */ case 2: h ^= (data[i + 1] & 0xFF) << 8; + /* no break */ case 1: h ^= (data[i + 0] & 0xFF); h *= m; + /* no break */ } h ^= h >> 13; @@ -186,3 +237,124 @@ return 1; } + +int ucx_map_load_enc(UcxMap *map, FILE *f, UcxAllocator allocator, + ucx_map_coder decoder, void* decdata) { + + int c; int r, n; + + char *key, *value; + + while ((c = fgetc(f)) > 0) { + /* Discard leading spaces and comments */ + if (c < 33) continue; + if (c == '#' || c == '!') { + while ((c = (char) fgetc(f)) > 0) { + if (c == '\n') break; + } + continue; + } + + /* read into key buffer */ + n = 16; + key = malloc(n); + r = 0; + do { + if (c == '=') break; + if (r > n - 2) { + n *= 2; + key = realloc(key, n); + } + key[r] = c; + r++; + } while ((c = fgetc(f)) > 0); + if (c <= 0) { + free(key); + return 1; + } + key[r] = 0; + while (key[--r] == ' ') key[r] = 0; + + /* skip whitespaces */ + while ((c = fgetc(f)) > 0) { + if (c > 32) break; + } + if (c <= 0) { + free(key); + return 1; + } + + /* read into value buffer */ + n = 64; + value = malloc(n); + r = 0; + do { + if (c == '\n') break; + if (r > n - 2) { + n *= 2; + value = realloc(value, n); + } + value[r] = c; + r++; + } while ((c = fgetc(f)) > 0); + value[r] = 0; + while (value[--r] < 33) value[r] = 0; + + if (decoder) { + size_t decodedSize; + void *decoded = decoder(value, decdata, &decodedSize); + free(value); + value = decoded; + r = decodedSize; + } else { + r += 2; + value = realloc(value, r); + } + + if (allocator.pool) { + void *pooledValue = allocator.malloc(allocator.pool, r); + memcpy(pooledValue, value, r); + free(value); + value = pooledValue; + } + + ucx_map_cstr_put(map, key, value); + free(key); + } + + return 0; +} + +int ucx_map_store_enc(UcxMap *map, FILE *f, + ucx_map_coder encoder, void *encdata) { + UcxMapIterator iter = ucx_map_iterator(map); + char *k, *v; + sstr_t key, value; + int written; + + UCX_MAP_FOREACH(v, iter) { + k = (char*) iter.cur->key.data; + key = sstr(k); + if (encoder) { + size_t encodedSize; + void *encoded = encoder(v, encdata, &encodedSize); + value = sstrn(encoded,encodedSize - 1); + } else { + value = sstr(v); + } + + written = 0; + written += fwrite(key.ptr, 1, key.length, f); + written += fwrite(" = ", 1, 3, f); + written += fwrite(value.ptr, 1, value.length, f); + written += fwrite("\n", 1, 1, f); + + if (encoder) { + free(value.ptr); + } + + if (written != key.length + value.length + 4) return 1; + } + + return 0; +} diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/map.h --- a/src/server/ucx/map.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/map.h Sat Oct 06 13:00:07 2012 +0200 @@ -7,22 +7,33 @@ #include "ucx.h" #include "string.h" +#include "mempool.h" +#include #ifdef __cplusplus extern "C" { #endif -#define UCX_MAP_FOREACH(type,elm,map,iter) \ - for(type elm;ucx_map_iter_next(&iter,(void*)&elm)==0;) +#define UCX_MAP_FOREACH(elm,iter) \ + for(;ucx_map_iter_next(&iter,(void*)&elm)==0;) typedef struct UcxMap UcxMap; typedef struct UcxKey UcxKey; typedef struct UcxMapElement UcxMapElement; typedef struct UcxMapIterator UcxMapIterator; +/* + * param 1: element + * param 2: additional data + * param 3: size of encoded data will be stored here + * returns encoded / decoded string or NULL on failure + */ +typedef void*(*ucx_map_coder)(void*,void*,size_t*); + struct UcxMap { UcxMapElement **map; size_t size; + size_t count; }; struct UcxKey { @@ -46,6 +57,10 @@ UcxMap *ucx_map_new(size_t size); void ucx_map_free(UcxMap *map); +/* you cannot clone maps with more than 390 mio entries */ +int ucx_map_copy(UcxMap *from, UcxMap *to, copy_func fnc, void *data); +UcxMap *ucx_map_clone(UcxMap *map, copy_func fnc, void *data); +int ucx_map_rehash(UcxMap *map); int ucx_map_put(UcxMap *map, UcxKey key, void *data); void* ucx_map_get(UcxMap *map, UcxKey key); @@ -63,6 +78,16 @@ int ucx_map_iter_next(UcxMapIterator *i, void **elm); +/* use macros for string maps only, values are not encoded */ +#define ucx_map_load(map, f, alloc) ucx_map_load_enc(map, f, alloc, NULL, NULL) +#define ucx_map_store(map, f) ucx_map_store_enc(map, f, NULL, NULL) + +int ucx_map_load_enc(UcxMap *map, FILE *f, UcxAllocator allocator, + ucx_map_coder decoder, void* decdata); +/* encoders shall provide null terminated strings*/ +int ucx_map_store_enc(UcxMap *map, FILE *f, + ucx_map_coder encoder, void* encdata); + #ifdef __cplusplus } #endif diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/mempool.h --- a/src/server/ucx/mempool.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/mempool.h Sat Oct 06 13:00:07 2012 +0200 @@ -5,6 +5,9 @@ #ifndef MPOOL_H #define MPOOL_H +#include +#include "allocator.h" + #ifdef __cplusplus extern "C" { #endif @@ -17,6 +20,11 @@ size_t size; } UcxMempool; +#define UCX_ALLOCATOR_MEMPOOL(pool) {pool, \ + (ucx_allocator_malloc) ucx_mempool_malloc, \ + (ucx_allocator_calloc) ucx_mempool_calloc, \ + (ucx_allocator_realloc) ucx_mempool_realloc} + #define ucx_mempool_new_default() ucx_mempool_new(16) UcxMempool *ucx_mempool_new(size_t n); int ucx_mempool_chcap(UcxMempool *pool, size_t newcap); diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/objs.mk --- a/src/server/ucx/objs.mk Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/objs.mk Sat Oct 06 13:00:07 2012 +0200 @@ -36,6 +36,7 @@ UCXOBJ += mempool.o UCXOBJ += string.o UCXOBJ += atomic.o +UCXOBJ += allocator.o UCXOBJS = $(UCXOBJ:%=$(UCX_OBJPRE)%) UCXSOURCE = $(UCXOBJ:%.o=ucx/%.c) diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/string.c --- a/src/server/ucx/string.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/string.c Sat Oct 06 13:00:07 2012 +0200 @@ -6,11 +6,10 @@ */ #include -#include +#include #include #include "string.h" -#include "mempool.h" sstr_t sstr (char *s) { sstr_t string; @@ -35,6 +34,7 @@ sstr_t str = va_arg(ap, sstr_t); size += str.length; } + va_end(ap); return size; } @@ -49,6 +49,7 @@ s.ptr = strncat (s.ptr, str.ptr, s.length); str = va_arg (ap, sstr_t); } + va_end(ap); return s; } @@ -57,12 +58,26 @@ va_list ap; va_start(ap, c1); s.ptr[0] = 0; - - s.ptr = strncat (s.ptr, c1.ptr, s.length); + + size_t len = s.length; + size_t cplen = c1.length > len ? len : c1.length; + char *ptr = s.ptr; + + memcpy(ptr, c1.ptr, cplen); + len -= cplen; + ptr += cplen; for (int i=0;i len ? len : str.length; + if(cplen <= 0) { + va_end(ap); + return s; + } + memcpy(ptr, str.ptr, cplen); + len -= cplen; + ptr += cplen; } + va_end(ap); return s; } @@ -84,29 +99,76 @@ return new_sstr; } -int sstrcmp(sstr_t s1, sstr_t s2) { - return memcmp(s1.ptr, s2.ptr, s1.length>s2.length ? s2.length: s1.length); +sstr_t* sstrsplit(sstr_t s, sstr_t d, size_t *n) { + if (d.length == 0) { + return NULL; + } + + sstr_t* result; + size_t nmax = *n; + *n = 1; + + /* special case: exact match - no processing needed */ + if (s.length == d.length && strncmp(s.ptr, d.ptr, s.length) == 0) { + result = malloc(sizeof(sstr_t)); + result[0] = sstrn("", 0); + return result; + } + sstr_t sv = sstrdup(s); + + for (int i = 0 ; i < s.length ; i++) { + if (sv.ptr[i] == d.ptr[0]) { + _Bool match = 1; + for (int j = 1 ; j < d.length ; j++) { + if (j+i < s.length) { + match &= (sv.ptr[i+j] == d.ptr[j]); + } else { + match = 0; + break; + } + } + if (match) { + (*n)++; + for (int j = 0 ; j < d.length ; j++) { + sv.ptr[i+j] = 0; + } + i += d.length; + } + } + if ((*n) == nmax) break; + } + result = malloc(sizeof(sstr_t) * (*n)); + + char *pptr = sv.ptr; + for (int i = 0 ; i < *n ; i++) { + size_t l = strlen(pptr); + char* ptr = malloc(l + 1); + memcpy(ptr, pptr, l); + ptr[l] = 0; + + result[i] = sstrn(ptr, l); + pptr += l + d.length; + } + + free(sv.ptr); + + return result; } -sstr_t sstrdub(sstr_t s) { - sstr_t newstring; - newstring.ptr = malloc(s.length + 1); - newstring.length = s.length; - newstring.ptr[newstring.length] = 0; - - memcpy(newstring.ptr, s.ptr, s.length); - - return newstring; +int sstrcmp(sstr_t s1, sstr_t s2) { + return strncmp(s1.ptr, s2.ptr, s1.length>s2.length ? s2.length: s1.length); } sstr_t sstrdup(sstr_t s) { sstr_t newstring; - newstring.ptr = malloc(s.length + 1); - newstring.length = s.length; - newstring.ptr[newstring.length] = 0; + newstring.ptr = (char*) malloc(s.length + 1); + if (newstring.ptr != NULL) { + newstring.length = s.length; + newstring.ptr[newstring.length] = 0; - memcpy(newstring.ptr, s.ptr, s.length); - + memcpy(newstring.ptr, s.ptr, s.length); + } + return newstring; } @@ -135,15 +197,28 @@ return newstr; } -sstr_t sstrdup_mp(UcxMempool *mp, sstr_t s) { +sstr_t sstrdup_mp(UcxMempool *pool, sstr_t s) { sstr_t newstring; - newstring.ptr = ucx_mempool_malloc(mp, s.length + 1); - newstring.length = s.length; - newstring.ptr[newstring.length] = 0; + newstring.ptr = (char*)ucx_mempool_malloc(pool, s.length + 1); + if (newstring.ptr != NULL) { + newstring.length = s.length; + newstring.ptr[newstring.length] = 0; - /* TODO: sometimes memcpy and/or memmove destroy the source */ - memcpy(newstring.ptr, s.ptr, s.length); + memcpy(newstring.ptr, s.ptr, s.length); + } return newstring; } +sstr_t sstrdup_pool(pool_handle_t *pool, sstr_t s) { + sstr_t newstring; + newstring.ptr = (char*)pool_malloc(pool, s.length + 1); + if (newstring.ptr != NULL) { + newstring.length = s.length; + newstring.ptr[newstring.length] = 0; + + memcpy(newstring.ptr, s.ptr, s.length); + } + + return newstring; +} diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/string.h --- a/src/server/ucx/string.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/string.h Sat Oct 06 13:00:07 2012 +0200 @@ -8,8 +8,12 @@ #ifndef _SSTRING_H #define _SSTRING_H +#include + #include "mempool.h" +#include "../util/pool.h" +/* use macros for literals only */ #define S(s) { s, sizeof(s)-1 } #define ST(s) sstrn(s, sizeof(s)-1) @@ -66,16 +70,34 @@ /* * */ -sstr_t sstrsubsl (sstr_t s, size_t start, size_t end); +sstr_t sstrsubsl (sstr_t s, size_t start, size_t length); +/* + * splits s into n parts + * + * s the string to split + * d the delimiter string + * n the maximum size of the resulting list + * a size of 0 indicates an unbounded list size + * the actual size of the list will be stored here + * + * Hint: use this value to avoid dynamic reallocation of the result list + * + * Returns a list of the split strings + * NOTE: this list needs to be freed manually after usage + * + * Returns NULL on error + */ +sstr_t* sstrsplit(sstr_t s, sstr_t d, size_t *n); int sstrcmp(sstr_t s1, sstr_t s2); -sstr_t sstrdub(sstr_t s); sstr_t sstrdup(sstr_t s); +// webserver extension sstr_t sstrtrim(sstr_t string); -sstr_t sstrdup_mp(UcxMempool *mp, sstr_t s); +sstr_t sstrdup_mp(UcxMempool *pool, sstr_t s); +sstr_t sstrdup_pool(pool_handle_t *pool, sstr_t s); #ifdef __cplusplus } diff -r 4417619a9bbd -r 450d2d5f4735 src/server/ucx/ucx.h --- a/src/server/ucx/ucx.h Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/ucx/ucx.h Sat Oct 06 13:00:07 2012 +0200 @@ -17,9 +17,6 @@ #define UCX_FOREACH(type,list,elem) \ for (type elem = list ; elem != NULL ; elem = elem->next) -/* source,data -> errno */ -typedef int(*ucx_callback)(void*,void*); - /* element1,element2,custom data -> {-1,0,1} */ typedef int(*cmp_func)(void*,void*,void*); diff -r 4417619a9bbd -r 450d2d5f4735 src/server/webdav/webdav.c --- a/src/server/webdav/webdav.c Sat Aug 18 11:39:34 2012 +0200 +++ b/src/server/webdav/webdav.c Sat Oct 06 13:00:07 2012 +0200 @@ -417,7 +417,7 @@ } void dav_resource_response(PropfindRequest *davrq, sstr_t path, sstr_t uri) { - printf("dav_resource_response %s %s\n", sstrdub(path).ptr, sstrdub(uri).ptr); + printf("dav_resource_response %s %s\n", sstrdup(path).ptr, sstrdup(uri).ptr); sbuf_puts(davrq->out, "\n"); sbuf_puts(davrq->out, ""); @@ -675,7 +675,8 @@ } UcxMapIterator iter = ucx_map_iterator(propstat->map); - UCX_MAP_FOREACH(UcxDlist*, proplist, propstat->map, iter) { + UcxDlist *proplist; + UCX_MAP_FOREACH(proplist, iter) { if(proplist) { sbuf_puts(out, "\n\n");