]> arthur.barton.de Git - netdata.git/blob - src/registry.c
almost operational registry - still needs testing
[netdata.git] / src / registry.c
1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4
5 // gcc -O1 -ggdb -Wall -Wextra -I ../src/ -I ../ -o registry ../src/registry.c ../src/dictionary.o ../src/log.o ../src/avl.o ../src/common.o ../src/appconfig.o ../src/web_buffer.o ../src/storage_number.o  -pthread -luuid -lm -DHAVE_CONFIG_H -DVARLIB_DIR="\"/tmp\""
6
7 #include <uuid/uuid.h>
8 #include <inttypes.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <unistd.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <errno.h>
16
17 #include "log.h"
18 #include "common.h"
19 #include "dictionary.h"
20 #include "appconfig.h"
21
22 #include "web_client.h"
23 #include "registry.h"
24
25 #define REGISTRY_URL_FLAGS_DEFAULT 0x00
26 #define REGISTRY_URL_FLAGS_EXPIRED 0x01
27
28 #define DICTIONARY_FLAGS DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE | DICTIONARY_FLAG_NAME_LINK_DONT_CLONE
29
30 // ----------------------------------------------------------------------------
31 // COMMON structures
32
33 struct registry {
34         int enabled;
35
36         // counters / statistics
37         unsigned long long persons_count;
38         unsigned long long machines_count;
39         unsigned long long usages_count;
40         unsigned long long urls_count;
41         unsigned long long persons_urls_count;
42         unsigned long long machines_urls_count;
43
44         // files
45         char *pathname;
46         char *db_filename;
47         char *log_filename;
48         FILE *registry_log_fp;
49
50         // the database
51         DICTIONARY *persons;    // dictionary of PERSON *, with key the PERSON.guid
52         DICTIONARY *machines;   // dictionary of MACHINE *, with key the MACHINE.guid
53         DICTIONARY *urls;               // dictionary of URL *, with key the URL.url
54
55         // concurrency locking
56         // we keep different locks for different things
57         // so that many tasks can be completed in parallel
58         pthread_mutex_t persons_lock;
59         pthread_mutex_t machines_lock;
60         pthread_mutex_t urls_lock;
61         pthread_mutex_t person_urls_lock;
62         pthread_mutex_t machine_urls_lock;
63         pthread_mutex_t log_lock;
64 } registry;
65
66 // ----------------------------------------------------------------------------
67 // URL structures
68 // Save memory by de-duplicating URLs
69 // so instead of storing URLs all over the place
70 // we store them here and we keep pointers elsewhere
71
72 struct url {
73         uint32_t links; // the number of links to this URL - when none is left, we free it
74         uint16_t len;   // the length of the URL in bytes
75         char url[1];    // the URL - dynamically allocated to more size
76 };
77 typedef struct url URL;
78
79
80 // ----------------------------------------------------------------------------
81 // MACHINE structures
82
83 // For each MACHINE-URL pair we keep this
84 struct machine_url {
85         URL *url;                                       // de-duplicated URL
86 //      DICTIONARY *persons;            // dictionary of PERSON *
87
88         uint8_t flags;
89         uint32_t first_t;                       // the first time we saw this
90         uint32_t last_t;                        // the last time we saw this
91         uint32_t usages;                        // how many times this has been accessed
92 };
93 typedef struct machine_url MACHINE_URL;
94
95 // A machine
96 struct machine {
97         char guid[36 + 1];                      // the GUID
98
99         DICTIONARY *urls;                       // MACHINE_URL *
100
101         uint32_t first_t;                       // the first time we saw this
102         uint32_t last_t;                        // the last time we saw this
103         uint32_t usages;                        // how many times this has been accessed
104 };
105 typedef struct machine MACHINE;
106
107
108 // ----------------------------------------------------------------------------
109 // PERSON structures
110
111 // for each PERSON-URL pair we keep this
112 struct person_url {
113         URL *url;                                       // de-duplicated URL
114         MACHINE *machine;                       // link the MACHINE of this URL
115
116         uint8_t flags;
117         uint32_t first_t;                       // the first time we saw this
118         uint32_t last_t;                        // the last time we saw this
119         uint32_t usages;                        // how many times this has been accessed
120
121         char name[1];                           // the name of the URL, as known by the user
122                                                                 // dynamically allocated to fit properly
123 };
124 typedef struct person_url PERSON_URL;
125
126 // A person
127 struct person {
128         char guid[36 + 1];                      // the person GUID
129
130         DICTIONARY *urls;                       // dictionary of PERSON_URL *
131
132         uint32_t first_t;                       // the first time we saw this
133         uint32_t last_t;                        // the last time we saw this
134         uint32_t usages;                        // how many times this has been accessed
135 };
136 typedef struct person PERSON;
137
138
139 // ----------------------------------------------------------------------------
140 // REGISTRY concurrency locking
141
142 static inline void registry_persons_lock(void) {
143         pthread_mutex_lock(&registry.persons_lock);
144 }
145
146 static inline void registry_persons_unlock(void) {
147         pthread_mutex_unlock(&registry.persons_lock);
148 }
149
150 static inline void registry_machines_lock(void) {
151         pthread_mutex_lock(&registry.machines_lock);
152 }
153
154 static inline void registry_machines_unlock(void) {
155         pthread_mutex_unlock(&registry.machines_lock);
156 }
157
158 static inline void registry_urls_lock(void) {
159         pthread_mutex_lock(&registry.urls_lock);
160 }
161
162 static inline void registry_urls_unlock(void) {
163         pthread_mutex_unlock(&registry.urls_lock);
164 }
165
166 // ideally, we should not lock the whole registry for
167 // updating a person's urls.
168 // however, to save the memory required for keeping a
169 // mutex (40 bytes) per person, we do...
170 static inline void registry_person_urls_lock(PERSON *p) {
171         (void)p;
172         pthread_mutex_lock(&registry.person_urls_lock);
173 }
174
175 static inline void registry_person_urls_unlock(PERSON *p) {
176         (void)p;
177         pthread_mutex_unlock(&registry.person_urls_lock);
178 }
179
180 // ideally, we should not lock the whole registry for
181 // updating a machine's urls.
182 // however, to save the memory required for keeping a
183 // mutex (40 bytes) per machine, we do...
184 static inline void registry_machine_urls_lock(MACHINE *m) {
185         (void)m;
186         pthread_mutex_lock(&registry.machine_urls_lock);
187 }
188
189 static inline void registry_machine_urls_unlock(MACHINE *m) {
190         (void)m;
191         pthread_mutex_unlock(&registry.machine_urls_lock);
192 }
193
194 static inline void registry_log_lock(void) {
195         pthread_mutex_lock(&registry.log_lock);
196 }
197
198 static inline void registry_log_unlock(void) {
199         pthread_mutex_unlock(&registry.log_lock);
200 }
201
202
203 // ----------------------------------------------------------------------------
204 // common functions
205
206 // parse a GUID and re-generated to be always lower case
207 // this is used as a protection against the variations of GUIDs
208 static inline int registry_regenerate_guid(const char *guid, char *result) {
209         uuid_t uuid;
210         if(unlikely(uuid_parse(guid, uuid) == -1)) {
211                 info("Registry: GUID '%s' is not a valid GUID.", guid);
212                 return -1;
213         }
214         else {
215                 uuid_unparse_lower(uuid, result);
216
217 #ifdef NETDATA_INTERNAL_CHECKS
218                 if(strcmp(guid, result))
219                         info("Registry: source GUID '%s' and re-generated GUID '%s' differ!", guid, result);
220 #endif /* NETDATA_INTERNAL_CHECKS */
221         }
222
223         return 0;
224 }
225
226 // make sure the names of the machines / URLs do not contain any tabs
227 // (which are used as our separator in the database files)
228 // and are properly trimmed (before and after)
229 static inline char *registry_fix_machine_name(char *name, size_t *len) {
230         char *s = name?name:"";
231
232         // skip leading spaces
233         while(*s && isspace(*s)) s++;
234
235         // make sure all spaces are a SPACE
236         char *t = s;
237         while(*t) {
238                 if(unlikely(isspace(*t)))
239                         *t = ' ';
240
241                 t++;
242         }
243
244         // remove trailing spaces
245         while(--t >= s) {
246                 if(*t == ' ')
247                         *t = '\0';
248                 else
249                         break;
250         }
251         t++;
252
253         if(likely(len))
254                 *len = (t - s);
255
256         return s;
257 }
258
259 static inline char *registry_fix_url(char *url, size_t *len) {
260         return registry_fix_machine_name(url, len);
261 }
262
263
264 // ----------------------------------------------------------------------------
265 // forward definition of functions
266
267 extern PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when);
268 extern PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when);
269
270
271 // ----------------------------------------------------------------------------
272 // URL
273
274 static inline URL *registry_url_allocate_nolock(const char *url, size_t urllen) {
275         debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): allocating %zu bytes", url, sizeof(URL) + urllen);
276         URL *u = malloc(sizeof(URL) + urllen);
277         if(!u) fatal("Cannot allocate %zu bytes for URL '%s'", sizeof(URL) + urllen);
278
279         // a simple strcpy() should do the job
280         // but I prefer to be safe, since the caller specified urllen
281         strncpy(u->url, url, urllen);
282         u->url[urllen] = '\0';
283
284         u->len = urllen;
285         u->links = 0;
286
287         debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): indexing it", url);
288         dictionary_set(registry.urls, u->url, u, sizeof(URL));
289
290         return u;
291 }
292
293 static inline URL *registry_url_get(const char *url, size_t urllen) {
294         debug(D_REGISTRY, "Registry: registry_url_get('%s')", url);
295
296         registry_urls_lock();
297
298         URL *u = dictionary_get(registry.urls, url);
299         if(!u) {
300                 u = registry_url_allocate_nolock(url, urllen);
301                 registry.urls_count++;
302         }
303
304         registry_urls_unlock();
305
306         return u;
307 }
308
309 static inline void registry_url_link_nolock(URL *u) {
310         u->links++;
311         debug(D_REGISTRY, "Registry: registry_url_link_nolock('%s'): URL has now %u links", u->url, u->links);
312 }
313
314 static inline void registry_url_unlink_nolock(URL *u) {
315         u->links--;
316         if(!u->links) {
317                 debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): No more links for this URL", u->url);
318                 dictionary_del(registry.urls, u->url);
319                 free(u);
320         }
321         else
322                 debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): URL has %u links left", u->url, u->links);
323 }
324
325
326 // ----------------------------------------------------------------------------
327 // MACHINE
328
329 static inline MACHINE *registry_machine_find(const char *machine_guid) {
330         debug(D_REGISTRY, "Registry: registry_machine_find('%s')", machine_guid);
331         return dictionary_get(registry.machines, machine_guid);
332 }
333
334 static inline MACHINE_URL *registry_machine_url_allocate(MACHINE *m, URL *u, time_t when) {
335         debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): allocating %zu bytes", m->guid, u->url, sizeof(MACHINE_URL));
336
337         MACHINE_URL *mu = malloc(sizeof(MACHINE_URL));
338         if(!mu) fatal("registry_machine_link_to_url('%s', '%s'): cannot allocate %zu bytes.", m->guid, u->url, sizeof(MACHINE_URL));
339
340         // mu->persons = dictionary_create(DICTIONARY_FLAGS);
341         // dictionary_set(mu->persons, p->guid, p, sizeof(PERSON));
342
343         mu->first_t = mu->last_t = when;
344         mu->usages = 1;
345         mu->url = u;
346         mu->flags = REGISTRY_URL_FLAGS_DEFAULT;
347
348         debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): indexing URL in machine", m->guid, u->url);
349         dictionary_set(m->urls, u->url, mu, sizeof(MACHINE_URL));
350         registry_url_link_nolock(u);
351
352         return mu;
353 }
354
355 static inline MACHINE *registry_machine_allocate(const char *machine_guid, time_t when) {
356         debug(D_REGISTRY, "Registry: registry_machine_allocate('%s'): creating new machine, sizeof(MACHINE)=%zu", machine_guid, sizeof(MACHINE));
357
358         MACHINE *m = calloc(1, sizeof(MACHINE));
359         if(!m) fatal("Registry: cannot allocate memory for new machine '%s'", machine_guid);
360
361         strncpy(m->guid, machine_guid, 36);
362
363         debug(D_REGISTRY, "Registry: registry_machine_allocate('%s'): creating dictionary of urls", machine_guid);
364         m->urls = dictionary_create(DICTIONARY_FLAGS);
365
366         m->first_t = m->last_t = when;
367         m->usages = 0;
368
369         dictionary_set(registry.machines, m->guid, m, sizeof(MACHINE));
370
371         return m;
372 }
373
374 // 1. validate machine GUID
375 // 2. if it is valid, find it or create it and return it
376 // 3. if it is not valid, return NULL
377 static inline MACHINE *registry_machine_get(const char *machine_guid, time_t when) {
378         MACHINE *m = NULL;
379
380         registry_machines_lock();
381
382         if(likely(machine_guid && *machine_guid)) {
383                 // validate it is a GUID
384                 char buf[36 + 1];
385                 if(unlikely(registry_regenerate_guid(machine_guid, buf) == -1))
386                         info("Registry: machine guid '%s' is not a valid guid. Ignoring it.", machine_guid);
387                 else {
388                         machine_guid = buf;
389                         m = registry_machine_find(machine_guid);
390                         if(!m) {
391                                 m = registry_machine_allocate(machine_guid, when);
392                                 registry.machines_count++;
393                         }
394                 }
395         }
396
397         registry_machines_unlock();
398
399         return m;
400 }
401
402
403 // ----------------------------------------------------------------------------
404 // PERSON
405
406 static inline PERSON *registry_person_find(const char *person_guid) {
407         debug(D_REGISTRY, "Registry: registry_person_find('%s')", person_guid);
408         return dictionary_get(registry.persons, person_guid);
409 }
410
411 static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) {
412         debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url, sizeof(PERSON_URL) + namelen);
413         PERSON_URL *pu = malloc(sizeof(PERSON_URL) + namelen);
414         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);
415
416         // a simple strcpy() should do the job
417         // but I prefer to be safe, since the caller specified urllen
418         strncpy(pu->name, name, namelen);
419         pu->name[namelen] = '\0';
420
421         pu->machine = m;
422         pu->first_t = pu->last_t = when;
423         pu->usages = 1;
424         pu->url = u;
425         pu->flags = REGISTRY_URL_FLAGS_DEFAULT;
426
427         debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): indexing URL in person", p->guid, m->guid, u->url);
428         dictionary_set(p->urls, u->url, pu, sizeof(PERSON_URL));
429         registry_url_link_nolock(u);
430
431         return pu;
432 }
433
434 static inline PERSON *registry_person_allocate(const char *person_guid, time_t when) {
435         PERSON *p = NULL;
436
437         debug(D_REGISTRY, "Registry: registry_person_allocate('%s'): allocating new person, sizeof(PERSON)=%zu", (person_guid)?person_guid:"", sizeof(PERSON));
438
439         p = calloc(1, sizeof(PERSON));
440         if(!p) fatal("Registry: cannot allocate memory for new person.");
441
442         if(!person_guid) {
443                 for (; ;) {
444                         uuid_t uuid;
445                         if (uuid_generate_time_safe(uuid) == -1)
446                                 info("Registry: uuid_generate_time_safe() reports UUID generation is not safe for uniqueness.");
447
448                         uuid_unparse_lower(uuid, p->guid);
449
450                         debug(D_REGISTRY, "Registry: Checking if the generated person guid '%s' is unique", p->guid);
451                         if (!dictionary_get(registry.persons, p->guid)) {
452                                 debug(D_REGISTRY, "Registry: generated person guid '%s' is unique", p->guid);
453                                 break;
454                         }
455                         else
456                                 info("Registry: generated person guid '%s' found in the registry. Retrying...", p->guid);
457                 }
458         }
459         else {
460                 strncpy(p->guid, person_guid, 36);
461                 p->guid[36] = '\0';
462         }
463
464         debug(D_REGISTRY, "Registry: registry_person_allocate('%s'): creating dictionary of urls", p->guid);
465         p->urls = dictionary_create(DICTIONARY_FLAGS);
466
467         p->first_t = p->last_t = when;
468         p->usages = 0;
469
470         dictionary_set(registry.persons, p->guid, p, sizeof(PERSON));
471         return p;
472 }
473
474
475 // 1. validate person GUID
476 // 2. if it is valid, find it
477 // 3. if it is not valid, create a new one
478 // 4. return it
479 static inline PERSON *registry_person_get(const char *person_guid, time_t when) {
480         PERSON *p = NULL;
481
482         registry_persons_lock();
483
484         if(person_guid && *person_guid) {
485                 char buf[36 + 1];
486                 // validate it is a GUID
487                 if(unlikely(registry_regenerate_guid(person_guid, buf) == -1))
488                         info("Registry: person guid '%s' is not a valid guid. Ignoring it.", person_guid);
489                 else {
490                         person_guid = buf;
491                         p = registry_person_find(person_guid);
492                         if(!p) person_guid = NULL;
493                 }
494         }
495
496         if(!p) {
497                 p = registry_person_allocate(NULL, when);
498                 registry.persons_count++;
499         }
500
501         registry_persons_unlock();
502
503         return p;
504 }
505
506 // ----------------------------------------------------------------------------
507 // LINKING OF OBJECTS
508
509 static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) {
510         debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): searching for URL in person", p->guid, m->guid, u->url);
511
512         registry_person_urls_lock(p);
513
514         PERSON_URL *pu = dictionary_get(p->urls, u->url);
515         if(!pu) {
516                 debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
517                 pu = registry_person_url_allocate(p, m, u, name, namelen, when);
518                 registry.persons_urls_count++;
519         }
520         else {
521                 debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): found", p->guid, m->guid, u->url);
522                 pu->usages++;
523
524                 if(pu->machine != m) {
525                         MACHINE_URL *mu = dictionary_get(pu->machine->urls, u->url);
526                         if(mu) {
527                                 info("registry_person_link_to_url('%s', '%s', '%s'): URL switched machines (old was '%s') - expiring it from previous machine.",
528                                          p->guid, m->guid, u->url, pu->machine->guid);
529                                 mu->flags |= REGISTRY_URL_FLAGS_EXPIRED;
530                         }
531
532                         pu->machine = m;
533                 }
534         }
535
536         p->usages++;
537         p->last_t = when;
538
539         if(pu->flags & REGISTRY_URL_FLAGS_EXPIRED) {
540                 info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL. Re-enabling URL.", p->guid, m->guid, u->url);
541                 pu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED;
542         }
543
544         registry_person_urls_unlock(p);
545
546         return pu;
547 }
548
549 static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) {
550         debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): searching for URL in machine", p->guid, m->guid, u->url);
551
552         registry_machine_urls_lock(m);
553
554         MACHINE_URL *mu = dictionary_get(m->urls, u->url);
555         if(!mu) {
556                 debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
557                 mu = registry_machine_url_allocate(m, u, when);
558                 registry.machines_urls_count++;
559         }
560         else {
561                 debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): found", p->guid, m->guid, u->url);
562                 mu->usages++;
563         }
564
565         //debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): indexing person in machine", p->guid, m->guid, u->url);
566         //dictionary_set(mu->persons, p->guid, p, sizeof(PERSON));
567
568         m->usages++;
569         m->last_t = when;
570
571         if(mu->flags & REGISTRY_URL_FLAGS_EXPIRED) {
572                 info("registry_machine_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url);
573                 mu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED;
574         }
575
576         registry_machine_urls_unlock(m);
577
578         return mu;
579 }
580
581 // ----------------------------------------------------------------------------
582 // REGISTRY LOG LOAD/SAVE
583
584 static inline void registry_log(const char action, PERSON *p, MACHINE *m, URL *u, char *name) {
585         if(likely(registry.registry_log_fp)) {
586                 // we lock only if the file is open
587                 // to allow replaying the log at registry_log_load()
588                 registry_log_lock();
589
590                 fprintf(registry.registry_log_fp, "%c\t%08x\t%s\t%s\t%s\t%s\n",
591                                 action,
592                                 p->last_t,
593                                 p->guid,
594                                 m->guid,
595                                 name,
596                                 u->url
597                 );
598
599                 registry_log_unlock();
600         }
601 }
602
603 void registry_log_open_nolock(void) {
604         registry.registry_log_fp = fopen(registry.log_filename, "a");
605
606         if(registry.registry_log_fp) {
607                 if (setvbuf(registry.registry_log_fp, NULL, _IOLBF, 0) != 0)
608                         error("Cannot set line buffering on registry log file.");
609         }
610 }
611
612 void registry_log_close_nolock(void) {
613         if(registry.registry_log_fp)
614                 fclose(registry.registry_log_fp);
615
616         registry.registry_log_fp = NULL;
617 }
618
619 void registry_log_recreate_nolock(void) {
620         if(registry.registry_log_fp != NULL) {
621                 registry_log_close_nolock();
622
623                 registry.registry_log_fp = fopen(registry.log_filename, "w");
624                 if(registry.registry_log_fp) fclose(registry.registry_log_fp);
625                 registry.registry_log_fp = NULL;
626
627                 registry_log_open_nolock();
628         }
629
630 }
631
632 int registry_log_load(void) {
633         char *s, buf[4096 + 1];
634         size_t line = -1;
635
636         // closing the log is required here
637         // otherwise we will append to it the values we read
638         registry_log_close_nolock();
639
640         debug(D_REGISTRY, "Registry: loading active db from: %s", registry.log_filename);
641         FILE *fp = fopen(registry.log_filename, "r");
642         if(!fp)
643                 error("Registry: cannot open registry file: %s", registry.log_filename);
644         else {
645                 line = 0;
646                 size_t len = 0;
647                 while ((s = fgets_trim_len(buf, 4096, fp, &len))) {
648                         line++;
649
650                         switch (s[0]) {
651                                 case 'A': // accesses
652                                 case 'D': // deletes
653
654                                         // verify it is valid
655                                         if (unlikely(len < 85 || s[1] != '\t' || s[10] != '\t' || s[47] != '\t' || s[84] != '\t')) {
656                                                 error("Registry: log line %u is wrong (len = %zu).", line, len);
657                                                 continue;
658                                         }
659
660                                         s[1] = s[10] = s[47] = s[84] = '\0';
661
662                                         // skip the name to find the url
663                                         char *url = &s[85];
664                                         while(*url && *url != '\t') url++;
665                                         if(!*url) {
666                                                 error("Registry: log line %u does not have a url.", line);
667                                                 continue;
668                                         }
669                                         *url++ = '\0';
670
671                                         if(s[0] == 'A')
672                                                 registry_request_access(&s[11], &s[48], url, &s[85], strtoul(&s[2], NULL, 16));
673                                         else
674                                                 registry_request_delete(&s[11], &s[48], url, &s[85], strtoul(&s[2], NULL, 16));
675
676                                         break;
677
678                                 default:
679                                         error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s);
680                                         break;
681                         }
682                 }
683         }
684
685         // open the log again
686         registry_log_open_nolock();
687
688         return line;
689 }
690
691
692 // ----------------------------------------------------------------------------
693 // REGISTRY REQUESTS
694
695 PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
696         debug(D_REGISTRY, "registry_request_access('%s', '%s', '%s'): NEW REQUEST", (person_guid)?person_guid:"", machine_guid, url);
697
698         MACHINE *m = registry_machine_get(machine_guid, when);
699         if(!m) return NULL;
700
701         // make sure the name is valid
702         size_t namelen;
703         name = registry_fix_machine_name(name, &namelen);
704
705         size_t urllen;
706         url = registry_fix_url(url, &urllen);
707
708         URL *u = registry_url_get(url, urllen);
709         PERSON *p = registry_person_get(person_guid, when);
710
711         registry_person_link_to_url(p, m, u, name, namelen, when);
712         registry_machine_link_to_url(p, m, u, when);
713
714         registry_log('A', p, m, u, name);
715
716         registry.usages_count++;
717         return p;
718 }
719
720 // verify the person, the machine and the URL exist in our DB
721 PERSON_URL *registry_verify_request(char *person_guid, char *machine_guid, char *url, PERSON **pp, MACHINE **mm) {
722         char pbuf[36 + 1], mbuf[36 + 1];
723
724         if(!person_guid || !*person_guid || !machine_guid || !*machine_guid || !url || !*url) {
725                 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");
726                 return NULL;
727         }
728
729         // normalize the url
730         url = registry_fix_url(url, NULL);
731
732         // make sure the person GUID is valid
733         if(registry_regenerate_guid(person_guid, pbuf) == -1) {
734                 info("Registry Request Verification: invalid person GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
735                 return NULL;
736         }
737         person_guid = pbuf;
738
739         // make sure the machine GUID is valid
740         if(registry_regenerate_guid(machine_guid, mbuf) == -1) {
741                 info("Registry Request Verification: invalid machine GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
742                 return NULL;
743         }
744         machine_guid = mbuf;
745
746         // make sure the machine exists
747         MACHINE *m = registry_machine_find(machine_guid);
748         if(!m) {
749                 info("Registry Request Verification: machine not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
750                 return NULL;
751         }
752         if(mm) *mm = m;
753
754         // make sure the person exist
755         PERSON *p = registry_person_find(person_guid);
756         if(!p) {
757                 info("Registry Request Verification: person not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
758                 return NULL;
759         }
760         if(pp) *pp = p;
761
762         PERSON_URL *pu = dictionary_get(p->urls, url);
763         if(!pu) {
764                 info("Registry Request Verification: URL not found for person, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
765                 return NULL;
766         }
767         return pu;
768 }
769
770 PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
771         (void)when;
772
773         PERSON *p = NULL;
774         MACHINE *m = NULL;
775         PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m);
776         if(!pu || !p || !m) return NULL;
777
778         // normalize the url
779         delete_url = registry_fix_url(delete_url, NULL);
780
781         // make sure the user is not deleting the url it uses
782         if(!strcmp(delete_url, pu->url->url)) {
783                 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);
784                 return NULL;
785         }
786
787         registry_person_urls_lock(p);
788
789         PERSON_URL *dpu = dictionary_get(p->urls, delete_url);
790         if(!dpu) {
791                 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);
792                 registry_person_urls_unlock(p);
793                 return NULL;
794         }
795
796         registry_log('D', p, m, pu->url, dpu->url->url);
797
798         dictionary_del(p->urls, dpu->url->url);
799         registry_url_unlink_nolock(dpu->url);
800         free(dpu);
801
802         registry_person_urls_unlock(p);
803         return p;
804 }
805
806 MACHINE *registry_request_machine(char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
807         (void)when;
808
809         char mbuf[36 + 1];
810
811         PERSON *p = NULL;
812         MACHINE *m = NULL;
813         PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m);
814         if(!pu || !p || !m) return NULL;
815
816         // make sure the machine GUID is valid
817         if(registry_regenerate_guid(request_machine, mbuf) == -1) {
818                 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);
819                 return NULL;
820         }
821         request_machine = mbuf;
822
823         // make sure the machine exists
824         m = registry_machine_find(request_machine);
825         if(!m) {
826                 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);
827                 return NULL;
828         }
829
830         // Verify the user has in the past accessed this machine
831         // We will walk through the PERSON_URLs to find the machine
832         // linking to our machine
833
834         // C magic !
835
836         // a structure to pass to the dictionary_get_all() callback handler
837         struct machine_request_callback_data {
838                 MACHINE *find_this_machine;
839                 PERSON_URL *result;
840         } rdata = { m, NULL };
841
842         // the callback function
843         // this will be run for every PERSON_URL of this PERSON
844         int machine_request_callback(void *entry, void *data) {
845                 PERSON_URL *mypu = (PERSON_URL *)entry;
846                 struct machine_request_callback_data *myrdata = (struct machine_request_callback_data *)data;
847
848                 if(mypu->machine == myrdata->find_this_machine) {
849                         myrdata->result = mypu;
850                         return -1; // this will also stop the walk through
851                 }
852
853                 return 0; // continue
854         }
855
856         // request a walk through on the dictionary
857         // no need for locking here, the underlying dictionary has its own
858         dictionary_get_all(p->urls, machine_request_callback, &rdata);
859
860         if(rdata.result)
861                 return m;
862
863         return NULL;
864 }
865
866
867 // ----------------------------------------------------------------------------
868 // REGISTRY JSON generation
869
870 #ifndef REGISTRY_STANDALONE_TESTS
871
872 static inline void registry_json_header(struct web_client *w, int status) {
873         buffer_flush(w->response.data);
874         buffer_sprintf(w->response.data, "{\n\"success\": %s", status?"true":"false");
875 }
876
877 static inline void registry_json_footer(struct web_client *w) {
878         buffer_strcat(w->response.data, "\n}\n");
879 }
880
881 static inline int registry_json_redirect(struct web_client *w) {
882         registry_json_header(w, 0);
883         buffer_sprintf(w->response.data, "\"registries\": \"%s\"", config_get("global", "users registries", "reg1.mynetdata.io reg2.mynetdata.io reg3.mynetdata.io"));
884         registry_json_footer(w);
885         return 200;
886 }
887
888 int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
889         if(!registry.enabled)
890                 return registry_json_redirect(w);
891
892         PERSON *p = registry_request_access(person_guid, machine_guid, url, name, when);
893         if(!p) {
894                 registry_json_header(w, 0);
895                 registry_json_footer(w);
896                 return 400;
897         }
898
899         // FIXME -- make this a permanent cookie
900         strncpy(w->cookie, p->guid, COOKIE_MAX);
901         w->cookie[COOKIE_MAX] = '\0';
902
903         registry_json_header(w, 1);
904
905         // FIXME - print an array of all URLs / Machines / Names
906
907         buffer_strcat(w->response.data, "\n}\n");
908         return 200;
909 }
910
911 int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
912         if(!registry.enabled)
913                 return registry_json_redirect(w);
914
915         PERSON *p = registry_request_delete(person_guid, machine_guid, url, delete_url, when);
916         if(!p) {
917                 registry_json_header(w, 0);
918                 registry_json_footer(w);
919                 return 400;
920         }
921
922         registry_json_header(w, 1);
923         registry_json_footer(w);
924         return 200;
925 }
926
927 int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
928         if(!registry.enabled)
929                 return registry_json_redirect(w);
930
931         MACHINE *m = registry_request_machine(person_guid, machine_guid, url, request_machine, when);
932         if(!m) {
933                 registry_json_header(w, 0);
934                 registry_json_footer(w);
935                 return 400;
936         }
937
938         registry_json_header(w, 1);
939
940         // FIXME - print an array of all URLs the machine is accessible
941
942         registry_json_footer(w);
943         return 200;
944 }
945
946 #endif /* REGISTRY_STANDALONE_TESTS */
947
948
949 // ----------------------------------------------------------------------------
950 // REGISTRY LOAD/SAVE
951
952 int registry_machine_save_url(void *entry, void *file) {
953         MACHINE_URL *mu = entry;
954         FILE *fp = file;
955
956         debug(D_REGISTRY, "Registry: registry_machine_save_url('%s')", mu->url->url);
957
958         int ret = fprintf(fp, "V\t%08x\t%08x\t%08x\t%02x\t%s\n",
959                         mu->first_t,
960                         mu->last_t,
961                         mu->usages,
962                         mu->flags,
963                         mu->url->url
964         );
965
966         // error handling is done at registry_save()
967
968         return ret;
969 }
970
971 int registry_machine_save(void *entry, void *file) {
972         MACHINE *m = entry;
973         FILE *fp = file;
974
975         debug(D_REGISTRY, "Registry: registry_machine_save('%s')", m->guid);
976
977         int ret = fprintf(fp, "M\t%08x\t%08x\t%08x\t%s\n",
978                         m->first_t,
979                         m->last_t,
980                         m->usages,
981                         m->guid
982         );
983
984         if(ret >= 0) {
985                 int ret2 = dictionary_get_all(m->urls, registry_machine_save_url, fp);
986                 if(ret2 < 0) return ret2;
987                 ret += ret2;
988         }
989
990         // error handling is done at registry_save()
991
992         return ret;
993 }
994
995 static inline int registry_person_save_url(void *entry, void *file) {
996         PERSON_URL *pu = entry;
997         FILE *fp = file;
998
999         debug(D_REGISTRY, "Registry: registry_person_save_url('%s')", pu->url->url);
1000
1001         int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\t%s\n",
1002                         pu->first_t,
1003                         pu->last_t,
1004                         pu->usages,
1005                         pu->flags,
1006                         pu->machine->guid,
1007                         pu->name,
1008                         pu->url->url
1009         );
1010
1011         // error handling is done at registry_save()
1012
1013         return ret;
1014 }
1015
1016 static inline int registry_person_save(void *entry, void *file) {
1017         PERSON *p = entry;
1018         FILE *fp = file;
1019
1020         debug(D_REGISTRY, "Registry: registry_person_save('%s')", p->guid);
1021
1022         int ret = fprintf(fp, "P\t%08x\t%08x\t%08x\t%s\n",
1023                         p->first_t,
1024                         p->last_t,
1025                         p->usages,
1026                         p->guid
1027         );
1028
1029         if(ret >= 0) {
1030                 int ret2 = dictionary_get_all(p->urls, registry_person_save_url, fp);
1031                 if (ret2 < 0) return ret2;
1032                 ret += ret2;
1033         }
1034
1035         // error handling is done at registry_save()
1036
1037         return ret;
1038 }
1039
1040 int registry_save(void) {
1041         if(!registry.enabled) return -1;
1042
1043         char tmp_filename[FILENAME_MAX + 1];
1044         char old_filename[FILENAME_MAX + 1];
1045
1046         snprintf(old_filename, FILENAME_MAX, "%s.old", registry.db_filename);
1047         snprintf(tmp_filename, FILENAME_MAX, "%s.tmp", registry.db_filename);
1048
1049         // make sure the log is not updated
1050         registry_log_lock();
1051
1052         debug(D_REGISTRY, "Registry: Creating file '%s'", tmp_filename);
1053         FILE *fp = fopen(tmp_filename, "w");
1054         if(!fp) {
1055                 error("Registry: Cannot create file: %s", tmp_filename);
1056                 registry_log_unlock();
1057                 return -1;
1058         }
1059
1060         debug(D_REGISTRY, "Saving all machines");
1061         int bytes1 = dictionary_get_all(registry.machines, registry_machine_save, fp);
1062         if(bytes1 < 0) {
1063                 error("Registry: Cannot save registry machines - return value %d", bytes1);
1064                 fclose(fp);
1065                 registry_log_unlock();
1066                 return bytes1;
1067         }
1068         debug(D_REGISTRY, "Registry: saving machines took %d bytes", bytes1);
1069
1070         debug(D_REGISTRY, "Saving all persons");
1071         int bytes2 = dictionary_get_all(registry.persons, registry_person_save, fp);
1072         if(bytes2 < 0) {
1073                 error("Registry: Cannot save registry persons - return value %d", bytes2);
1074                 fclose(fp);
1075                 registry_log_unlock();
1076                 return bytes2;
1077         }
1078         debug(D_REGISTRY, "Registry: saving persons took %d bytes", bytes2);
1079
1080         // save the totals
1081         fprintf(fp, "T\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\n",
1082                         registry.persons_count,
1083                         registry.machines_count,
1084                         registry.usages_count,
1085                         registry.urls_count,
1086                         registry.persons_urls_count,
1087                         registry.machines_urls_count
1088         );
1089
1090         fclose(fp);
1091
1092         errno = 0;
1093
1094         // remove the .old db
1095         debug(D_REGISTRY, "Registry: Removing old db '%s'", old_filename);
1096         if(unlink(old_filename) == -1 && errno != ENOENT)
1097                 error("Registry: cannot remove old registry file '%s'", old_filename);
1098
1099         // rename the db to .old
1100         debug(D_REGISTRY, "Registry: Link current db '%s' to .old: '%s'", registry.db_filename, old_filename);
1101         if(link(registry.db_filename, old_filename) == -1 && errno != ENOENT)
1102                 error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename, registry.db_filename);
1103
1104         else {
1105                 // remove the database (it is saved in .old)
1106                 debug(D_REGISTRY, "Registry: removing db '%s'", registry.db_filename);
1107                 if (unlink(registry.db_filename) == -1 && errno != ENOENT)
1108                         error("Registry: cannot remove old registry file '%s'", registry.db_filename);
1109
1110                 // move the .tmp to make it active
1111                 debug(D_REGISTRY, "Registry: linking tmp db '%s' to active db '%s'", tmp_filename, registry.db_filename);
1112                 if (link(tmp_filename, registry.db_filename) == -1) {
1113                         error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename,
1114                                   registry.db_filename);
1115
1116                         // move the .old back
1117                         debug(D_REGISTRY, "Registry: linking old db '%s' to active db '%s'", old_filename, registry.db_filename);
1118                         if(link(old_filename, registry.db_filename) == -1)
1119                                 error("Registry: cannot move file '%s' to '%s'. Recovering the old registry DB failed!", old_filename, registry.db_filename);
1120                 }
1121                 else {
1122                         debug(D_REGISTRY, "Registry: removing tmp db '%s'", tmp_filename);
1123                         if(unlink(tmp_filename) == -1)
1124                                 error("Registry: cannot remove tmp registry file '%s'", tmp_filename);
1125
1126                         // it has been moved successfully
1127                         // discard the current registry log
1128                         registry_log_recreate_nolock();
1129                 }
1130         }
1131
1132         // continue operations
1133         registry_log_unlock();
1134
1135         return -1;
1136 }
1137
1138 static inline size_t registry_load(void) {
1139         char *s, buf[4096 + 1];
1140         PERSON *p = NULL;
1141         MACHINE *m = NULL;
1142         URL *u = NULL;
1143         size_t line = 0;
1144
1145         debug(D_REGISTRY, "Registry: loading active db from: %s", registry.db_filename);
1146         FILE *fp = fopen(registry.db_filename, "r");
1147         if(!fp) {
1148                 error("Registry: cannot open registry file: %s", registry.db_filename);
1149                 return 0;
1150         }
1151
1152         size_t len = 0;
1153         while((s = fgets_trim_len(buf, 4096, fp, &len))) {
1154                 line++;
1155
1156                 debug(D_REGISTRY, "Registry: read line %zu to length %zu: %s", line, len, s);
1157                 switch(*s) {
1158                         case 'T': // totals
1159                                 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')) {
1160                                         error("Registry totals line %u is wrong (len = %zu).", line, len);
1161                                         continue;
1162                                 }
1163                                 registry.persons_count = strtoull(&s[2], NULL, 16);
1164                                 registry.machines_count = strtoull(&s[19], NULL, 16);
1165                                 registry.usages_count = strtoull(&s[36], NULL, 16);
1166                                 registry.urls_count = strtoull(&s[53], NULL, 16);
1167                                 registry.persons_urls_count = strtoull(&s[70], NULL, 16);
1168                                 registry.machines_urls_count = strtoull(&s[87], NULL, 16);
1169                                 break;
1170
1171                         case 'P': // person
1172                                 m = NULL;
1173                                 // verify it is valid
1174                                 if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
1175                                         error("Registry person line %u is wrong (len = %zu).", line, len);
1176                                         continue;
1177                                 }
1178
1179                                 s[1] = s[10] = s[19] = s[28] = '\0';
1180                                 p = registry_person_allocate(&s[29], strtoul(&s[2], NULL, 16));
1181                                 p->last_t = strtoul(&s[11], NULL, 16);
1182                                 p->usages = strtoul(&s[20], NULL, 16);
1183                                 debug(D_REGISTRY, "Registry loaded person '%s', first: %u, last: %u, usages: %u", p->guid, p->first_t, p->last_t, p->usages);
1184                                 break;
1185
1186                         case 'M': // machine
1187                                 p = NULL;
1188                                 // verify it is valid
1189                                 if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
1190                                         error("Registry person line %u is wrong (len = %zu).", line, len);
1191                                         continue;
1192                                 }
1193
1194                                 s[1] = s[10] = s[19] = s[28] = '\0';
1195                                 m = registry_machine_allocate(&s[29], strtoul(&s[2], NULL, 16));
1196                                 m->last_t = strtoul(&s[11], NULL, 16);
1197                                 m->usages = strtoul(&s[20], NULL, 16);
1198                                 debug(D_REGISTRY, "Registry loaded machine '%s', first: %u, last: %u, usages: %u", m->guid, m->first_t, m->last_t, m->usages);
1199                                 break;
1200
1201                         case 'U': // person URL
1202                                 if(unlikely(!p)) {
1203                                         error("Registry: ignoring line %zu, no person loaded: %s", line, s);
1204                                         continue;
1205                                 }
1206
1207                                 // verify it is valid
1208                                 if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t') {
1209                                         error("Registry person URL line %u is wrong (len = %zu).", line, len);
1210                                         continue;
1211                                 }
1212
1213                                 s[1] = s[10] = s[19] = s[28] = s[31] = s[68] = '\0';
1214
1215                                 // skip the name to find the url
1216                                 char *url = &s[69];
1217                                 while(*url && *url != '\t') url++;
1218                                 if(!*url) {
1219                                         error("Registry person URL line %u does not have a url.", line);
1220                                         continue;
1221                                 }
1222                                 *url++ = '\0';
1223
1224                                 u = registry_url_allocate_nolock(url, strlen(url));
1225
1226                                 time_t first_t = strtoul(&s[2], NULL, 16);
1227
1228                                 m = registry_machine_find(&s[32]);
1229                                 if(!m) m = registry_machine_allocate(&s[32], first_t);
1230
1231                                 PERSON_URL *pu = registry_person_url_allocate(p, m, u, &s[69], strlen(&s[69]), first_t);
1232                                 pu->last_t = strtoul(&s[11], NULL, 16);
1233                                 pu->usages = strtoul(&s[20], NULL, 16);
1234                                 pu->flags = strtoul(&s[29], NULL, 16);
1235                                 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);
1236                                 break;
1237
1238                         case 'V': // machine URL
1239                                 if(unlikely(!m)) {
1240                                         error("Registry: ignoring line %zu, no machine loaded: %s", line, s);
1241                                         continue;
1242                                 }
1243
1244                                 // verify it is valid
1245                                 if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t') {
1246                                         error("Registry person URL line %u is wrong (len = %zu).", line, len);
1247                                         continue;
1248                                 }
1249
1250                                 s[1] = s[10] = s[19] = s[28] = s[31] = '\0';
1251                                 u = registry_url_allocate_nolock(&s[32], strlen(&s[32]));
1252
1253                                 MACHINE_URL *mu = registry_machine_url_allocate(m, u, strtoul(&s[2], NULL, 16));
1254                                 mu->last_t = strtoul(&s[11], NULL, 16);
1255                                 mu->usages = strtoul(&s[20], NULL, 16);
1256                                 mu->flags = strtoul(&s[29], NULL, 16);
1257                                 debug(D_REGISTRY, "Registry loaded machine URL '%s', machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, m->guid, mu->first_t, mu->last_t, mu->usages, mu->flags);
1258                                 break;
1259
1260                         default:
1261                                 error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.db_filename, s);
1262                                 break;
1263                 }
1264         }
1265         fclose(fp);
1266
1267         registry_log_load();
1268
1269         return line;
1270 }
1271
1272 // ----------------------------------------------------------------------------
1273 // REGISTRY
1274
1275 void registry_init(void) {
1276         char filename[FILENAME_MAX + 1];
1277         registry.pathname = config_get("registry", "registry db directory", VARLIB_DIR);
1278         if(mkdir(registry.pathname, 0644) == -1 && errno != EEXIST)
1279                 error("Cannot create directory '%s'", registry.pathname);
1280
1281         snprintf(filename, FILENAME_MAX, "%s/%s", registry.pathname, "registry.db");
1282         registry.db_filename = config_get("registry", "registry db file", filename);
1283
1284         snprintf(filename, FILENAME_MAX, "%s/%s", registry.pathname, "registry-log.db");
1285         registry.log_filename = config_get("registry", "registry log file", filename);
1286
1287         registry.persons_count = 0;
1288         registry.machines_count = 0;
1289         registry.usages_count = 0;
1290         registry.urls_count = 0;
1291         registry.persons_urls_count = 0;
1292         registry.machines_urls_count = 0;
1293
1294         debug(D_REGISTRY, "Registry: creating global registry dictionary for persons.");
1295         registry.persons = dictionary_create(DICTIONARY_FLAGS);
1296
1297         debug(D_REGISTRY, "Registry: creating global registry dictionary for machines.");
1298         registry.machines = dictionary_create(DICTIONARY_FLAGS);
1299
1300         debug(D_REGISTRY, "Registry: creating global registry dictionary for urls.");
1301         registry.urls = dictionary_create(DICTIONARY_FLAGS);
1302
1303         pthread_mutex_init(&registry.persons_lock, NULL);
1304         pthread_mutex_init(&registry.machines_lock, NULL);
1305         pthread_mutex_init(&registry.urls_lock, NULL);
1306         pthread_mutex_init(&registry.person_urls_lock, NULL);
1307         pthread_mutex_init(&registry.machine_urls_lock, NULL);
1308
1309         registry_log_open_nolock();
1310         registry_load();
1311
1312         registry.enabled = 1;
1313 }
1314
1315 void registry_free(void) {
1316         if(!registry.enabled) return;
1317
1318         // we need to destroy the dictionaries ourselves
1319         // since the dictionaries use memory we allocated
1320
1321         while(registry.persons->values_index.root) {
1322                 PERSON *p = ((NAME_VALUE *)registry.persons->values_index.root)->value;
1323
1324                 // fprintf(stderr, "\nPERSON: '%s', first: %u, last: %u, usages: %u\n", p->guid, p->first_t, p->last_t, p->usages);
1325
1326                 while(p->urls->values_index.root) {
1327                         PERSON_URL *pu = ((NAME_VALUE *)p->urls->values_index.root)->value;
1328
1329                         // fprintf(stderr, "\tURL: '%s', first: %u, last: %u, usages: %u, flags: 0x%02x\n", pu->url->url, pu->first_t, pu->last_t, pu->usages, pu->flags);
1330
1331                         debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", pu->url->url, p->guid);
1332                         dictionary_del(p->urls, pu->url->url);
1333
1334                         debug(D_REGISTRY, "Registry: unlinking url '%s' from person", pu->url->url);
1335                         registry_url_unlink_nolock(pu->url);
1336
1337                         debug(D_REGISTRY, "Registry: freeing person url");
1338                         free(pu);
1339                 }
1340
1341                 debug(D_REGISTRY, "Registry: deleting person '%s' from persons registry", p->guid);
1342                 dictionary_del(registry.persons, p->guid);
1343
1344                 debug(D_REGISTRY, "Registry: destroying URL dictionary of person '%s'", p->guid);
1345                 dictionary_destroy(p->urls);
1346
1347                 debug(D_REGISTRY, "Registry: freeing person '%s'", p->guid);
1348                 free(p);
1349         }
1350
1351         while(registry.machines->values_index.root) {
1352                 MACHINE *m = ((NAME_VALUE *)registry.machines->values_index.root)->value;
1353
1354                 // fprintf(stderr, "\nMACHINE: '%s', first: %u, last: %u, usages: %u\n", m->guid, m->first_t, m->last_t, m->usages);
1355
1356                 while(m->urls->values_index.root) {
1357                         MACHINE_URL *mu = ((NAME_VALUE *)m->urls->values_index.root)->value;
1358
1359                         // fprintf(stderr, "\tURL: '%s', first: %u, last: %u, usages: %u, flags: 0x%02x\n", mu->url->url, mu->first_t, mu->last_t, mu->usages, mu->flags);
1360
1361                         //debug(D_REGISTRY, "Registry: destroying persons dictionary from url '%s'", mu->url->url);
1362                         //dictionary_destroy(mu->persons);
1363
1364                         debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", mu->url->url, m->guid);
1365                         dictionary_del(m->urls, mu->url->url);
1366
1367                         debug(D_REGISTRY, "Registry: unlinking url '%s' from machine", mu->url->url);
1368                         registry_url_unlink_nolock(mu->url);
1369
1370                         debug(D_REGISTRY, "Registry: freeing machine url");
1371                         free(mu);
1372                 }
1373
1374                 debug(D_REGISTRY, "Registry: deleting machine '%s' from machines registry", m->guid);
1375                 dictionary_del(registry.machines, m->guid);
1376
1377                 debug(D_REGISTRY, "Registry: destroying URL dictionary of machine '%s'", m->guid);
1378                 dictionary_destroy(m->urls);
1379
1380                 debug(D_REGISTRY, "Registry: freeing machine '%s'", m->guid);
1381                 free(m);
1382         }
1383
1384         // and free the memory of remaining dictionary structures
1385
1386         debug(D_REGISTRY, "Registry: destroying persons dictionary");
1387         dictionary_destroy(registry.persons);
1388
1389         debug(D_REGISTRY, "Registry: destroying machines dictionary");
1390         dictionary_destroy(registry.machines);
1391
1392         debug(D_REGISTRY, "Registry: destroying urls dictionary");
1393         dictionary_destroy(registry.urls);
1394 }
1395
1396
1397 #ifdef REGISTRY_STANDALONE_TESTS
1398
1399 // ----------------------------------------------------------------------------
1400 // TESTS
1401
1402 int test1(int argc, char **argv) {
1403
1404         void print_stats(uint32_t requests, unsigned long long start, unsigned long long end) {
1405                 fprintf(stderr, " > SPEED: %u requests served in %0.2f seconds ( >>> %llu per second <<< )\n",
1406                                 requests, (end-start) / 1000000.0, (unsigned long long)requests * 1000000ULL / (end-start));
1407
1408                 fprintf(stderr, " > DB   : persons %llu, machines %llu, unique URLs %llu, accesses %llu, URLs: for persons %llu, for machines %llu\n",
1409                                 registry.persons_count, registry.machines_count, registry.urls_count, registry.usages_count,
1410                                 registry.persons_urls_count, registry.machines_urls_count);
1411         }
1412
1413         (void) argc;
1414         (void) argv;
1415
1416         uint32_t u, users = 1000000;
1417         uint32_t m, machines = 200000;
1418         uint32_t machines2 = machines * 2;
1419
1420         char **users_guids = malloc(users * sizeof(char *));
1421         char **machines_guids = malloc(machines2 * sizeof(char *));
1422         char **machines_urls = malloc(machines2 * sizeof(char *));
1423         unsigned long long start;
1424
1425         registry_init();
1426
1427         fprintf(stderr, "Generating %u machine guids\n", machines2);
1428         for(m = 0; m < machines2 ;m++) {
1429                 uuid_t uuid;
1430                 machines_guids[m] = malloc(36+1);
1431                 uuid_generate(uuid);
1432                 uuid_unparse(uuid, machines_guids[m]);
1433
1434                 char buf[FILENAME_MAX + 1];
1435                 snprintf(buf, FILENAME_MAX, "http://%u.netdata.rocks/", m+1);
1436                 machines_urls[m] = strdup(buf);
1437
1438                 // fprintf(stderr, "\tmachine %u: '%s', url: '%s'\n", m + 1, machines_guids[m], machines_urls[m]);
1439         }
1440
1441         start = timems();
1442         fprintf(stderr, "\nGenerating %u users accessing %u machines\n", users, machines);
1443         m = 0;
1444         time_t now = time(NULL);
1445         for(u = 0; u < users ; u++) {
1446                 if(++m == machines) m = 0;
1447
1448                 PERSON *p = registry_request_access(NULL, machines_guids[m], machines_urls[m], "test", now);
1449                 users_guids[u] = p->guid;
1450         }
1451         print_stats(u, start, timems());
1452
1453         start = timems();
1454         fprintf(stderr, "\nAll %u users accessing again the same %u servers\n", users, machines);
1455         m = 0;
1456         now = time(NULL);
1457         for(u = 0; u < users ; u++) {
1458                 if(++m == machines) m = 0;
1459
1460                 PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
1461
1462                 if(p->guid != users_guids[u])
1463                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
1464         }
1465         print_stats(u, start, timems());
1466
1467         start = timems();
1468         fprintf(stderr, "\nAll %u users accessing a new server, out of the %u servers\n", users, machines);
1469         m = 1;
1470         now = time(NULL);
1471         for(u = 0; u < users ; u++) {
1472                 if(++m == machines) m = 0;
1473
1474                 PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
1475
1476                 if(p->guid != users_guids[u])
1477                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
1478         }
1479         print_stats(u, start, timems());
1480
1481         start = timems();
1482         fprintf(stderr, "\n%u random users accessing a random server, out of the %u servers\n", users, machines);
1483         now = time(NULL);
1484         for(u = 0; u < users ; u++) {
1485                 uint32_t tu = random() * users / RAND_MAX;
1486                 uint32_t tm = random() * machines / RAND_MAX;
1487
1488                 PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
1489
1490                 if(p->guid != users_guids[tu])
1491                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
1492         }
1493         print_stats(u, start, timems());
1494
1495         start = timems();
1496         fprintf(stderr, "\n%u random users accessing a random server, out of %u servers\n", users, machines2);
1497         now = time(NULL);
1498         for(u = 0; u < users ; u++) {
1499                 uint32_t tu = random() * users / RAND_MAX;
1500                 uint32_t tm = random() * machines2 / RAND_MAX;
1501
1502                 PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
1503
1504                 if(p->guid != users_guids[tu])
1505                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
1506         }
1507         print_stats(u, start, timems());
1508
1509         for(m = 0; m < 10; m++) {
1510                 start = timems();
1511                 fprintf(stderr,
1512                                 "\n%u random user accesses to a random server, out of %u servers,\n > using 1/10000 with a random url, 1/1000 with a mismatched url\n",
1513                                 users * 2, machines2);
1514                 now = time(NULL);
1515                 for (u = 0; u < users * 2; u++) {
1516                         uint32_t tu = random() * users / RAND_MAX;
1517                         uint32_t tm = random() * machines2 / RAND_MAX;
1518
1519                         char *url = machines_urls[tm];
1520                         char buf[FILENAME_MAX + 1];
1521                         if (random() % 10000 == 1234) {
1522                                 snprintf(buf, FILENAME_MAX, "http://random.%ld.netdata.rocks/", random());
1523                                 url = buf;
1524                         }
1525                         else if (random() % 1000 == 123)
1526                                 url = machines_urls[random() * machines2 / RAND_MAX];
1527
1528                         PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], url, "test", now);
1529
1530                         if (p->guid != users_guids[tu])
1531                                 fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
1532                 }
1533                 print_stats(u, start, timems());
1534         }
1535
1536         fprintf(stderr, "\n\nSAVE\n");
1537         start = timems();
1538         registry_save();
1539         print_stats(registry.persons_count, start, timems());
1540
1541         fprintf(stderr, "\n\nCLEANUP\n");
1542         start = timems();
1543         registry_free();
1544         print_stats(registry.persons_count, start, timems());
1545         return 0;
1546 }
1547
1548 // ----------------------------------------------------------------------------
1549 // TESTING
1550
1551 int main(int argc, char **argv) {
1552         // debug_flags = 0xFFFFFFFF;
1553         // test1(argc, argv);
1554         // exit(0);
1555
1556         (void)argc;
1557         (void)argv;
1558
1559
1560         PERSON *p1, *p2;
1561
1562         fprintf(stderr, "\n\nINITIALIZATION\n");
1563
1564         registry_init();
1565
1566         int i = 2;
1567
1568         fprintf(stderr, "\n\nADDING ENTRY\n");
1569         p1 = registry_request_access("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
1570
1571         if(0)
1572         while(i--) {
1573 #ifdef REGISTRY_STDOUT_DUMP
1574                 fprintf(stderr, "\n\nADDING ENTRY\n");
1575 #endif /* REGISTRY_STDOUT_DUMP */
1576                 p1 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
1577
1578 #ifdef REGISTRY_STDOUT_DUMP
1579                 fprintf(stderr, "\n\nADDING ANOTHER URL\n");
1580 #endif /* REGISTRY_STDOUT_DUMP */
1581                 p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", "test", time(NULL));
1582
1583 #ifdef REGISTRY_STDOUT_DUMP
1584                 fprintf(stderr, "\n\nADDING ANOTHER URL\n");
1585 #endif /* REGISTRY_STDOUT_DUMP */
1586                 p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
1587
1588 #ifdef REGISTRY_STDOUT_DUMP
1589                 fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
1590 #endif /* REGISTRY_STDOUT_DUMP */
1591                 p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
1592
1593 #ifdef REGISTRY_STDOUT_DUMP
1594                 fprintf(stderr, "\n\nADDING ANOTHER PERSON\n");
1595 #endif /* REGISTRY_STDOUT_DUMP */
1596                 p2 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
1597
1598 #ifdef REGISTRY_STDOUT_DUMP
1599                 fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
1600 #endif /* REGISTRY_STDOUT_DUMP */
1601                 p2 = registry_request_access(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
1602         }
1603
1604         fprintf(stderr, "\n\nSAVE\n");
1605         registry_save();
1606
1607         fprintf(stderr, "\n\nCLEANUP\n");
1608         registry_free();
1609         return 0;
1610 }
1611
1612 #endif /* REGISTRY_STANDALONE_TESTS */