]> arthur.barton.de Git - netdata.git/commitdiff
almost operational registry - still needs testing
authorCosta Tsaousis <costa@tsaousis.gr>
Tue, 10 May 2016 01:01:55 +0000 (04:01 +0300)
committerCosta Tsaousis <costa@tsaousis.gr>
Tue, 10 May 2016 01:01:55 +0000 (04:01 +0300)
src/Makefile.am
src/registry.c
src/registry.h [new file with mode: 0644]
src/web_client.c
src/web_client.h

index ef7d20fdde93eb2284173fe66f93bc0bac7c6276..69d1a2fe5a37216d85378a47fe554091d1b05fd2 100644 (file)
@@ -59,6 +59,7 @@ netdata_SOURCES = \
        sys_kernel_mm_ksm.c \
        sys_fs_cgroup.c \
        procfile.c procfile.h \
+       registry.c registry.h \
        rrd.c rrd.h \
        rrd2json.c rrd2json.h \
        storage_number.c storage_number.h \
index 0944585c62114b9eee7f05a8d1610a222df9ebd4..6ab9e6a57df5653b0e4603c204b50f4932a1dd7c 100644 (file)
@@ -19,6 +19,9 @@
 #include "dictionary.h"
 #include "appconfig.h"
 
+#include "web_client.h"
+#include "registry.h"
+
 #define REGISTRY_URL_FLAGS_DEFAULT 0x00
 #define REGISTRY_URL_FLAGS_EXPIRED 0x01
 
@@ -28,6 +31,9 @@
 // COMMON structures
 
 struct registry {
+       int enabled;
+
+       // counters / statistics
        unsigned long long persons_count;
        unsigned long long machines_count;
        unsigned long long usages_count;
@@ -35,26 +41,38 @@ struct registry {
        unsigned long long persons_urls_count;
        unsigned long long machines_urls_count;
 
+       // files
        char *pathname;
        char *db_filename;
        char *log_filename;
        FILE *registry_log_fp;
 
+       // the database
        DICTIONARY *persons;    // dictionary of PERSON *, with key the PERSON.guid
        DICTIONARY *machines;   // dictionary of MACHINE *, with key the MACHINE.guid
        DICTIONARY *urls;               // dictionary of URL *, with key the URL.url
 
+       // concurrency locking
+       // we keep different locks for different things
+       // so that many tasks can be completed in parallel
+       pthread_mutex_t persons_lock;
+       pthread_mutex_t machines_lock;
+       pthread_mutex_t urls_lock;
+       pthread_mutex_t person_urls_lock;
+       pthread_mutex_t machine_urls_lock;
+       pthread_mutex_t log_lock;
 } registry;
 
-
 // ----------------------------------------------------------------------------
 // URL structures
 // Save memory by de-duplicating URLs
+// so instead of storing URLs all over the place
+// we store them here and we keep pointers elsewhere
 
 struct url {
-       uint32_t links;
-       uint16_t len;
-       char url[1];
+       uint32_t links; // the number of links to this URL - when none is left, we free it
+       uint16_t len;   // the length of the URL in bytes
+       char url[1];    // the URL - dynamically allocated to more size
 };
 typedef struct url URL;
 
@@ -76,7 +94,7 @@ typedef struct machine_url MACHINE_URL;
 
 // A machine
 struct machine {
-       char guid[36 + 1];
+       char guid[36 + 1];                      // the GUID
 
        DICTIONARY *urls;                       // MACHINE_URL *
 
@@ -92,21 +110,24 @@ typedef struct machine MACHINE;
 
 // for each PERSON-URL pair we keep this
 struct person_url {
-       URL *url;                               // de-duplicated URL
-       MACHINE *machine;
+       URL *url;                                       // de-duplicated URL
+       MACHINE *machine;                       // link the MACHINE of this URL
 
        uint8_t flags;
        uint32_t first_t;                       // the first time we saw this
        uint32_t last_t;                        // the last time we saw this
        uint32_t usages;                        // how many times this has been accessed
+
+       char name[1];                           // the name of the URL, as known by the user
+                                                               // dynamically allocated to fit properly
 };
 typedef struct person_url PERSON_URL;
 
 // A person
 struct person {
-       char guid[36 + 1];
+       char guid[36 + 1];                      // the person GUID
 
-       DICTIONARY *urls; // PERSON_URL *
+       DICTIONARY *urls;                       // dictionary of PERSON_URL *
 
        uint32_t first_t;                       // the first time we saw this
        uint32_t last_t;                        // the last time we saw this
@@ -114,54 +135,191 @@ struct person {
 };
 typedef struct person PERSON;
 
-extern PERSON *registry_request(const char *person_guid, const char *machine_guid, const char *url, time_t when);
+
+// ----------------------------------------------------------------------------
+// REGISTRY concurrency locking
+
+static inline void registry_persons_lock(void) {
+       pthread_mutex_lock(&registry.persons_lock);
+}
+
+static inline void registry_persons_unlock(void) {
+       pthread_mutex_unlock(&registry.persons_lock);
+}
+
+static inline void registry_machines_lock(void) {
+       pthread_mutex_lock(&registry.machines_lock);
+}
+
+static inline void registry_machines_unlock(void) {
+       pthread_mutex_unlock(&registry.machines_lock);
+}
+
+static inline void registry_urls_lock(void) {
+       pthread_mutex_lock(&registry.urls_lock);
+}
+
+static inline void registry_urls_unlock(void) {
+       pthread_mutex_unlock(&registry.urls_lock);
+}
+
+// ideally, we should not lock the whole registry for
+// updating a person's urls.
+// however, to save the memory required for keeping a
+// mutex (40 bytes) per person, we do...
+static inline void registry_person_urls_lock(PERSON *p) {
+       (void)p;
+       pthread_mutex_lock(&registry.person_urls_lock);
+}
+
+static inline void registry_person_urls_unlock(PERSON *p) {
+       (void)p;
+       pthread_mutex_unlock(&registry.person_urls_lock);
+}
+
+// ideally, we should not lock the whole registry for
+// updating a machine's urls.
+// however, to save the memory required for keeping a
+// mutex (40 bytes) per machine, we do...
+static inline void registry_machine_urls_lock(MACHINE *m) {
+       (void)m;
+       pthread_mutex_lock(&registry.machine_urls_lock);
+}
+
+static inline void registry_machine_urls_unlock(MACHINE *m) {
+       (void)m;
+       pthread_mutex_unlock(&registry.machine_urls_lock);
+}
+
+static inline void registry_log_lock(void) {
+       pthread_mutex_lock(&registry.log_lock);
+}
+
+static inline void registry_log_unlock(void) {
+       pthread_mutex_unlock(&registry.log_lock);
+}
+
+
+// ----------------------------------------------------------------------------
+// common functions
+
+// parse a GUID and re-generated to be always lower case
+// this is used as a protection against the variations of GUIDs
+static inline int registry_regenerate_guid(const char *guid, char *result) {
+       uuid_t uuid;
+       if(unlikely(uuid_parse(guid, uuid) == -1)) {
+               info("Registry: GUID '%s' is not a valid GUID.", guid);
+               return -1;
+       }
+       else {
+               uuid_unparse_lower(uuid, result);
+
+#ifdef NETDATA_INTERNAL_CHECKS
+               if(strcmp(guid, result))
+                       info("Registry: source GUID '%s' and re-generated GUID '%s' differ!", guid, result);
+#endif /* NETDATA_INTERNAL_CHECKS */
+       }
+
+       return 0;
+}
+
+// make sure the names of the machines / URLs do not contain any tabs
+// (which are used as our separator in the database files)
+// and are properly trimmed (before and after)
+static inline char *registry_fix_machine_name(char *name, size_t *len) {
+       char *s = name?name:"";
+
+       // skip leading spaces
+       while(*s && isspace(*s)) s++;
+
+       // make sure all spaces are a SPACE
+       char *t = s;
+       while(*t) {
+               if(unlikely(isspace(*t)))
+                       *t = ' ';
+
+               t++;
+       }
+
+       // remove trailing spaces
+       while(--t >= s) {
+               if(*t == ' ')
+                       *t = '\0';
+               else
+                       break;
+       }
+       t++;
+
+       if(likely(len))
+               *len = (t - s);
+
+       return s;
+}
+
+static inline char *registry_fix_url(char *url, size_t *len) {
+       return registry_fix_machine_name(url, len);
+}
+
+
+// ----------------------------------------------------------------------------
+// forward definition of functions
+
+extern PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when);
+extern PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when);
+
 
 // ----------------------------------------------------------------------------
 // URL
 
-static inline URL *registry_url_allocate(const char *url) {
-       size_t len = strlen(url);
+static inline URL *registry_url_allocate_nolock(const char *url, size_t urllen) {
+       debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): allocating %zu bytes", url, sizeof(URL) + urllen);
+       URL *u = malloc(sizeof(URL) + urllen);
+       if(!u) fatal("Cannot allocate %zu bytes for URL '%s'", sizeof(URL) + urllen);
 
-       debug(D_REGISTRY, "Registry: registry_url_allocate('%s'): allocating %zu bytes", url, sizeof(URL) + len);
-       URL *u = malloc(sizeof(URL) + len);
-       if(!u) fatal("Cannot allocate %zu bytes for URL '%s'", sizeof(URL) + len);
+       // a simple strcpy() should do the job
+       // but I prefer to be safe, since the caller specified urllen
+       strncpy(u->url, url, urllen);
+       u->url[urllen] = '\0';
 
-       strcpy(u->url, url);
-       u->len = len;
+       u->len = urllen;
        u->links = 0;
 
-       debug(D_REGISTRY, "Registry: registry_url_allocate('%s'): indexing it", url);
+       debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): indexing it", url);
        dictionary_set(registry.urls, u->url, u, sizeof(URL));
 
        return u;
 }
 
-static inline URL *registry_url_get(const char *url) {
+static inline URL *registry_url_get(const char *url, size_t urllen) {
        debug(D_REGISTRY, "Registry: registry_url_get('%s')", url);
 
+       registry_urls_lock();
+
        URL *u = dictionary_get(registry.urls, url);
        if(!u) {
-               u = registry_url_allocate(url);
+               u = registry_url_allocate_nolock(url, urllen);
                registry.urls_count++;
        }
 
+       registry_urls_unlock();
+
        return u;
 }
 
-static inline void registry_url_link(URL *u) {
+static inline void registry_url_link_nolock(URL *u) {
        u->links++;
-       debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): This URL has now %u links", u->url, u->links);
+       debug(D_REGISTRY, "Registry: registry_url_link_nolock('%s'): URL has now %u links", u->url, u->links);
 }
 
-static inline void registry_url_unlink(URL *u) {
+static inline void registry_url_unlink_nolock(URL *u) {
        u->links--;
        if(!u->links) {
-               debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): No more links for this URL", u->url);
+               debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): No more links for this URL", u->url);
                dictionary_del(registry.urls, u->url);
                free(u);
        }
        else
-               debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): This URL has %u links left", u->url, u->links);
+               debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): URL has %u links left", u->url, u->links);
 }
 
 
@@ -189,7 +347,7 @@ static inline MACHINE_URL *registry_machine_url_allocate(MACHINE *m, URL *u, tim
 
        debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): indexing URL in machine", m->guid, u->url);
        dictionary_set(m->urls, u->url, mu, sizeof(MACHINE_URL));
-       registry_url_link(u);
+       registry_url_link_nolock(u);
 
        return mu;
 }
@@ -219,18 +377,14 @@ static inline MACHINE *registry_machine_allocate(const char *machine_guid, time_
 static inline MACHINE *registry_machine_get(const char *machine_guid, time_t when) {
        MACHINE *m = NULL;
 
+       registry_machines_lock();
+
        if(likely(machine_guid && *machine_guid)) {
                // validate it is a GUID
-               uuid_t uuid;
-               if(uuid_parse(machine_guid, uuid) == -1) {
+               char buf[36 + 1];
+               if(unlikely(registry_regenerate_guid(machine_guid, buf) == -1))
                        info("Registry: machine guid '%s' is not a valid guid. Ignoring it.", machine_guid);
-               }
                else {
-                       char buf[36 + 1];
-                       uuid_unparse_lower(uuid, buf);
-                       if(strcmp(machine_guid, buf))
-                               info("Registry: machine guid '%s' and re-generated '%s' differ!", machine_guid, buf);
-
                        machine_guid = buf;
                        m = registry_machine_find(machine_guid);
                        if(!m) {
@@ -240,6 +394,8 @@ static inline MACHINE *registry_machine_get(const char *machine_guid, time_t whe
                }
        }
 
+       registry_machines_unlock();
+
        return m;
 }
 
@@ -252,10 +408,15 @@ static inline PERSON *registry_person_find(const char *person_guid) {
        return dictionary_get(registry.persons, person_guid);
 }
 
-static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, URL *u, time_t when) {
-       debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url, sizeof(PERSON_URL));
-       PERSON_URL *pu = malloc(sizeof(PERSON_URL));
-       if(!pu) fatal("registry_person_link_to_url('%s', '%s', '%s'): cannot allocate %zu bytes.", p->guid, m->guid, u->url, sizeof(PERSON_URL));
+static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) {
+       debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url, sizeof(PERSON_URL) + namelen);
+       PERSON_URL *pu = malloc(sizeof(PERSON_URL) + namelen);
+       if(!pu) fatal("registry_person_link_to_url('%s', '%s', '%s'): cannot allocate %zu bytes.", p->guid, m->guid, u->url, sizeof(PERSON_URL) + namelen);
+
+       // a simple strcpy() should do the job
+       // but I prefer to be safe, since the caller specified urllen
+       strncpy(pu->name, name, namelen);
+       pu->name[namelen] = '\0';
 
        pu->machine = m;
        pu->first_t = pu->last_t = when;
@@ -265,7 +426,7 @@ static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, UR
 
        debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): indexing URL in person", p->guid, m->guid, u->url);
        dictionary_set(p->urls, u->url, pu, sizeof(PERSON_URL));
-       registry_url_link(u);
+       registry_url_link_nolock(u);
 
        return pu;
 }
@@ -318,18 +479,14 @@ static inline PERSON *registry_person_allocate(const char *person_guid, time_t w
 static inline PERSON *registry_person_get(const char *person_guid, time_t when) {
        PERSON *p = NULL;
 
+       registry_persons_lock();
+
        if(person_guid && *person_guid) {
+               char buf[36 + 1];
                // validate it is a GUID
-               uuid_t uuid;
-               if(uuid_parse(person_guid, uuid) == -1) {
+               if(unlikely(registry_regenerate_guid(person_guid, buf) == -1))
                        info("Registry: person guid '%s' is not a valid guid. Ignoring it.", person_guid);
-               }
                else {
-                       char buf[36 + 1];
-                       uuid_unparse_lower(uuid, buf);
-                       if(strcmp(person_guid, buf))
-                               info("Registry: person guid '%s' and re-generated '%s' differ!", person_guid, buf);
-
                        person_guid = buf;
                        p = registry_person_find(person_guid);
                        if(!p) person_guid = NULL;
@@ -341,19 +498,23 @@ static inline PERSON *registry_person_get(const char *person_guid, time_t when)
                registry.persons_count++;
        }
 
+       registry_persons_unlock();
+
        return p;
 }
 
 // ----------------------------------------------------------------------------
 // LINKING OF OBJECTS
 
-static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) {
+static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) {
        debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): searching for URL in person", p->guid, m->guid, u->url);
 
+       registry_person_urls_lock(p);
+
        PERSON_URL *pu = dictionary_get(p->urls, u->url);
        if(!pu) {
                debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
-               pu = registry_person_url_allocate(p, m, u, when);
+               pu = registry_person_url_allocate(p, m, u, name, namelen, when);
                registry.persons_urls_count++;
        }
        else {
@@ -375,8 +536,12 @@ static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL
        p->usages++;
        p->last_t = when;
 
-       if(pu->flags & REGISTRY_URL_FLAGS_EXPIRED)
-               info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url);
+       if(pu->flags & REGISTRY_URL_FLAGS_EXPIRED) {
+               info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL. Re-enabling URL.", p->guid, m->guid, u->url);
+               pu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED;
+       }
+
+       registry_person_urls_unlock(p);
 
        return pu;
 }
@@ -384,6 +549,8 @@ static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL
 static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) {
        debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): searching for URL in machine", p->guid, m->guid, u->url);
 
+       registry_machine_urls_lock(m);
+
        MACHINE_URL *mu = dictionary_get(m->urls, u->url);
        if(!mu) {
                debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
@@ -401,8 +568,12 @@ static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, U
        m->usages++;
        m->last_t = when;
 
-       if(mu->flags & REGISTRY_URL_FLAGS_EXPIRED)
-               info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url);
+       if(mu->flags & REGISTRY_URL_FLAGS_EXPIRED) {
+               info("registry_machine_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url);
+               mu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED;
+       }
+
+       registry_machine_urls_unlock(m);
 
        return mu;
 }
@@ -410,18 +581,26 @@ static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, U
 // ----------------------------------------------------------------------------
 // REGISTRY LOG LOAD/SAVE
 
-static inline void registry_log(const char action, PERSON *p, MACHINE *m, URL *u) {
-       if(likely(registry.registry_log_fp))
-               fprintf(registry.registry_log_fp, "%c\t%08x\t%s\t%s\t%s\n",
+static inline void registry_log(const char action, PERSON *p, MACHINE *m, URL *u, char *name) {
+       if(likely(registry.registry_log_fp)) {
+               // we lock only if the file is open
+               // to allow replaying the log at registry_log_load()
+               registry_log_lock();
+
+               fprintf(registry.registry_log_fp, "%c\t%08x\t%s\t%s\t%s\t%s\n",
                                action,
                                p->last_t,
                                p->guid,
                                m->guid,
+                               name,
                                u->url
                );
+
+               registry_log_unlock();
+       }
 }
 
-void registry_log_open(void) {
+void registry_log_open_nolock(void) {
        registry.registry_log_fp = fopen(registry.log_filename, "a");
 
        if(registry.registry_log_fp) {
@@ -430,85 +609,342 @@ void registry_log_open(void) {
        }
 }
 
-void registry_log_close(void) {
+void registry_log_close_nolock(void) {
        if(registry.registry_log_fp)
                fclose(registry.registry_log_fp);
 
        registry.registry_log_fp = NULL;
 }
 
-void registry_log_recreate(void) {
+void registry_log_recreate_nolock(void) {
        if(registry.registry_log_fp != NULL) {
-               fclose(registry.registry_log_fp);
+               registry_log_close_nolock();
 
                registry.registry_log_fp = fopen(registry.log_filename, "w");
                if(registry.registry_log_fp) fclose(registry.registry_log_fp);
                registry.registry_log_fp = NULL;
 
-               registry_log_open();
+               registry_log_open_nolock();
        }
+
 }
 
 int registry_log_load(void) {
        char *s, buf[4096 + 1];
-       size_t line = 0;
+       size_t line = -1;
 
-       registry_log_close();
+       // closing the log is required here
+       // otherwise we will append to it the values we read
+       registry_log_close_nolock();
 
        debug(D_REGISTRY, "Registry: loading active db from: %s", registry.log_filename);
        FILE *fp = fopen(registry.log_filename, "r");
-       if(!fp) {
+       if(!fp)
                error("Registry: cannot open registry file: %s", registry.log_filename);
-               return 0;
-       }
-
-       size_t len = 0;
-       while((s = fgets_trim_len(buf, 4096, fp, &len))) {
-               line++;
-
-               switch(s[0]) {
-                       case 'A':
-                               // verify it is valid
-                               if(unlikely(len < 85 || s[1] != '\t' || s[10] != '\t' || s[47] != '\t' || s[84] != '\t'))
-                                       error("Registry log line %u is wrong (len = %zu).", line, len);
+       else {
+               line = 0;
+               size_t len = 0;
+               while ((s = fgets_trim_len(buf, 4096, fp, &len))) {
+                       line++;
+
+                       switch (s[0]) {
+                               case 'A': // accesses
+                               case 'D': // deletes
+
+                                       // verify it is valid
+                                       if (unlikely(len < 85 || s[1] != '\t' || s[10] != '\t' || s[47] != '\t' || s[84] != '\t')) {
+                                               error("Registry: log line %u is wrong (len = %zu).", line, len);
+                                               continue;
+                                       }
+
+                                       s[1] = s[10] = s[47] = s[84] = '\0';
+
+                                       // skip the name to find the url
+                                       char *url = &s[85];
+                                       while(*url && *url != '\t') url++;
+                                       if(!*url) {
+                                               error("Registry: log line %u does not have a url.", line);
+                                               continue;
+                                       }
+                                       *url++ = '\0';
+
+                                       if(s[0] == 'A')
+                                               registry_request_access(&s[11], &s[48], url, &s[85], strtoul(&s[2], NULL, 16));
+                                       else
+                                               registry_request_delete(&s[11], &s[48], url, &s[85], strtoul(&s[2], NULL, 16));
 
-                               s[1] = s[10] = s[47] = s[84] = '\0';
-                               registry_request(&s[11], &s[48], &s[85], strtoul(&s[2], NULL, 16));
-                               break;
+                                       break;
 
-                       default:
-                               error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s);
-                               break;
+                               default:
+                                       error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s);
+                                       break;
+                       }
                }
        }
 
-       registry_log_open();
+       // open the log again
+       registry_log_open_nolock();
 
-       return 0;
+       return line;
 }
 
 
 // ----------------------------------------------------------------------------
 // REGISTRY REQUESTS
 
-PERSON *registry_request(const char *person_guid, const char *machine_guid, const char *url, time_t when) {
-       debug(D_REGISTRY, "registry_request('%s', '%s', '%s'): NEW REQUEST", (person_guid)?person_guid:"", machine_guid, url);
+PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
+       debug(D_REGISTRY, "registry_request_access('%s', '%s', '%s'): NEW REQUEST", (person_guid)?person_guid:"", machine_guid, url);
 
        MACHINE *m = registry_machine_get(machine_guid, when);
        if(!m) return NULL;
 
-       URL *u = registry_url_get(url);
+       // make sure the name is valid
+       size_t namelen;
+       name = registry_fix_machine_name(name, &namelen);
+
+       size_t urllen;
+       url = registry_fix_url(url, &urllen);
+
+       URL *u = registry_url_get(url, urllen);
        PERSON *p = registry_person_get(person_guid, when);
 
-       registry_person_link_to_url(p, m, u, when);
+       registry_person_link_to_url(p, m, u, name, namelen, when);
        registry_machine_link_to_url(p, m, u, when);
 
-       registry_log('A', p, m, u);
+       registry_log('A', p, m, u, name);
 
        registry.usages_count++;
        return p;
 }
 
+// verify the person, the machine and the URL exist in our DB
+PERSON_URL *registry_verify_request(char *person_guid, char *machine_guid, char *url, PERSON **pp, MACHINE **mm) {
+       char pbuf[36 + 1], mbuf[36 + 1];
+
+       if(!person_guid || !*person_guid || !machine_guid || !*machine_guid || !url || !*url) {
+               info("Registry Request Verification: invalid request! person: '%s', machine '%s', url '%s'", person_guid?person_guid:"UNSET", machine_guid?machine_guid:"UNSET", url?url:"UNSET");
+               return NULL;
+       }
+
+       // normalize the url
+       url = registry_fix_url(url, NULL);
+
+       // make sure the person GUID is valid
+       if(registry_regenerate_guid(person_guid, pbuf) == -1) {
+               info("Registry Request Verification: invalid person GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+               return NULL;
+       }
+       person_guid = pbuf;
+
+       // make sure the machine GUID is valid
+       if(registry_regenerate_guid(machine_guid, mbuf) == -1) {
+               info("Registry Request Verification: invalid machine GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+               return NULL;
+       }
+       machine_guid = mbuf;
+
+       // make sure the machine exists
+       MACHINE *m = registry_machine_find(machine_guid);
+       if(!m) {
+               info("Registry Request Verification: machine not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+               return NULL;
+       }
+       if(mm) *mm = m;
+
+       // make sure the person exist
+       PERSON *p = registry_person_find(person_guid);
+       if(!p) {
+               info("Registry Request Verification: person not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+               return NULL;
+       }
+       if(pp) *pp = p;
+
+       PERSON_URL *pu = dictionary_get(p->urls, url);
+       if(!pu) {
+               info("Registry Request Verification: URL not found for person, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+               return NULL;
+       }
+       return pu;
+}
+
+PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
+       (void)when;
+
+       PERSON *p = NULL;
+       MACHINE *m = NULL;
+       PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m);
+       if(!pu || !p || !m) return NULL;
+
+       // normalize the url
+       delete_url = registry_fix_url(delete_url, NULL);
+
+       // make sure the user is not deleting the url it uses
+       if(!strcmp(delete_url, pu->url->url)) {
+               info("Registry Delete Request: delete URL is the one currently accessing, person: '%s', machine '%s', url '%s', delete url '%s'", p->guid, m->guid, pu->url->url, delete_url);
+               return NULL;
+       }
+
+       registry_person_urls_lock(p);
+
+       PERSON_URL *dpu = dictionary_get(p->urls, delete_url);
+       if(!dpu) {
+               info("Registry Delete Request: URL not found for person, person: '%s', machine '%s', url '%s', delete url '%s'", p->guid, m->guid, pu->url->url, delete_url);
+               registry_person_urls_unlock(p);
+               return NULL;
+       }
+
+       registry_log('D', p, m, pu->url, dpu->url->url);
+
+       dictionary_del(p->urls, dpu->url->url);
+       registry_url_unlink_nolock(dpu->url);
+       free(dpu);
+
+       registry_person_urls_unlock(p);
+       return p;
+}
+
+MACHINE *registry_request_machine(char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
+       (void)when;
+
+       char mbuf[36 + 1];
+
+       PERSON *p = NULL;
+       MACHINE *m = NULL;
+       PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m);
+       if(!pu || !p || !m) return NULL;
+
+       // make sure the machine GUID is valid
+       if(registry_regenerate_guid(request_machine, mbuf) == -1) {
+               info("Registry Machine URLs request: invalid machine GUID, person: '%s', machine '%s', url '%s', request machine '%s'", p->guid, m->guid, pu->url->url, request_machine);
+               return NULL;
+       }
+       request_machine = mbuf;
+
+       // make sure the machine exists
+       m = registry_machine_find(request_machine);
+       if(!m) {
+               info("Registry Machine URLs request: machine not found, person: '%s', machine '%s', url '%s', request machine '%s'", p->guid, m->guid, pu->url->url, request_machine);
+               return NULL;
+       }
+
+       // Verify the user has in the past accessed this machine
+       // We will walk through the PERSON_URLs to find the machine
+       // linking to our machine
+
+       // C magic !
+
+       // a structure to pass to the dictionary_get_all() callback handler
+       struct machine_request_callback_data {
+               MACHINE *find_this_machine;
+               PERSON_URL *result;
+       } rdata = { m, NULL };
+
+       // the callback function
+       // this will be run for every PERSON_URL of this PERSON
+       int machine_request_callback(void *entry, void *data) {
+               PERSON_URL *mypu = (PERSON_URL *)entry;
+               struct machine_request_callback_data *myrdata = (struct machine_request_callback_data *)data;
+
+               if(mypu->machine == myrdata->find_this_machine) {
+                       myrdata->result = mypu;
+                       return -1; // this will also stop the walk through
+               }
+
+               return 0; // continue
+       }
+
+       // request a walk through on the dictionary
+       // no need for locking here, the underlying dictionary has its own
+       dictionary_get_all(p->urls, machine_request_callback, &rdata);
+
+       if(rdata.result)
+               return m;
+
+       return NULL;
+}
+
+
+// ----------------------------------------------------------------------------
+// REGISTRY JSON generation
+
+#ifndef REGISTRY_STANDALONE_TESTS
+
+static inline void registry_json_header(struct web_client *w, int status) {
+       buffer_flush(w->response.data);
+       buffer_sprintf(w->response.data, "{\n\"success\": %s", status?"true":"false");
+}
+
+static inline void registry_json_footer(struct web_client *w) {
+       buffer_strcat(w->response.data, "\n}\n");
+}
+
+static inline int registry_json_redirect(struct web_client *w) {
+       registry_json_header(w, 0);
+       buffer_sprintf(w->response.data, "\"registries\": \"%s\"", config_get("global", "users registries", "reg1.mynetdata.io reg2.mynetdata.io reg3.mynetdata.io"));
+       registry_json_footer(w);
+       return 200;
+}
+
+int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
+       if(!registry.enabled)
+               return registry_json_redirect(w);
+
+       PERSON *p = registry_request_access(person_guid, machine_guid, url, name, when);
+       if(!p) {
+               registry_json_header(w, 0);
+               registry_json_footer(w);
+               return 400;
+       }
+
+       // FIXME -- make this a permanent cookie
+       strncpy(w->cookie, p->guid, COOKIE_MAX);
+       w->cookie[COOKIE_MAX] = '\0';
+
+       registry_json_header(w, 1);
+
+       // FIXME - print an array of all URLs / Machines / Names
+
+       buffer_strcat(w->response.data, "\n}\n");
+       return 200;
+}
+
+int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
+       if(!registry.enabled)
+               return registry_json_redirect(w);
+
+       PERSON *p = registry_request_delete(person_guid, machine_guid, url, delete_url, when);
+       if(!p) {
+               registry_json_header(w, 0);
+               registry_json_footer(w);
+               return 400;
+       }
+
+       registry_json_header(w, 1);
+       registry_json_footer(w);
+       return 200;
+}
+
+int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
+       if(!registry.enabled)
+               return registry_json_redirect(w);
+
+       MACHINE *m = registry_request_machine(person_guid, machine_guid, url, request_machine, when);
+       if(!m) {
+               registry_json_header(w, 0);
+               registry_json_footer(w);
+               return 400;
+       }
+
+       registry_json_header(w, 1);
+
+       // FIXME - print an array of all URLs the machine is accessible
+
+       registry_json_footer(w);
+       return 200;
+}
+
+#endif /* REGISTRY_STANDALONE_TESTS */
+
 
 // ----------------------------------------------------------------------------
 // REGISTRY LOAD/SAVE
@@ -527,6 +963,8 @@ int registry_machine_save_url(void *entry, void *file) {
                        mu->url->url
        );
 
+       // error handling is done at registry_save()
+
        return ret;
 }
 
@@ -549,6 +987,8 @@ int registry_machine_save(void *entry, void *file) {
                ret += ret2;
        }
 
+       // error handling is done at registry_save()
+
        return ret;
 }
 
@@ -558,15 +998,18 @@ static inline int registry_person_save_url(void *entry, void *file) {
 
        debug(D_REGISTRY, "Registry: registry_person_save_url('%s')", pu->url->url);
 
-       int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\n",
+       int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\t%s\n",
                        pu->first_t,
                        pu->last_t,
                        pu->usages,
                        pu->flags,
                        pu->machine->guid,
+                       pu->name,
                        pu->url->url
        );
 
+       // error handling is done at registry_save()
+
        return ret;
 }
 
@@ -589,20 +1032,28 @@ static inline int registry_person_save(void *entry, void *file) {
                ret += ret2;
        }
 
+       // error handling is done at registry_save()
+
        return ret;
 }
 
-static int registry_save(void) {
+int registry_save(void) {
+       if(!registry.enabled) return -1;
+
        char tmp_filename[FILENAME_MAX + 1];
        char old_filename[FILENAME_MAX + 1];
 
        snprintf(old_filename, FILENAME_MAX, "%s.old", registry.db_filename);
        snprintf(tmp_filename, FILENAME_MAX, "%s.tmp", registry.db_filename);
 
+       // make sure the log is not updated
+       registry_log_lock();
+
        debug(D_REGISTRY, "Registry: Creating file '%s'", tmp_filename);
        FILE *fp = fopen(tmp_filename, "w");
        if(!fp) {
                error("Registry: Cannot create file: %s", tmp_filename);
+               registry_log_unlock();
                return -1;
        }
 
@@ -611,6 +1062,7 @@ static int registry_save(void) {
        if(bytes1 < 0) {
                error("Registry: Cannot save registry machines - return value %d", bytes1);
                fclose(fp);
+               registry_log_unlock();
                return bytes1;
        }
        debug(D_REGISTRY, "Registry: saving machines took %d bytes", bytes1);
@@ -620,10 +1072,21 @@ static int registry_save(void) {
        if(bytes2 < 0) {
                error("Registry: Cannot save registry persons - return value %d", bytes2);
                fclose(fp);
+               registry_log_unlock();
                return bytes2;
        }
        debug(D_REGISTRY, "Registry: saving persons took %d bytes", bytes2);
 
+       // save the totals
+       fprintf(fp, "T\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\n",
+                       registry.persons_count,
+                       registry.machines_count,
+                       registry.usages_count,
+                       registry.urls_count,
+                       registry.persons_urls_count,
+                       registry.machines_urls_count
+       );
+
        fclose(fp);
 
        errno = 0;
@@ -662,10 +1125,13 @@ static int registry_save(void) {
 
                        // it has been moved successfully
                        // discard the current registry log
-                       registry_log_recreate();
+                       registry_log_recreate_nolock();
                }
        }
 
+       // continue operations
+       registry_log_unlock();
+
        return -1;
 }
 
@@ -688,13 +1154,27 @@ static inline size_t registry_load(void) {
                line++;
 
                debug(D_REGISTRY, "Registry: read line %zu to length %zu: %s", line, len, s);
-
                switch(*s) {
+                       case 'T': // totals
+                               if(unlikely(len != 103 || s[1] != '\t' || s[18] != '\t' || s[35] != '\t' || s[52] != '\t' || s[69] != '\t' || s[86] != '\t' || s[103] != '\0')) {
+                                       error("Registry totals line %u is wrong (len = %zu).", line, len);
+                                       continue;
+                               }
+                               registry.persons_count = strtoull(&s[2], NULL, 16);
+                               registry.machines_count = strtoull(&s[19], NULL, 16);
+                               registry.usages_count = strtoull(&s[36], NULL, 16);
+                               registry.urls_count = strtoull(&s[53], NULL, 16);
+                               registry.persons_urls_count = strtoull(&s[70], NULL, 16);
+                               registry.machines_urls_count = strtoull(&s[87], NULL, 16);
+                               break;
+
                        case 'P': // person
                                m = NULL;
                                // verify it is valid
-                               if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0'))
+                               if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
                                        error("Registry person line %u is wrong (len = %zu).", line, len);
+                                       continue;
+                               }
 
                                s[1] = s[10] = s[19] = s[28] = '\0';
                                p = registry_person_allocate(&s[29], strtoul(&s[2], NULL, 16));
@@ -706,8 +1186,10 @@ static inline size_t registry_load(void) {
                        case 'M': // machine
                                p = NULL;
                                // verify it is valid
-                               if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0'))
+                               if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
                                        error("Registry person line %u is wrong (len = %zu).", line, len);
+                                       continue;
+                               }
 
                                s[1] = s[10] = s[19] = s[28] = '\0';
                                m = registry_machine_allocate(&s[29], strtoul(&s[2], NULL, 16));
@@ -719,40 +1201,54 @@ static inline size_t registry_load(void) {
                        case 'U': // person URL
                                if(unlikely(!p)) {
                                        error("Registry: ignoring line %zu, no person loaded: %s", line, s);
-                                       break;
+                                       continue;
                                }
 
                                // verify it is valid
-                               if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t')
+                               if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t') {
                                        error("Registry person URL line %u is wrong (len = %zu).", line, len);
+                                       continue;
+                               }
 
                                s[1] = s[10] = s[19] = s[28] = s[31] = s[68] = '\0';
-                               u = registry_url_allocate(&s[69]);
+
+                               // skip the name to find the url
+                               char *url = &s[69];
+                               while(*url && *url != '\t') url++;
+                               if(!*url) {
+                                       error("Registry person URL line %u does not have a url.", line);
+                                       continue;
+                               }
+                               *url++ = '\0';
+
+                               u = registry_url_allocate_nolock(url, strlen(url));
 
                                time_t first_t = strtoul(&s[2], NULL, 16);
 
                                m = registry_machine_find(&s[32]);
                                if(!m) m = registry_machine_allocate(&s[32], first_t);
 
-                               PERSON_URL *pu = registry_person_url_allocate(p, m, u, first_t);
+                               PERSON_URL *pu = registry_person_url_allocate(p, m, u, &s[69], strlen(&s[69]), first_t);
                                pu->last_t = strtoul(&s[11], NULL, 16);
                                pu->usages = strtoul(&s[20], NULL, 16);
                                pu->flags = strtoul(&s[29], NULL, 16);
-                               debug(D_REGISTRY, "Registry loaded person URL '%s', machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, m->guid, pu->first_t, pu->last_t, pu->usages, pu->flags);
+                               debug(D_REGISTRY, "Registry loaded person URL '%s' with name '%s' of machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, pu->name, m->guid, pu->first_t, pu->last_t, pu->usages, pu->flags);
                                break;
 
                        case 'V': // machine URL
                                if(unlikely(!m)) {
                                        error("Registry: ignoring line %zu, no machine loaded: %s", line, s);
-                                       break;
+                                       continue;
                                }
 
                                // verify it is valid
-                               if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t')
+                               if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t') {
                                        error("Registry person URL line %u is wrong (len = %zu).", line, len);
+                                       continue;
+                               }
 
                                s[1] = s[10] = s[19] = s[28] = s[31] = '\0';
-                               u = registry_url_allocate(&s[32]);
+                               u = registry_url_allocate_nolock(&s[32], strlen(&s[32]));
 
                                MACHINE_URL *mu = registry_machine_url_allocate(m, u, strtoul(&s[2], NULL, 16));
                                mu->last_t = strtoul(&s[11], NULL, 16);
@@ -778,7 +1274,6 @@ static inline size_t registry_load(void) {
 
 void registry_init(void) {
        char filename[FILENAME_MAX + 1];
-
        registry.pathname = config_get("registry", "registry db directory", VARLIB_DIR);
        if(mkdir(registry.pathname, 0644) == -1 && errno != EEXIST)
                error("Cannot create directory '%s'", registry.pathname);
@@ -805,11 +1300,24 @@ void registry_init(void) {
        debug(D_REGISTRY, "Registry: creating global registry dictionary for urls.");
        registry.urls = dictionary_create(DICTIONARY_FLAGS);
 
-       registry_log_open();
+       pthread_mutex_init(&registry.persons_lock, NULL);
+       pthread_mutex_init(&registry.machines_lock, NULL);
+       pthread_mutex_init(&registry.urls_lock, NULL);
+       pthread_mutex_init(&registry.person_urls_lock, NULL);
+       pthread_mutex_init(&registry.machine_urls_lock, NULL);
+
+       registry_log_open_nolock();
        registry_load();
+
+       registry.enabled = 1;
 }
 
 void registry_free(void) {
+       if(!registry.enabled) return;
+
+       // we need to destroy the dictionaries ourselves
+       // since the dictionaries use memory we allocated
+
        while(registry.persons->values_index.root) {
                PERSON *p = ((NAME_VALUE *)registry.persons->values_index.root)->value;
 
@@ -824,7 +1332,7 @@ void registry_free(void) {
                        dictionary_del(p->urls, pu->url->url);
 
                        debug(D_REGISTRY, "Registry: unlinking url '%s' from person", pu->url->url);
-                       registry_url_unlink(pu->url);
+                       registry_url_unlink_nolock(pu->url);
 
                        debug(D_REGISTRY, "Registry: freeing person url");
                        free(pu);
@@ -857,7 +1365,7 @@ void registry_free(void) {
                        dictionary_del(m->urls, mu->url->url);
 
                        debug(D_REGISTRY, "Registry: unlinking url '%s' from machine", mu->url->url);
-                       registry_url_unlink(mu->url);
+                       registry_url_unlink_nolock(mu->url);
 
                        debug(D_REGISTRY, "Registry: freeing machine url");
                        free(mu);
@@ -873,6 +1381,8 @@ void registry_free(void) {
                free(m);
        }
 
+       // and free the memory of remaining dictionary structures
+
        debug(D_REGISTRY, "Registry: destroying persons dictionary");
        dictionary_destroy(registry.persons);
 
@@ -884,6 +1394,8 @@ void registry_free(void) {
 }
 
 
+#ifdef REGISTRY_STANDALONE_TESTS
+
 // ----------------------------------------------------------------------------
 // TESTS
 
@@ -933,7 +1445,7 @@ int test1(int argc, char **argv) {
        for(u = 0; u < users ; u++) {
                if(++m == machines) m = 0;
 
-               PERSON *p = registry_request(NULL, machines_guids[m], machines_urls[m], now);
+               PERSON *p = registry_request_access(NULL, machines_guids[m], machines_urls[m], "test", now);
                users_guids[u] = p->guid;
        }
        print_stats(u, start, timems());
@@ -945,7 +1457,7 @@ int test1(int argc, char **argv) {
        for(u = 0; u < users ; u++) {
                if(++m == machines) m = 0;
 
-               PERSON *p = registry_request(users_guids[u], machines_guids[m], machines_urls[m], now);
+               PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
 
                if(p->guid != users_guids[u])
                        fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
@@ -959,7 +1471,7 @@ int test1(int argc, char **argv) {
        for(u = 0; u < users ; u++) {
                if(++m == machines) m = 0;
 
-               PERSON *p = registry_request(users_guids[u], machines_guids[m], machines_urls[m], now);
+               PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
 
                if(p->guid != users_guids[u])
                        fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
@@ -973,7 +1485,7 @@ int test1(int argc, char **argv) {
                uint32_t tu = random() * users / RAND_MAX;
                uint32_t tm = random() * machines / RAND_MAX;
 
-               PERSON *p = registry_request(users_guids[tu], machines_guids[tm], machines_urls[tm], now);
+               PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
 
                if(p->guid != users_guids[tu])
                        fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
@@ -987,7 +1499,7 @@ int test1(int argc, char **argv) {
                uint32_t tu = random() * users / RAND_MAX;
                uint32_t tm = random() * machines2 / RAND_MAX;
 
-               PERSON *p = registry_request(users_guids[tu], machines_guids[tm], machines_urls[tm], now);
+               PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
 
                if(p->guid != users_guids[tu])
                        fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
@@ -1013,7 +1525,7 @@ int test1(int argc, char **argv) {
                        else if (random() % 1000 == 123)
                                url = machines_urls[random() * machines2 / RAND_MAX];
 
-                       PERSON *p = registry_request(users_guids[tu], machines_guids[tm], url, now);
+                       PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], url, "test", now);
 
                        if (p->guid != users_guids[tu])
                                fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
@@ -1037,9 +1549,9 @@ int test1(int argc, char **argv) {
 // TESTING
 
 int main(int argc, char **argv) {
-       debug_flags = 0xFFFFFFFF;
-       //test1(argc, argv);
-       //exit(0);
+       // debug_flags = 0xFFFFFFFF;
+       // test1(argc, argv);
+       // exit(0);
 
        (void)argc;
        (void)argv;
@@ -1054,39 +1566,39 @@ int main(int argc, char **argv) {
        int i = 2;
 
        fprintf(stderr, "\n\nADDING ENTRY\n");
-       // p1 = registry_request("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", time(NULL));
+       p1 = registry_request_access("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
 
        if(0)
        while(i--) {
 #ifdef REGISTRY_STDOUT_DUMP
                fprintf(stderr, "\n\nADDING ENTRY\n");
 #endif /* REGISTRY_STDOUT_DUMP */
-               p1 = registry_request(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", time(NULL));
+               p1 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
 
 #ifdef REGISTRY_STDOUT_DUMP
                fprintf(stderr, "\n\nADDING ANOTHER URL\n");
 #endif /* REGISTRY_STDOUT_DUMP */
-               p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", time(NULL));
+               p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", "test", time(NULL));
 
 #ifdef REGISTRY_STDOUT_DUMP
                fprintf(stderr, "\n\nADDING ANOTHER URL\n");
 #endif /* REGISTRY_STDOUT_DUMP */
-               p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", time(NULL));
+               p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
 
 #ifdef REGISTRY_STDOUT_DUMP
                fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
 #endif /* REGISTRY_STDOUT_DUMP */
-               p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", time(NULL));
+               p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
 
 #ifdef REGISTRY_STDOUT_DUMP
                fprintf(stderr, "\n\nADDING ANOTHER PERSON\n");
 #endif /* REGISTRY_STDOUT_DUMP */
-               p2 = registry_request(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", time(NULL));
+               p2 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
 
 #ifdef REGISTRY_STDOUT_DUMP
                fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
 #endif /* REGISTRY_STDOUT_DUMP */
-               p2 = registry_request(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", time(NULL));
+               p2 = registry_request_access(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
        }
 
        fprintf(stderr, "\n\nSAVE\n");
@@ -1096,3 +1608,5 @@ int main(int argc, char **argv) {
        registry_free();
        return 0;
 }
+
+#endif /* REGISTRY_STANDALONE_TESTS */
diff --git a/src/registry.h b/src/registry.h
new file mode 100644 (file)
index 0000000..3a35c86
--- /dev/null
@@ -0,0 +1,16 @@
+#include "web_client.h"
+
+#ifndef NETDATA_REGISTRY_H
+#define NETDATA_REGISTRY_H 1
+
+#define NETDATA_REGISTRY_COOKIE_NAME "netdata_registry_id"
+
+extern int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when);
+extern int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when);
+extern int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when);
+
+extern void registry_init(void);
+extern void registry_free(void);
+extern int registry_save(void);
+
+#endif /* NETDATA_REGISTRY_H */
index a98ddbd3cb49b55aeede13eb7494aa09b717f109..77396dfddac057c5f0272fffcca6748869871849 100644 (file)
@@ -26,6 +26,7 @@
 #include "global_statistics.h"
 #include "rrd.h"
 #include "rrd2json.h"
+#include "registry.h"
 
 #include "web_client.h"
 #include "../config.h"
@@ -173,6 +174,7 @@ void web_client_reset(struct web_client *w)
        }
 
        w->last_url[0] = '\0';
+       w->cookie[0] = '\0';
 
        w->mode = WEB_CLIENT_MODE_NORMAL;
 
@@ -644,7 +646,7 @@ int web_client_api_request_v1_data(struct web_client *w, char *url)
                if(!name || !*name) continue;
                if(!value || !*value) continue;
 
-               debug(D_WEB_CLIENT, "%llu: API v1 query param '%s' with value '%s'", w->id, name, value);
+               debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
 
                // name and value are now the parameters
                // they are not null and not empty
@@ -784,19 +786,111 @@ cleanup:
        return ret;
 }
 
+int web_client_api_request_v1_registry(struct web_client *w, char *url)
+{
+       char person_guid[36 + 1] = "";
+
+       debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
+
+       char *cookie = strstr(w->response.data->buffer, " " NETDATA_REGISTRY_COOKIE_NAME "=");
+       if(cookie) {
+               strncpy(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME) + 2], 36);
+               person_guid[36] = '\0';
+       }
+
+       char action = '\0';
+       char *machine_guid = NULL,
+                       *machine_url = NULL,
+                       *url_name = NULL,
+                       *search_machine_guid = NULL,
+                       *delete_url = NULL;
+
+       while(url) {
+               char *value = mystrsep(&url, "?&[]");
+               if (!value || !*value) continue;
+
+               char *name = mystrsep(&value, "=");
+               if (!name || !*name) continue;
+               if (!value || !*value) continue;
+
+               debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
+
+               if(!strcmp(name, "action")) {
+                       if(!strcmp(value, "access")) action = 'A';
+                       else if(!strcmp(value, "delete")) action = 'D';
+                       else if(!strcmp(value, "search")) action = 'S';
+               }
+               else if(!strcmp(name, "machine"))
+                       machine_guid = value;
+
+               else if(!strcmp(name, "url"))
+                       machine_url = value;
+
+               else if(action == 'A') {
+                       if(!strcmp(name, "name"))
+                               url_name = value;
+               }
+               else if(action == 'D') {
+                       if(!strcmp(name, "delete_url"))
+                               delete_url = value;
+               }
+               else if(action == 'S') {
+                       if(!strcmp(name, "for"))
+                               search_machine_guid = value;
+               }
+       }
+
+       if((!action || !machine_guid || !machine_url) || (action == 'A' && !url_name) || (action == 'D' && !delete_url) || (action == 'S' && !search_machine_guid)) {
+               buffer_flush(w->response.data);
+               buffer_sprintf(w->response.data, "Invalid registry request - required parameters missing.");
+               return 400;
+       }
+
+       switch(action) {
+               case 'A':
+                       return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, time(NULL));
+
+               case 'D':
+                       return registry_request_delete_json(w, person_guid, machine_guid, machine_url, delete_url, time(NULL));
+
+               case 'S':
+                       return registry_request_search_json(w, person_guid, machine_guid, machine_url, search_machine_guid, time(NULL));
+       }
+
+       buffer_flush(w->response.data);
+       buffer_sprintf(w->response.data, "Invalid or no registry action.");
+       return 400;
+}
+
 int web_client_api_request_v1(struct web_client *w, char *url)
 {
+       static uint32_t data_hash = 0, chart_hash = 0, charts_hash = 0, registry_hash = 0;
+
+       if(unlikely(data_hash == 0)) {
+               data_hash = simple_hash("data");
+               chart_hash = simple_hash("chart");
+               charts_hash = simple_hash("charts");
+               registry_hash = simple_hash("registry");
+       }
+
        // get the command
        char *tok = mystrsep(&url, "/?&");
        if(tok && *tok) {
                debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
+               uint32_t hash = simple_hash(tok);
 
-               if(strcmp(tok, "data") == 0)
+               if(hash == data_hash && !strcmp(tok, "data"))
                        return web_client_api_request_v1_data(w, url);
-               else if(strcmp(tok, "chart") == 0)
+
+               else if(hash == chart_hash && !strcmp(tok, "chart"))
                        return web_client_api_request_v1_chart(w, url);
-               else if(strcmp(tok, "charts") == 0)
+
+               else if(hash == charts_hash && !strcmp(tok, "charts"))
                        return web_client_api_request_v1_charts(w, url);
+
+               else if(hash == registry_hash && !strcmp(tok, "registry"))
+                       return web_client_api_request_v1_registry(w, url);
+
                else {
                        buffer_flush(w->response.data);
                        buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok);
@@ -1043,6 +1137,32 @@ cleanup:
 }
 */
 
+// get the request buffer, just after the GET or OPTIONS
+// and find the url to be decoded, decode it and return
+// a newly allocated buffer with it
+static inline char *find_url_and_decode_it(char *request) {
+       char *e = request, *url = NULL;
+
+       // find the SPACE + "HTTP/"
+       while(*e) {
+               // find the space
+               while (*e && *e != ' ') e++;
+
+               // is it SPACE + "HTTP/" ?
+               if(*e && !strncmp(e, " HTTP/", 6))
+                       break;
+       }
+
+       if(*e) {
+               // we have the end
+               *e = '\0';
+               url = url_decode(request);
+               *e = ' ';
+       }
+
+       return url;
+}
+
 void web_client_process(struct web_client *w) {
        int code = 500;
        ssize_t bytes;
@@ -1069,33 +1189,24 @@ void web_client_process(struct web_client *w) {
                        enable_gzip = 1;
 #endif // NETDATA_WITH_ZLIB
 
-               int datasource_type = DATASOURCE_DATATABLE_JSONP;
-               //if(strstr(w->response.data->buffer, "X-DataSource-Auth"))
-               //      datasource_type = DATASOURCE_GOOGLE_JSON;
+               w->mode = WEB_CLIENT_MODE_NORMAL;
 
-               char *buf = (char *)buffer_tostring(w->response.data);
-               char *tok = strsep(&buf, " \r\n");
-               char *url = NULL;
+               char *tok = (char *)buffer_tostring(w->response.data);
+               char *url = NULL, *encoded_url = NULL;
                char *pointer_to_free = NULL; // keep url_decode() allocated buffer
 
-               w->mode = WEB_CLIENT_MODE_NORMAL;
-
-               if(buf && strcmp(tok, "GET") == 0) {
-                       tok = strsep(&buf, " \r\n");
-                       pointer_to_free = url = url_decode(tok);
-                       debug(D_WEB_CLIENT, "%llu: Processing HTTP GET on url '%s'.", w->id, url);
-               }
-               else if(buf && strcmp(tok, "OPTIONS") == 0) {
-                       tok = strsep(&buf, " \r\n");
-                       pointer_to_free = url = url_decode(tok);
-                       debug(D_WEB_CLIENT, "%llu: Processing HTTP OPTIONS on url '%s'.", w->id, url);
+               if(!strncmp(tok, "GET ", 4))
+                       encoded_url = &tok[4];
+               else if(!strncmp(tok, "OPTIONS ", 8)) {
+                       encoded_url = &tok[8];
                        w->mode = WEB_CLIENT_MODE_OPTIONS;
                }
-               else if (buf && strcmp(tok, "POST") == 0) {
-                       w->keepalive = 0;
-                       tok = strsep(&buf, " \r\n");
-                       pointer_to_free = url = url_decode(tok);
-                       debug(D_WEB_CLIENT, "%llu: I don't know how to handle POST with form data. Assuming it is a GET on url '%s'.", w->id, url);
+
+               if(encoded_url) {
+                       pointer_to_free = url = find_url_and_decode_it(encoded_url);
+
+                       if(url) debug(D_WEB_CLIENT, "%llu: Processing url '%s'.", w->id, url);
+                       else debug(D_WEB_CLIENT, "%llu: Cannot find a valid URL in '%s'", w->id, encoded_url);
                }
 
                w->last_url[0] = '\0';
@@ -1124,26 +1235,29 @@ void web_client_process(struct web_client *w) {
 
                                if(strcmp(tok, "api") == 0) {
                                        // the client is requesting api access
-                                       datasource_type = DATASOURCE_JSON;
                                        code = web_client_api_request(w, url);
                                }
 #ifdef NETDATA_INTERNAL_CHECKS
                                else if(strcmp(tok, "exit") == 0) {
-                                       netdata_exit = 1;
                                        code = 200;
                                        w->response.data->contenttype = CT_TEXT_PLAIN;
                                        buffer_flush(w->response.data);
-                                       buffer_strcat(w->response.data, "will do");
+
+                                       if(!netdata_exit)
+                                               buffer_strcat(w->response.data, "ok, will do...");
+                                       else
+                                               buffer_strcat(w->response.data, "I am doing it already");
+
+                                       netdata_exit = 1;
                                }
 #endif
                                else if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
                                        // the client is requesting rrd data
-                                       datasource_type = DATASOURCE_JSON;
-                                       code = web_client_data_request(w, url, datasource_type);
+                                       code = web_client_data_request(w, url, DATASOURCE_JSON);
                                }
                                else if(strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource"
                                        // the client is requesting google datasource
-                                       code = web_client_data_request(w, url, datasource_type);
+                                       code = web_client_data_request(w, url, DATASOURCE_DATATABLE_JSONP);
                                }
                                else if(strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph"
                                        // the client is requesting an rrd graph
@@ -1269,7 +1383,7 @@ void web_client_process(struct web_client *w) {
                else {
                        strcpy(w->last_url, "not a valid response");
 
-                       if(buf) debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, buf);
+                       debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer);
 
                        code = 500;
                        buffer_flush(w->response.data);
@@ -1437,10 +1551,17 @@ void web_client_process(struct web_client *w) {
                , date
                );
 
+       if(w->cookie[0]) {
+               buffer_sprintf(w->response.header_output,
+                  "Set-Cookie: %s\r\n",
+                  w->cookie);
+       }
+
        if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
                buffer_strcat(w->response.header_output,
                        "Access-Control-Allow-Methods: GET, OPTIONS\r\n"
-                       "Access-Control-Allow-Headers: accept, x-requested-with\r\n"
+                       "Access-Control-Allow-Credentials: true\r\n"
+                       "Access-Control-Allow-Headers: Accept, X-Requested-With, Content-Type, Cookie\r\n"
                        "Access-Control-Max-Age: 1209600\r\n" // 86400 * 14
                        );
        }
index 3823dbc9120c094ac87e3d77bb4cf6b5a812b1e5..e6540c9ff5c5dc104b642a2ffc3bf4d14c1440a7 100644 (file)
@@ -11,6 +11,7 @@
 #include <netdb.h>
 
 #include "web_buffer.h"
+#include "dictionary.h"
 
 #define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
 extern int web_client_timeout;
@@ -26,6 +27,7 @@ extern int web_enable_gzip;
 #define URL_MAX 8192
 #define ZLIB_CHUNK     16384
 #define HTTP_RESPONSE_HEADER_SIZE 4096
+#define COOKIE_MAX 200
 
 struct response {
        BUFFER *header;                                 // our response header
@@ -58,6 +60,8 @@ struct web_client {
 
        struct timeval tv_in, tv_ready;
 
+       char cookie[COOKIE_MAX+1];
+
        int mode;
        int keepalive;