]> arthur.barton.de Git - netdata.git/blob - src/registry.c
1c998923be864e84ce2eafafb5ee6f3715a8c5db
[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
807 // a structure to pass to the dictionary_get_all() callback handler
808 struct machine_request_callback_data {
809         MACHINE *find_this_machine;
810         PERSON_URL *result;
811 };
812
813 // the callback function
814 // this will be run for every PERSON_URL of this PERSON
815 int machine_request_callback(void *entry, void *data) {
816         PERSON_URL *mypu = (PERSON_URL *)entry;
817         struct machine_request_callback_data *myrdata = (struct machine_request_callback_data *)data;
818
819         if(mypu->machine == myrdata->find_this_machine) {
820                 myrdata->result = mypu;
821                 return -1; // this will also stop the walk through
822         }
823
824         return 0; // continue
825 }
826
827 MACHINE *registry_request_machine(char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
828         (void)when;
829
830         char mbuf[36 + 1];
831
832         PERSON *p = NULL;
833         MACHINE *m = NULL;
834         PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m);
835         if(!pu || !p || !m) return NULL;
836
837         // make sure the machine GUID is valid
838         if(registry_regenerate_guid(request_machine, mbuf) == -1) {
839                 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);
840                 return NULL;
841         }
842         request_machine = mbuf;
843
844         // make sure the machine exists
845         m = registry_machine_find(request_machine);
846         if(!m) {
847                 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);
848                 return NULL;
849         }
850
851         // Verify the user has in the past accessed this machine
852         // We will walk through the PERSON_URLs to find the machine
853         // linking to our machine
854
855         // a structure to pass to the dictionary_get_all() callback handler
856         struct machine_request_callback_data rdata = { m, NULL };
857
858         // request a walk through on the dictionary
859         // no need for locking here, the underlying dictionary has its own
860         dictionary_get_all(p->urls, machine_request_callback, &rdata);
861
862         if(rdata.result)
863                 return m;
864
865         return NULL;
866 }
867
868
869 // ----------------------------------------------------------------------------
870 // REGISTRY JSON generation
871
872 #ifndef REGISTRY_STANDALONE_TESTS
873
874 static inline void registry_json_header(struct web_client *w, int status) {
875         buffer_flush(w->response.data);
876         buffer_sprintf(w->response.data, "{\n\"success\": %s", status?"true":"false");
877 }
878
879 static inline void registry_json_footer(struct web_client *w) {
880         buffer_strcat(w->response.data, "\n}\n");
881 }
882
883 static inline int registry_json_redirect(struct web_client *w) {
884         registry_json_header(w, 0);
885         buffer_sprintf(w->response.data, "\"registries\": \"%s\"", config_get("global", "users registries", "reg1.mynetdata.io reg2.mynetdata.io reg3.mynetdata.io"));
886         registry_json_footer(w);
887         return 200;
888 }
889
890 int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
891         if(!registry.enabled)
892                 return registry_json_redirect(w);
893
894         PERSON *p = registry_request_access(person_guid, machine_guid, url, name, when);
895         if(!p) {
896                 registry_json_header(w, 0);
897                 registry_json_footer(w);
898                 return 400;
899         }
900
901         // FIXME -- make this a permanent cookie
902         strncpy(w->cookie, p->guid, COOKIE_MAX);
903         w->cookie[COOKIE_MAX] = '\0';
904
905         registry_json_header(w, 1);
906
907         // FIXME - print an array of all URLs / Machines / Names
908
909         buffer_strcat(w->response.data, "\n}\n");
910         return 200;
911 }
912
913 int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
914         if(!registry.enabled)
915                 return registry_json_redirect(w);
916
917         PERSON *p = registry_request_delete(person_guid, machine_guid, url, delete_url, when);
918         if(!p) {
919                 registry_json_header(w, 0);
920                 registry_json_footer(w);
921                 return 400;
922         }
923
924         registry_json_header(w, 1);
925         registry_json_footer(w);
926         return 200;
927 }
928
929 int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
930         if(!registry.enabled)
931                 return registry_json_redirect(w);
932
933         MACHINE *m = registry_request_machine(person_guid, machine_guid, url, request_machine, when);
934         if(!m) {
935                 registry_json_header(w, 0);
936                 registry_json_footer(w);
937                 return 400;
938         }
939
940         registry_json_header(w, 1);
941
942         // FIXME - print an array of all URLs the machine is accessible
943
944         registry_json_footer(w);
945         return 200;
946 }
947
948 #endif /* REGISTRY_STANDALONE_TESTS */
949
950
951 // ----------------------------------------------------------------------------
952 // REGISTRY LOAD/SAVE
953
954 int registry_machine_save_url(void *entry, void *file) {
955         MACHINE_URL *mu = entry;
956         FILE *fp = file;
957
958         debug(D_REGISTRY, "Registry: registry_machine_save_url('%s')", mu->url->url);
959
960         int ret = fprintf(fp, "V\t%08x\t%08x\t%08x\t%02x\t%s\n",
961                         mu->first_t,
962                         mu->last_t,
963                         mu->usages,
964                         mu->flags,
965                         mu->url->url
966         );
967
968         // error handling is done at registry_save()
969
970         return ret;
971 }
972
973 int registry_machine_save(void *entry, void *file) {
974         MACHINE *m = entry;
975         FILE *fp = file;
976
977         debug(D_REGISTRY, "Registry: registry_machine_save('%s')", m->guid);
978
979         int ret = fprintf(fp, "M\t%08x\t%08x\t%08x\t%s\n",
980                         m->first_t,
981                         m->last_t,
982                         m->usages,
983                         m->guid
984         );
985
986         if(ret >= 0) {
987                 int ret2 = dictionary_get_all(m->urls, registry_machine_save_url, fp);
988                 if(ret2 < 0) return ret2;
989                 ret += ret2;
990         }
991
992         // error handling is done at registry_save()
993
994         return ret;
995 }
996
997 static inline int registry_person_save_url(void *entry, void *file) {
998         PERSON_URL *pu = entry;
999         FILE *fp = file;
1000
1001         debug(D_REGISTRY, "Registry: registry_person_save_url('%s')", pu->url->url);
1002
1003         int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\t%s\n",
1004                         pu->first_t,
1005                         pu->last_t,
1006                         pu->usages,
1007                         pu->flags,
1008                         pu->machine->guid,
1009                         pu->name,
1010                         pu->url->url
1011         );
1012
1013         // error handling is done at registry_save()
1014
1015         return ret;
1016 }
1017
1018 static inline int registry_person_save(void *entry, void *file) {
1019         PERSON *p = entry;
1020         FILE *fp = file;
1021
1022         debug(D_REGISTRY, "Registry: registry_person_save('%s')", p->guid);
1023
1024         int ret = fprintf(fp, "P\t%08x\t%08x\t%08x\t%s\n",
1025                         p->first_t,
1026                         p->last_t,
1027                         p->usages,
1028                         p->guid
1029         );
1030
1031         if(ret >= 0) {
1032                 int ret2 = dictionary_get_all(p->urls, registry_person_save_url, fp);
1033                 if (ret2 < 0) return ret2;
1034                 ret += ret2;
1035         }
1036
1037         // error handling is done at registry_save()
1038
1039         return ret;
1040 }
1041
1042 int registry_save(void) {
1043         if(!registry.enabled) return -1;
1044
1045         char tmp_filename[FILENAME_MAX + 1];
1046         char old_filename[FILENAME_MAX + 1];
1047
1048         snprintf(old_filename, FILENAME_MAX, "%s.old", registry.db_filename);
1049         snprintf(tmp_filename, FILENAME_MAX, "%s.tmp", registry.db_filename);
1050
1051         // make sure the log is not updated
1052         registry_log_lock();
1053
1054         debug(D_REGISTRY, "Registry: Creating file '%s'", tmp_filename);
1055         FILE *fp = fopen(tmp_filename, "w");
1056         if(!fp) {
1057                 error("Registry: Cannot create file: %s", tmp_filename);
1058                 registry_log_unlock();
1059                 return -1;
1060         }
1061
1062         debug(D_REGISTRY, "Saving all machines");
1063         int bytes1 = dictionary_get_all(registry.machines, registry_machine_save, fp);
1064         if(bytes1 < 0) {
1065                 error("Registry: Cannot save registry machines - return value %d", bytes1);
1066                 fclose(fp);
1067                 registry_log_unlock();
1068                 return bytes1;
1069         }
1070         debug(D_REGISTRY, "Registry: saving machines took %d bytes", bytes1);
1071
1072         debug(D_REGISTRY, "Saving all persons");
1073         int bytes2 = dictionary_get_all(registry.persons, registry_person_save, fp);
1074         if(bytes2 < 0) {
1075                 error("Registry: Cannot save registry persons - return value %d", bytes2);
1076                 fclose(fp);
1077                 registry_log_unlock();
1078                 return bytes2;
1079         }
1080         debug(D_REGISTRY, "Registry: saving persons took %d bytes", bytes2);
1081
1082         // save the totals
1083         fprintf(fp, "T\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\n",
1084                         registry.persons_count,
1085                         registry.machines_count,
1086                         registry.usages_count,
1087                         registry.urls_count,
1088                         registry.persons_urls_count,
1089                         registry.machines_urls_count
1090         );
1091
1092         fclose(fp);
1093
1094         errno = 0;
1095
1096         // remove the .old db
1097         debug(D_REGISTRY, "Registry: Removing old db '%s'", old_filename);
1098         if(unlink(old_filename) == -1 && errno != ENOENT)
1099                 error("Registry: cannot remove old registry file '%s'", old_filename);
1100
1101         // rename the db to .old
1102         debug(D_REGISTRY, "Registry: Link current db '%s' to .old: '%s'", registry.db_filename, old_filename);
1103         if(link(registry.db_filename, old_filename) == -1 && errno != ENOENT)
1104                 error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename, registry.db_filename);
1105
1106         else {
1107                 // remove the database (it is saved in .old)
1108                 debug(D_REGISTRY, "Registry: removing db '%s'", registry.db_filename);
1109                 if (unlink(registry.db_filename) == -1 && errno != ENOENT)
1110                         error("Registry: cannot remove old registry file '%s'", registry.db_filename);
1111
1112                 // move the .tmp to make it active
1113                 debug(D_REGISTRY, "Registry: linking tmp db '%s' to active db '%s'", tmp_filename, registry.db_filename);
1114                 if (link(tmp_filename, registry.db_filename) == -1) {
1115                         error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename,
1116                                   registry.db_filename);
1117
1118                         // move the .old back
1119                         debug(D_REGISTRY, "Registry: linking old db '%s' to active db '%s'", old_filename, registry.db_filename);
1120                         if(link(old_filename, registry.db_filename) == -1)
1121                                 error("Registry: cannot move file '%s' to '%s'. Recovering the old registry DB failed!", old_filename, registry.db_filename);
1122                 }
1123                 else {
1124                         debug(D_REGISTRY, "Registry: removing tmp db '%s'", tmp_filename);
1125                         if(unlink(tmp_filename) == -1)
1126                                 error("Registry: cannot remove tmp registry file '%s'", tmp_filename);
1127
1128                         // it has been moved successfully
1129                         // discard the current registry log
1130                         registry_log_recreate_nolock();
1131                 }
1132         }
1133
1134         // continue operations
1135         registry_log_unlock();
1136
1137         return -1;
1138 }
1139
1140 static inline size_t registry_load(void) {
1141         char *s, buf[4096 + 1];
1142         PERSON *p = NULL;
1143         MACHINE *m = NULL;
1144         URL *u = NULL;
1145         size_t line = 0;
1146
1147         debug(D_REGISTRY, "Registry: loading active db from: %s", registry.db_filename);
1148         FILE *fp = fopen(registry.db_filename, "r");
1149         if(!fp) {
1150                 error("Registry: cannot open registry file: %s", registry.db_filename);
1151                 return 0;
1152         }
1153
1154         size_t len = 0;
1155         while((s = fgets_trim_len(buf, 4096, fp, &len))) {
1156                 line++;
1157
1158                 debug(D_REGISTRY, "Registry: read line %zu to length %zu: %s", line, len, s);
1159                 switch(*s) {
1160                         case 'T': // totals
1161                                 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')) {
1162                                         error("Registry totals line %u is wrong (len = %zu).", line, len);
1163                                         continue;
1164                                 }
1165                                 registry.persons_count = strtoull(&s[2], NULL, 16);
1166                                 registry.machines_count = strtoull(&s[19], NULL, 16);
1167                                 registry.usages_count = strtoull(&s[36], NULL, 16);
1168                                 registry.urls_count = strtoull(&s[53], NULL, 16);
1169                                 registry.persons_urls_count = strtoull(&s[70], NULL, 16);
1170                                 registry.machines_urls_count = strtoull(&s[87], NULL, 16);
1171                                 break;
1172
1173                         case 'P': // person
1174                                 m = NULL;
1175                                 // verify it is valid
1176                                 if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
1177                                         error("Registry person line %u is wrong (len = %zu).", line, len);
1178                                         continue;
1179                                 }
1180
1181                                 s[1] = s[10] = s[19] = s[28] = '\0';
1182                                 p = registry_person_allocate(&s[29], strtoul(&s[2], NULL, 16));
1183                                 p->last_t = strtoul(&s[11], NULL, 16);
1184                                 p->usages = strtoul(&s[20], NULL, 16);
1185                                 debug(D_REGISTRY, "Registry loaded person '%s', first: %u, last: %u, usages: %u", p->guid, p->first_t, p->last_t, p->usages);
1186                                 break;
1187
1188                         case 'M': // machine
1189                                 p = NULL;
1190                                 // verify it is valid
1191                                 if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
1192                                         error("Registry person line %u is wrong (len = %zu).", line, len);
1193                                         continue;
1194                                 }
1195
1196                                 s[1] = s[10] = s[19] = s[28] = '\0';
1197                                 m = registry_machine_allocate(&s[29], strtoul(&s[2], NULL, 16));
1198                                 m->last_t = strtoul(&s[11], NULL, 16);
1199                                 m->usages = strtoul(&s[20], NULL, 16);
1200                                 debug(D_REGISTRY, "Registry loaded machine '%s', first: %u, last: %u, usages: %u", m->guid, m->first_t, m->last_t, m->usages);
1201                                 break;
1202
1203                         case 'U': // person URL
1204                                 if(unlikely(!p)) {
1205                                         error("Registry: ignoring line %zu, no person loaded: %s", line, s);
1206                                         continue;
1207                                 }
1208
1209                                 // verify it is valid
1210                                 if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t') {
1211                                         error("Registry person URL line %u is wrong (len = %zu).", line, len);
1212                                         continue;
1213                                 }
1214
1215                                 s[1] = s[10] = s[19] = s[28] = s[31] = s[68] = '\0';
1216
1217                                 // skip the name to find the url
1218                                 char *url = &s[69];
1219                                 while(*url && *url != '\t') url++;
1220                                 if(!*url) {
1221                                         error("Registry person URL line %u does not have a url.", line);
1222                                         continue;
1223                                 }
1224                                 *url++ = '\0';
1225
1226                                 u = registry_url_allocate_nolock(url, strlen(url));
1227
1228                                 time_t first_t = strtoul(&s[2], NULL, 16);
1229
1230                                 m = registry_machine_find(&s[32]);
1231                                 if(!m) m = registry_machine_allocate(&s[32], first_t);
1232
1233                                 PERSON_URL *pu = registry_person_url_allocate(p, m, u, &s[69], strlen(&s[69]), first_t);
1234                                 pu->last_t = strtoul(&s[11], NULL, 16);
1235                                 pu->usages = strtoul(&s[20], NULL, 16);
1236                                 pu->flags = strtoul(&s[29], NULL, 16);
1237                                 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);
1238                                 break;
1239
1240                         case 'V': // machine URL
1241                                 if(unlikely(!m)) {
1242                                         error("Registry: ignoring line %zu, no machine loaded: %s", line, s);
1243                                         continue;
1244                                 }
1245
1246                                 // verify it is valid
1247                                 if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t') {
1248                                         error("Registry person URL line %u is wrong (len = %zu).", line, len);
1249                                         continue;
1250                                 }
1251
1252                                 s[1] = s[10] = s[19] = s[28] = s[31] = '\0';
1253                                 u = registry_url_allocate_nolock(&s[32], strlen(&s[32]));
1254
1255                                 MACHINE_URL *mu = registry_machine_url_allocate(m, u, strtoul(&s[2], NULL, 16));
1256                                 mu->last_t = strtoul(&s[11], NULL, 16);
1257                                 mu->usages = strtoul(&s[20], NULL, 16);
1258                                 mu->flags = strtoul(&s[29], NULL, 16);
1259                                 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);
1260                                 break;
1261
1262                         default:
1263                                 error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.db_filename, s);
1264                                 break;
1265                 }
1266         }
1267         fclose(fp);
1268
1269         registry_log_load();
1270
1271         return line;
1272 }
1273
1274 // ----------------------------------------------------------------------------
1275 // REGISTRY
1276
1277 void registry_init(void) {
1278         char filename[FILENAME_MAX + 1];
1279         registry.pathname = config_get("registry", "registry db directory", VARLIB_DIR);
1280         if(mkdir(registry.pathname, 0644) == -1 && errno != EEXIST)
1281                 error("Cannot create directory '%s'", registry.pathname);
1282
1283         snprintf(filename, FILENAME_MAX, "%s/%s", registry.pathname, "registry.db");
1284         registry.db_filename = config_get("registry", "registry db file", filename);
1285
1286         snprintf(filename, FILENAME_MAX, "%s/%s", registry.pathname, "registry-log.db");
1287         registry.log_filename = config_get("registry", "registry log file", filename);
1288
1289         registry.persons_count = 0;
1290         registry.machines_count = 0;
1291         registry.usages_count = 0;
1292         registry.urls_count = 0;
1293         registry.persons_urls_count = 0;
1294         registry.machines_urls_count = 0;
1295
1296         debug(D_REGISTRY, "Registry: creating global registry dictionary for persons.");
1297         registry.persons = dictionary_create(DICTIONARY_FLAGS);
1298
1299         debug(D_REGISTRY, "Registry: creating global registry dictionary for machines.");
1300         registry.machines = dictionary_create(DICTIONARY_FLAGS);
1301
1302         debug(D_REGISTRY, "Registry: creating global registry dictionary for urls.");
1303         registry.urls = dictionary_create(DICTIONARY_FLAGS);
1304
1305         pthread_mutex_init(&registry.persons_lock, NULL);
1306         pthread_mutex_init(&registry.machines_lock, NULL);
1307         pthread_mutex_init(&registry.urls_lock, NULL);
1308         pthread_mutex_init(&registry.person_urls_lock, NULL);
1309         pthread_mutex_init(&registry.machine_urls_lock, NULL);
1310
1311         registry_log_open_nolock();
1312         registry_load();
1313
1314         registry.enabled = 1;
1315 }
1316
1317 void registry_free(void) {
1318         if(!registry.enabled) return;
1319
1320         // we need to destroy the dictionaries ourselves
1321         // since the dictionaries use memory we allocated
1322
1323         while(registry.persons->values_index.root) {
1324                 PERSON *p = ((NAME_VALUE *)registry.persons->values_index.root)->value;
1325
1326                 // fprintf(stderr, "\nPERSON: '%s', first: %u, last: %u, usages: %u\n", p->guid, p->first_t, p->last_t, p->usages);
1327
1328                 while(p->urls->values_index.root) {
1329                         PERSON_URL *pu = ((NAME_VALUE *)p->urls->values_index.root)->value;
1330
1331                         // 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);
1332
1333                         debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", pu->url->url, p->guid);
1334                         dictionary_del(p->urls, pu->url->url);
1335
1336                         debug(D_REGISTRY, "Registry: unlinking url '%s' from person", pu->url->url);
1337                         registry_url_unlink_nolock(pu->url);
1338
1339                         debug(D_REGISTRY, "Registry: freeing person url");
1340                         free(pu);
1341                 }
1342
1343                 debug(D_REGISTRY, "Registry: deleting person '%s' from persons registry", p->guid);
1344                 dictionary_del(registry.persons, p->guid);
1345
1346                 debug(D_REGISTRY, "Registry: destroying URL dictionary of person '%s'", p->guid);
1347                 dictionary_destroy(p->urls);
1348
1349                 debug(D_REGISTRY, "Registry: freeing person '%s'", p->guid);
1350                 free(p);
1351         }
1352
1353         while(registry.machines->values_index.root) {
1354                 MACHINE *m = ((NAME_VALUE *)registry.machines->values_index.root)->value;
1355
1356                 // fprintf(stderr, "\nMACHINE: '%s', first: %u, last: %u, usages: %u\n", m->guid, m->first_t, m->last_t, m->usages);
1357
1358                 while(m->urls->values_index.root) {
1359                         MACHINE_URL *mu = ((NAME_VALUE *)m->urls->values_index.root)->value;
1360
1361                         // 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);
1362
1363                         //debug(D_REGISTRY, "Registry: destroying persons dictionary from url '%s'", mu->url->url);
1364                         //dictionary_destroy(mu->persons);
1365
1366                         debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", mu->url->url, m->guid);
1367                         dictionary_del(m->urls, mu->url->url);
1368
1369                         debug(D_REGISTRY, "Registry: unlinking url '%s' from machine", mu->url->url);
1370                         registry_url_unlink_nolock(mu->url);
1371
1372                         debug(D_REGISTRY, "Registry: freeing machine url");
1373                         free(mu);
1374                 }
1375
1376                 debug(D_REGISTRY, "Registry: deleting machine '%s' from machines registry", m->guid);
1377                 dictionary_del(registry.machines, m->guid);
1378
1379                 debug(D_REGISTRY, "Registry: destroying URL dictionary of machine '%s'", m->guid);
1380                 dictionary_destroy(m->urls);
1381
1382                 debug(D_REGISTRY, "Registry: freeing machine '%s'", m->guid);
1383                 free(m);
1384         }
1385
1386         // and free the memory of remaining dictionary structures
1387
1388         debug(D_REGISTRY, "Registry: destroying persons dictionary");
1389         dictionary_destroy(registry.persons);
1390
1391         debug(D_REGISTRY, "Registry: destroying machines dictionary");
1392         dictionary_destroy(registry.machines);
1393
1394         debug(D_REGISTRY, "Registry: destroying urls dictionary");
1395         dictionary_destroy(registry.urls);
1396 }
1397
1398
1399 #ifdef REGISTRY_STANDALONE_TESTS
1400
1401 // ----------------------------------------------------------------------------
1402 // TESTS
1403
1404 int test1(int argc, char **argv) {
1405
1406         void print_stats(uint32_t requests, unsigned long long start, unsigned long long end) {
1407                 fprintf(stderr, " > SPEED: %u requests served in %0.2f seconds ( >>> %llu per second <<< )\n",
1408                                 requests, (end-start) / 1000000.0, (unsigned long long)requests * 1000000ULL / (end-start));
1409
1410                 fprintf(stderr, " > DB   : persons %llu, machines %llu, unique URLs %llu, accesses %llu, URLs: for persons %llu, for machines %llu\n",
1411                                 registry.persons_count, registry.machines_count, registry.urls_count, registry.usages_count,
1412                                 registry.persons_urls_count, registry.machines_urls_count);
1413         }
1414
1415         (void) argc;
1416         (void) argv;
1417
1418         uint32_t u, users = 1000000;
1419         uint32_t m, machines = 200000;
1420         uint32_t machines2 = machines * 2;
1421
1422         char **users_guids = malloc(users * sizeof(char *));
1423         char **machines_guids = malloc(machines2 * sizeof(char *));
1424         char **machines_urls = malloc(machines2 * sizeof(char *));
1425         unsigned long long start;
1426
1427         registry_init();
1428
1429         fprintf(stderr, "Generating %u machine guids\n", machines2);
1430         for(m = 0; m < machines2 ;m++) {
1431                 uuid_t uuid;
1432                 machines_guids[m] = malloc(36+1);
1433                 uuid_generate(uuid);
1434                 uuid_unparse(uuid, machines_guids[m]);
1435
1436                 char buf[FILENAME_MAX + 1];
1437                 snprintf(buf, FILENAME_MAX, "http://%u.netdata.rocks/", m+1);
1438                 machines_urls[m] = strdup(buf);
1439
1440                 // fprintf(stderr, "\tmachine %u: '%s', url: '%s'\n", m + 1, machines_guids[m], machines_urls[m]);
1441         }
1442
1443         start = timems();
1444         fprintf(stderr, "\nGenerating %u users accessing %u machines\n", users, machines);
1445         m = 0;
1446         time_t now = time(NULL);
1447         for(u = 0; u < users ; u++) {
1448                 if(++m == machines) m = 0;
1449
1450                 PERSON *p = registry_request_access(NULL, machines_guids[m], machines_urls[m], "test", now);
1451                 users_guids[u] = p->guid;
1452         }
1453         print_stats(u, start, timems());
1454
1455         start = timems();
1456         fprintf(stderr, "\nAll %u users accessing again the same %u servers\n", users, machines);
1457         m = 0;
1458         now = time(NULL);
1459         for(u = 0; u < users ; u++) {
1460                 if(++m == machines) m = 0;
1461
1462                 PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
1463
1464                 if(p->guid != users_guids[u])
1465                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
1466         }
1467         print_stats(u, start, timems());
1468
1469         start = timems();
1470         fprintf(stderr, "\nAll %u users accessing a new server, out of the %u servers\n", users, machines);
1471         m = 1;
1472         now = time(NULL);
1473         for(u = 0; u < users ; u++) {
1474                 if(++m == machines) m = 0;
1475
1476                 PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
1477
1478                 if(p->guid != users_guids[u])
1479                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
1480         }
1481         print_stats(u, start, timems());
1482
1483         start = timems();
1484         fprintf(stderr, "\n%u random users accessing a random server, out of the %u servers\n", users, machines);
1485         now = time(NULL);
1486         for(u = 0; u < users ; u++) {
1487                 uint32_t tu = random() * users / RAND_MAX;
1488                 uint32_t tm = random() * machines / RAND_MAX;
1489
1490                 PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
1491
1492                 if(p->guid != users_guids[tu])
1493                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
1494         }
1495         print_stats(u, start, timems());
1496
1497         start = timems();
1498         fprintf(stderr, "\n%u random users accessing a random server, out of %u servers\n", users, machines2);
1499         now = time(NULL);
1500         for(u = 0; u < users ; u++) {
1501                 uint32_t tu = random() * users / RAND_MAX;
1502                 uint32_t tm = random() * machines2 / RAND_MAX;
1503
1504                 PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
1505
1506                 if(p->guid != users_guids[tu])
1507                         fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
1508         }
1509         print_stats(u, start, timems());
1510
1511         for(m = 0; m < 10; m++) {
1512                 start = timems();
1513                 fprintf(stderr,
1514                                 "\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",
1515                                 users * 2, machines2);
1516                 now = time(NULL);
1517                 for (u = 0; u < users * 2; u++) {
1518                         uint32_t tu = random() * users / RAND_MAX;
1519                         uint32_t tm = random() * machines2 / RAND_MAX;
1520
1521                         char *url = machines_urls[tm];
1522                         char buf[FILENAME_MAX + 1];
1523                         if (random() % 10000 == 1234) {
1524                                 snprintf(buf, FILENAME_MAX, "http://random.%ld.netdata.rocks/", random());
1525                                 url = buf;
1526                         }
1527                         else if (random() % 1000 == 123)
1528                                 url = machines_urls[random() * machines2 / RAND_MAX];
1529
1530                         PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], url, "test", now);
1531
1532                         if (p->guid != users_guids[tu])
1533                                 fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
1534                 }
1535                 print_stats(u, start, timems());
1536         }
1537
1538         fprintf(stderr, "\n\nSAVE\n");
1539         start = timems();
1540         registry_save();
1541         print_stats(registry.persons_count, start, timems());
1542
1543         fprintf(stderr, "\n\nCLEANUP\n");
1544         start = timems();
1545         registry_free();
1546         print_stats(registry.persons_count, start, timems());
1547         return 0;
1548 }
1549
1550 // ----------------------------------------------------------------------------
1551 // TESTING
1552
1553 int main(int argc, char **argv) {
1554         // debug_flags = 0xFFFFFFFF;
1555         // test1(argc, argv);
1556         // exit(0);
1557
1558         (void)argc;
1559         (void)argv;
1560
1561
1562         PERSON *p1, *p2;
1563
1564         fprintf(stderr, "\n\nINITIALIZATION\n");
1565
1566         registry_init();
1567
1568         int i = 2;
1569
1570         fprintf(stderr, "\n\nADDING ENTRY\n");
1571         p1 = registry_request_access("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
1572
1573         if(0)
1574         while(i--) {
1575 #ifdef REGISTRY_STDOUT_DUMP
1576                 fprintf(stderr, "\n\nADDING ENTRY\n");
1577 #endif /* REGISTRY_STDOUT_DUMP */
1578                 p1 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
1579
1580 #ifdef REGISTRY_STDOUT_DUMP
1581                 fprintf(stderr, "\n\nADDING ANOTHER URL\n");
1582 #endif /* REGISTRY_STDOUT_DUMP */
1583                 p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", "test", time(NULL));
1584
1585 #ifdef REGISTRY_STDOUT_DUMP
1586                 fprintf(stderr, "\n\nADDING ANOTHER URL\n");
1587 #endif /* REGISTRY_STDOUT_DUMP */
1588                 p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
1589
1590 #ifdef REGISTRY_STDOUT_DUMP
1591                 fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
1592 #endif /* REGISTRY_STDOUT_DUMP */
1593                 p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
1594
1595 #ifdef REGISTRY_STDOUT_DUMP
1596                 fprintf(stderr, "\n\nADDING ANOTHER PERSON\n");
1597 #endif /* REGISTRY_STDOUT_DUMP */
1598                 p2 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
1599
1600 #ifdef REGISTRY_STDOUT_DUMP
1601                 fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
1602 #endif /* REGISTRY_STDOUT_DUMP */
1603                 p2 = registry_request_access(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
1604         }
1605
1606         fprintf(stderr, "\n\nSAVE\n");
1607         registry_save();
1608
1609         fprintf(stderr, "\n\nCLEANUP\n");
1610         registry_free();
1611         return 0;
1612 }
1613
1614 #endif /* REGISTRY_STANDALONE_TESTS */