summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/os/system-maps
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnetdata/os/system-maps')
-rw-r--r--src/libnetdata/os/system-maps/cache-host-users-and-groups.c101
-rw-r--r--src/libnetdata/os/system-maps/cache-host-users-and-groups.h9
-rw-r--r--src/libnetdata/os/system-maps/cached-gid-groupname.c149
-rw-r--r--src/libnetdata/os/system-maps/cached-gid-groupname.h24
-rw-r--r--src/libnetdata/os/system-maps/cached-sid-username.c200
-rw-r--r--src/libnetdata/os/system-maps/cached-sid-username.h17
-rw-r--r--src/libnetdata/os/system-maps/cached-uid-username.c149
-rw-r--r--src/libnetdata/os/system-maps/cached-uid-username.h24
-rw-r--r--src/libnetdata/os/system-maps/system-services.h96
9 files changed, 769 insertions, 0 deletions
diff --git a/src/libnetdata/os/system-maps/cache-host-users-and-groups.c b/src/libnetdata/os/system-maps/cache-host-users-and-groups.c
new file mode 100644
index 000000000..53825fd35
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cache-host-users-and-groups.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "libnetdata/libnetdata.h"
+
+static bool file_changed(const struct stat *statbuf __maybe_unused, struct timespec *last_modification_time __maybe_unused) {
+#if defined(OS_MACOS) || defined(OS_WINDOWS)
+ return false;
+#else
+ if(likely(statbuf->st_mtim.tv_sec == last_modification_time->tv_sec &&
+ statbuf->st_mtim.tv_nsec == last_modification_time->tv_nsec)) return false;
+
+ last_modification_time->tv_sec = statbuf->st_mtim.tv_sec;
+ last_modification_time->tv_nsec = statbuf->st_mtim.tv_nsec;
+
+ return true;
+#endif
+}
+
+static size_t read_passwd_or_group(const char *filename, struct timespec *last_modification_time, void (*cb)(uint32_t gid, const char *name, uint32_t version), uint32_t version) {
+ struct stat statbuf;
+ if(unlikely(stat(filename, &statbuf) || !file_changed(&statbuf, last_modification_time)))
+ return 0;
+
+ procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 0;
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0;
+
+ size_t line, lines = procfile_lines(ff);
+
+ size_t added = 0;
+ for(line = 0; line < lines ;line++) {
+ size_t words = procfile_linewords(ff, line);
+ if(unlikely(words < 3)) continue;
+
+ char *name = procfile_lineword(ff, line, 0);
+ if(unlikely(!name || !*name)) continue;
+
+ char *id_string = procfile_lineword(ff, line, 2);
+ if(unlikely(!id_string || !*id_string)) continue;
+
+ uint32_t id = str2ull(id_string, NULL);
+
+ cb(id, name, version);
+ added++;
+ }
+
+ procfile_close(ff);
+ return added;
+}
+
+void update_cached_host_users(void) {
+ if(!netdata_configured_host_prefix || !*netdata_configured_host_prefix) return;
+
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ if(!spinlock_trylock(&spinlock)) return;
+
+ char filename[FILENAME_MAX];
+ static bool initialized = false;
+
+ size_t added = 0;
+
+ if(!initialized) {
+ initialized = true;
+ cached_usernames_init();
+ }
+
+ static uint32_t passwd_version = 0;
+ static struct timespec passwd_ts = { 0 };
+ snprintfz(filename, FILENAME_MAX, "%s/etc/passwd", netdata_configured_host_prefix);
+ added = read_passwd_or_group(filename, &passwd_ts, cached_username_populate_by_uid, ++passwd_version);
+ if(added) cached_usernames_delete_old_versions(passwd_version);
+
+ spinlock_unlock(&spinlock);
+}
+
+void update_cached_host_groups(void) {
+ if(!netdata_configured_host_prefix || !*netdata_configured_host_prefix) return;
+
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ if(!spinlock_trylock(&spinlock)) return;
+
+ char filename[FILENAME_MAX];
+ static bool initialized = false;
+
+ size_t added = 0;
+
+ if(!initialized) {
+ initialized = true;
+ cached_groupnames_init();
+ }
+
+ static uint32_t group_version = 0;
+ static struct timespec group_ts = { 0 };
+ snprintfz(filename, FILENAME_MAX, "%s/etc/group", netdata_configured_host_prefix);
+ added = read_passwd_or_group(filename, &group_ts, cached_groupname_populate_by_gid, ++group_version);
+ if(added) cached_groupnames_delete_old_versions(group_version);
+
+ spinlock_unlock(&spinlock);
+}
diff --git a/src/libnetdata/os/system-maps/cache-host-users-and-groups.h b/src/libnetdata/os/system-maps/cache-host-users-and-groups.h
new file mode 100644
index 000000000..7a84bcadf
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cache-host-users-and-groups.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CACHE_HOST_USERS_AND_GROUPS_H
+#define NETDATA_CACHE_HOST_USERS_AND_GROUPS_H
+
+void update_cached_host_users(void);
+void update_cached_host_groups(void);
+
+#endif //NETDATA_CACHE_HOST_USERS_AND_GROUPS_H
diff --git a/src/libnetdata/os/system-maps/cached-gid-groupname.c b/src/libnetdata/os/system-maps/cached-gid-groupname.c
new file mode 100644
index 000000000..3fabe94a2
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cached-gid-groupname.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "cached-gid-groupname.h"
+
+// --------------------------------------------------------------------------------------------------------------------
+// hashtable for caching gid to groupname mappings
+// key is the gid, value is groupname (STRING)
+
+#define SIMPLE_HASHTABLE_KEY_TYPE gid_t
+#define SIMPLE_HASHTABLE_VALUE_TYPE CACHED_GROUPNAME
+#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION cached_groupname_to_gid_ptr
+#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION compar_gid_ptr
+#define SIMPLE_HASHTABLE_NAME _GROUPNAMES_CACHE
+#include "libnetdata/simple_hashtable/simple_hashtable.h"
+
+static struct {
+ bool initialized;
+ SPINLOCK spinlock;
+ SIMPLE_HASHTABLE_GROUPNAMES_CACHE ht;
+} group_cache = {
+ .initialized = false,
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .ht = { 0 },
+};
+
+static gid_t *cached_groupname_to_gid_ptr(CACHED_GROUPNAME *cu) {
+ return &cu->gid;
+}
+
+static bool compar_gid_ptr(gid_t *a, gid_t *b) {
+ return *a == *b;
+}
+
+void cached_groupname_populate_by_gid(gid_t gid, const char *groupname, uint32_t version) {
+ internal_fatal(!group_cache.initialized, "system-users cache needs to be initialized");
+ if(!groupname || !*groupname) return;
+
+ spinlock_lock(&group_cache.spinlock);
+
+ XXH64_hash_t hash = XXH3_64bits(&gid, sizeof(gid));
+ SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_get_slot_GROUPNAMES_CACHE(&group_cache.ht, hash, &gid, true);
+ CACHED_GROUPNAME *cg = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(!cg || (cg->version && version > cg->version)) {
+ internal_fatal(cg && cg->gid != gid, "invalid gid matched from cache");
+
+ if(cg)
+ string_freez(cg->groupname);
+ else
+ cg = callocz(1, sizeof(*cg));
+
+ cg->version = version;
+ cg->gid = gid;
+ cg->groupname = string_strdupz(groupname);
+ simple_hashtable_set_slot_GROUPNAMES_CACHE(&group_cache.ht, sl, hash, cg);
+ }
+
+ spinlock_unlock(&group_cache.spinlock);
+}
+
+CACHED_GROUPNAME cached_groupname_get_by_gid(gid_t gid) {
+ internal_fatal(!group_cache.initialized, "system-users cache needs to be initialized");
+
+ spinlock_lock(&group_cache.spinlock);
+
+ XXH64_hash_t hash = XXH3_64bits(&gid, sizeof(gid));
+ SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_get_slot_GROUPNAMES_CACHE(&group_cache.ht, hash, &gid, true);
+ CACHED_GROUPNAME *cg = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(!cg) {
+ cg = callocz(1, sizeof(*cg));
+
+ static char tmp[1024]; // we are inside a global spinlock - it is ok to be static
+ struct group gr, *result = NULL;
+
+ if (getgrgid_r(gid, &gr, tmp, sizeof(tmp), &result) != 0 || !result || !gr.gr_name || !(*gr.gr_name)) {
+ char name[UINT64_MAX_LENGTH];
+ print_uint64(name, gid);
+ cg->groupname = string_strdupz(name);
+ }
+ else
+ cg->groupname = string_strdupz(gr.gr_name);
+
+ cg->gid = gid;
+ simple_hashtable_set_slot_GROUPNAMES_CACHE(&group_cache.ht, sl, hash, cg);
+ }
+
+ internal_fatal(cg->gid != gid, "invalid gid matched from cache");
+
+ CACHED_GROUPNAME rc = {
+ .version = cg->version,
+ .gid = cg->gid,
+ .groupname = string_dup(cg->groupname),
+ };
+
+ spinlock_unlock(&group_cache.spinlock);
+ return rc;
+}
+
+void cached_groupname_release(CACHED_GROUPNAME cg) {
+ string_freez(cg.groupname);
+}
+
+void cached_groupnames_init(void) {
+ if(group_cache.initialized) return;
+ group_cache.initialized = true;
+
+ spinlock_init(&group_cache.spinlock);
+ simple_hashtable_init_GROUPNAMES_CACHE(&group_cache.ht, 100);
+}
+
+void cached_groupnames_destroy(void) {
+ if(!group_cache.initialized) return;
+
+ spinlock_lock(&group_cache.spinlock);
+
+ for(SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_first_read_only_GROUPNAMES_CACHE(&group_cache.ht);
+ sl;
+ sl = simple_hashtable_next_read_only_GROUPNAMES_CACHE(&group_cache.ht, sl)) {
+ CACHED_GROUPNAME *u = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(u) {
+ string_freez(u->groupname);
+ freez(u);
+ // simple_hashtable_del_slot_GROUPNAMES_CACHE(&uc.ht, sl);
+ }
+ }
+
+ simple_hashtable_destroy_GROUPNAMES_CACHE(&group_cache.ht);
+ group_cache.initialized = false;
+
+ spinlock_unlock(&group_cache.spinlock);
+}
+
+void cached_groupnames_delete_old_versions(uint32_t version) {
+ if(!group_cache.initialized) return;
+
+ spinlock_lock(&group_cache.spinlock);
+
+ for(SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_first_read_only_GROUPNAMES_CACHE(&group_cache.ht);
+ sl;
+ sl = simple_hashtable_next_read_only_GROUPNAMES_CACHE(&group_cache.ht, sl)) {
+ CACHED_GROUPNAME *cg = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(cg && cg->version && cg->version < version) {
+ string_freez(cg->groupname);
+ freez(cg);
+ simple_hashtable_del_slot_GROUPNAMES_CACHE(&group_cache.ht, sl);
+ }
+ }
+
+ spinlock_unlock(&group_cache.spinlock);
+}
diff --git a/src/libnetdata/os/system-maps/cached-gid-groupname.h b/src/libnetdata/os/system-maps/cached-gid-groupname.h
new file mode 100644
index 000000000..81a62523e
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cached-gid-groupname.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CACHED_UID_GROUPNAME_H
+#define NETDATA_CACHED_UID_GROUPNAME_H
+
+#include "libnetdata/libnetdata.h"
+
+struct netdata_string;
+
+typedef struct {
+ uint32_t version;
+ gid_t gid;
+ struct netdata_string *groupname;
+} CACHED_GROUPNAME;
+
+void cached_groupname_populate_by_gid(gid_t gid, const char *groupname, uint32_t version);
+CACHED_GROUPNAME cached_groupname_get_by_gid(gid_t gid);
+void cached_groupname_release(CACHED_GROUPNAME cg);
+void cached_groupnames_delete_old_versions(uint32_t version);
+
+void cached_groupnames_init(void);
+void cached_groupnames_destroy(void);
+
+#endif //NETDATA_CACHED_UID_GROUPNAME_H
diff --git a/src/libnetdata/os/system-maps/cached-sid-username.c b/src/libnetdata/os/system-maps/cached-sid-username.c
new file mode 100644
index 000000000..a0f90c546
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cached-sid-username.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "../../libnetdata.h"
+
+#if defined(OS_WINDOWS)
+#include "cached-sid-username.h"
+
+typedef struct {
+ size_t len;
+ uint8_t sid[];
+} SID_KEY;
+
+typedef struct {
+ // IMPORTANT:
+ // This is malloc'd ! You have to manually set fields to zero.
+
+ STRING *account;
+ STRING *domain;
+ STRING *full;
+ STRING *sid_str;
+
+ // this needs to be last, because of its variable size
+ SID_KEY key;
+} SID_VALUE;
+
+#define SIMPLE_HASHTABLE_NAME _SID
+#define SIMPLE_HASHTABLE_VALUE_TYPE SID_VALUE
+#define SIMPLE_HASHTABLE_KEY_TYPE SID_KEY
+#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION sid_value_to_key
+#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION sid_cache_compar
+#define SIMPLE_HASHTABLE_SAMPLE_IMPLEMENTATION 1
+#include "libnetdata/simple_hashtable/simple_hashtable.h"
+
+static struct {
+ SPINLOCK spinlock;
+ struct simple_hashtable_SID hashtable;
+} sid_globals = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .hashtable = { 0 },
+};
+
+static inline SID_KEY *sid_value_to_key(SID_VALUE *s) {
+ return &s->key;
+}
+
+static inline bool sid_cache_compar(SID_KEY *a, SID_KEY *b) {
+ return a->len == b->len && memcmp(&a->sid, &b->sid, a->len) == 0;
+}
+
+void cached_sid_username_init(void) {
+ simple_hashtable_init_SID(&sid_globals.hashtable, 100);
+}
+
+static char *account2utf8(const wchar_t *user) {
+ static __thread char buffer[256];
+ if(utf16_to_utf8(buffer, sizeof(buffer), user, -1, NULL) == 0)
+ buffer[0] = '\0';
+ return buffer;
+}
+
+static char *domain2utf8(const wchar_t *domain) {
+ static __thread char buffer[256];
+ if(utf16_to_utf8(buffer, sizeof(buffer), domain, -1, NULL) == 0)
+ buffer[0] = '\0';
+ return buffer;
+}
+
+static void lookup_user_in_system(SID_VALUE *sv) {
+ static __thread wchar_t account_unicode[256];
+ static __thread wchar_t domain_unicode[256];
+ static __thread char tmp[512 + 2];
+
+ DWORD account_name_size = sizeof(account_unicode) / sizeof(account_unicode[0]);
+ DWORD domain_name_size = sizeof(domain_unicode) / sizeof(domain_unicode[0]);
+ SID_NAME_USE sid_type;
+
+ if (LookupAccountSidW(NULL, sv->key.sid, account_unicode, &account_name_size, domain_unicode, &domain_name_size, &sid_type)) {
+ const char *account = account2utf8(account_unicode);
+ const char *domain = domain2utf8(domain_unicode);
+ snprintfz(tmp, sizeof(tmp), "%s\\%s", domain, account);
+ sv->domain = string_strdupz(domain);
+ sv->account = string_strdupz(account);
+ sv->full = string_strdupz(tmp);
+ }
+ else {
+ sv->domain = NULL;
+ sv->account = NULL;
+ sv->full = NULL;
+ }
+
+ wchar_t *sid_string = NULL;
+ if (ConvertSidToStringSidW(sv->key.sid, &sid_string))
+ sv->sid_str = string_strdupz(account2utf8(sid_string));
+ else
+ sv->sid_str = NULL;
+}
+
+static SID_VALUE *lookup_or_convert_user_id_to_name_lookup(PSID sid) {
+ if(!sid || !IsValidSid(sid))
+ return NULL;
+
+ size_t size = GetLengthSid(sid);
+
+ size_t tmp_size = sizeof(SID_VALUE) + size;
+ size_t tmp_key_size = sizeof(SID_KEY) + size;
+ uint8_t buf[tmp_size];
+ SID_VALUE *tmp = (SID_VALUE *)&buf;
+ memcpy(&tmp->key.sid, sid, size);
+ tmp->key.len = size;
+
+ spinlock_lock(&sid_globals.spinlock);
+ SID_VALUE *found = simple_hashtable_get_SID(&sid_globals.hashtable, &tmp->key, tmp_key_size);
+ spinlock_unlock(&sid_globals.spinlock);
+ if(found) return found;
+
+ // allocate the SID_VALUE
+ found = mallocz(tmp_size);
+ memcpy(found, buf, tmp_size);
+
+ lookup_user_in_system(found);
+
+ // add it to the cache
+ spinlock_lock(&sid_globals.spinlock);
+ simple_hashtable_set_SID(&sid_globals.hashtable, &found->key, tmp_key_size, found);
+ spinlock_unlock(&sid_globals.spinlock);
+
+ return found;
+}
+
+bool cached_sid_to_account_domain_sidstr(PSID sid, TXT_UTF8 *dst_account, TXT_UTF8 *dst_domain, TXT_UTF8 *dst_sid_str) {
+ SID_VALUE *found = lookup_or_convert_user_id_to_name_lookup(sid);
+
+ if(found) {
+ if (found->account) {
+ txt_utf8_resize(dst_account, string_strlen(found->account) + 1, false);
+ memcpy(dst_account->data, string2str(found->account), string_strlen(found->account) + 1);
+ dst_account->used = string_strlen(found->account) + 1;
+ }
+ else
+ txt_utf8_empty(dst_account);
+
+ if (found->domain) {
+ txt_utf8_resize(dst_domain, string_strlen(found->domain) + 1, false);
+ memcpy(dst_domain->data, string2str(found->domain), string_strlen(found->domain) + 1);
+ dst_domain->used = string_strlen(found->domain) + 1;
+ }
+ else
+ txt_utf8_empty(dst_domain);
+
+ if (found->sid_str) {
+ txt_utf8_resize(dst_sid_str, string_strlen(found->sid_str) + 1, false);
+ memcpy(dst_sid_str->data, string2str(found->sid_str), string_strlen(found->sid_str) + 1);
+ dst_sid_str->used = string_strlen(found->sid_str) + 1;
+ }
+ else
+ txt_utf8_empty(dst_sid_str);
+
+ return true;
+ }
+
+ txt_utf8_empty(dst_account);
+ txt_utf8_empty(dst_domain);
+ txt_utf8_empty(dst_sid_str);
+ return false;
+}
+
+bool cached_sid_to_buffer_append(PSID sid, BUFFER *dst, const char *prefix) {
+ SID_VALUE *found = lookup_or_convert_user_id_to_name_lookup(sid);
+ size_t added = 0;
+
+ if(found) {
+ if (found->full) {
+ if (prefix && *prefix)
+ buffer_strcat(dst, prefix);
+
+ buffer_fast_strcat(dst, string2str(found->full), string_strlen(found->full));
+ added++;
+ }
+ if (found->sid_str) {
+ if (prefix && *prefix)
+ buffer_strcat(dst, prefix);
+
+ buffer_fast_strcat(dst, string2str(found->sid_str), string_strlen(found->sid_str));
+ added++;
+ }
+ }
+
+ return added > 0;
+}
+
+STRING *cached_sid_fullname_or_sid_str(PSID sid) {
+ SID_VALUE *found = lookup_or_convert_user_id_to_name_lookup(sid);
+ if(found) {
+ if(found->full) return string_dup(found->full);
+ return string_dup(found->sid_str);
+ }
+ return NULL;
+}
+
+#endif \ No newline at end of file
diff --git a/src/libnetdata/os/system-maps/cached-sid-username.h b/src/libnetdata/os/system-maps/cached-sid-username.h
new file mode 100644
index 000000000..4077cad11
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cached-sid-username.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CACHED_SID_USERNAME_H
+#define NETDATA_CACHED_SID_USERNAME_H
+
+#include "../../libnetdata.h"
+
+#if defined(OS_WINDOWS)
+#include "../../string/utf8.h"
+
+bool cached_sid_to_account_domain_sidstr(void *sid, TXT_UTF8 *dst_account, TXT_UTF8 *dst_domain, TXT_UTF8 *dst_sid_str);
+bool cached_sid_to_buffer_append(void *sid, BUFFER *dst, const char *prefix);
+void cached_sid_username_init(void);
+STRING *cached_sid_fullname_or_sid_str(void *sid);
+#endif
+
+#endif //NETDATA_CACHED_SID_USERNAME_H
diff --git a/src/libnetdata/os/system-maps/cached-uid-username.c b/src/libnetdata/os/system-maps/cached-uid-username.c
new file mode 100644
index 000000000..35d93f2f0
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cached-uid-username.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "cached-uid-username.h"
+
+// --------------------------------------------------------------------------------------------------------------------
+// hashtable for caching uid to username mappings
+// key is the uid, value is username (STRING)
+
+#define SIMPLE_HASHTABLE_KEY_TYPE uid_t
+#define SIMPLE_HASHTABLE_VALUE_TYPE CACHED_USERNAME
+#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION cached_username_to_uid_ptr
+#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION compar_uid_ptr
+#define SIMPLE_HASHTABLE_NAME _USERNAMES_CACHE
+#include "libnetdata/simple_hashtable/simple_hashtable.h"
+
+static struct {
+ bool initialized;
+ SPINLOCK spinlock;
+ SIMPLE_HASHTABLE_USERNAMES_CACHE ht;
+} user_cache = {
+ .initialized = false,
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .ht = { 0 },
+};
+
+static uid_t *cached_username_to_uid_ptr(CACHED_USERNAME *cu) {
+ return &cu->uid;
+}
+
+static bool compar_uid_ptr(uid_t *a, uid_t *b) {
+ return *a == *b;
+}
+
+void cached_username_populate_by_uid(uid_t uid, const char *username, uint32_t version) {
+ internal_fatal(!user_cache.initialized, "system-users cache needs to be initialized");
+ if(!username || !*username) return;
+
+ spinlock_lock(&user_cache.spinlock);
+
+ XXH64_hash_t hash = XXH3_64bits(&uid, sizeof(uid));
+ SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_get_slot_USERNAMES_CACHE(&user_cache.ht, hash, &uid, true);
+ CACHED_USERNAME *cu = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(!cu || (cu->version && version > cu->version)) {
+ internal_fatal(cu && cu->uid != uid, "invalid uid matched from cache");
+
+ if(cu)
+ string_freez(cu->username);
+ else
+ cu = callocz(1, sizeof(*cu));
+
+ cu->version = version;
+ cu->uid = uid;
+ cu->username = string_strdupz(username);
+ simple_hashtable_set_slot_USERNAMES_CACHE(&user_cache.ht, sl, hash, cu);
+ }
+
+ spinlock_unlock(&user_cache.spinlock);
+}
+
+CACHED_USERNAME cached_username_get_by_uid(uid_t uid) {
+ internal_fatal(!user_cache.initialized, "system-users cache needs to be initialized");
+
+ spinlock_lock(&user_cache.spinlock);
+
+ XXH64_hash_t hash = XXH3_64bits(&uid, sizeof(uid));
+ SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_get_slot_USERNAMES_CACHE(&user_cache.ht, hash, &uid, true);
+ CACHED_USERNAME *cu = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(!cu) {
+ cu = callocz(1, sizeof(*cu));
+
+ static char tmp[1024]; // we are inside a global spinlock - it is ok to be static
+ struct passwd pw, *result = NULL;
+
+ if (getpwuid_r(uid, &pw, tmp, sizeof(tmp), &result) != 0 || !result || !pw.pw_name || !(*pw.pw_name)) {
+ char name[UINT64_MAX_LENGTH];
+ print_uint64(name, uid);
+ cu->username = string_strdupz(name);
+ }
+ else
+ cu->username = string_strdupz(pw.pw_name);
+
+ cu->uid = uid;
+ simple_hashtable_set_slot_USERNAMES_CACHE(&user_cache.ht, sl, hash, cu);
+ }
+
+ internal_fatal(cu->uid != uid, "invalid uid matched from cache");
+
+ CACHED_USERNAME rc = {
+ .version = cu->version,
+ .uid = cu->uid,
+ .username = string_dup(cu->username),
+ };
+
+ spinlock_unlock(&user_cache.spinlock);
+ return rc;
+}
+
+void cached_username_release(CACHED_USERNAME cu) {
+ string_freez(cu.username);
+}
+
+void cached_usernames_init(void) {
+ if(user_cache.initialized) return;
+ user_cache.initialized = true;
+
+ spinlock_init(&user_cache.spinlock);
+ simple_hashtable_init_USERNAMES_CACHE(&user_cache.ht, 100);
+}
+
+void cached_usernames_destroy(void) {
+ if(!user_cache.initialized) return;
+
+ spinlock_lock(&user_cache.spinlock);
+
+ for(SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_first_read_only_USERNAMES_CACHE(&user_cache.ht);
+ sl;
+ sl = simple_hashtable_next_read_only_USERNAMES_CACHE(&user_cache.ht, sl)) {
+ CACHED_USERNAME *u = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(u) {
+ string_freez(u->username);
+ freez(u);
+ // simple_hashtable_del_slot_USERNAMES_CACHE(&uc.ht, sl);
+ }
+ }
+
+ simple_hashtable_destroy_USERNAMES_CACHE(&user_cache.ht);
+ user_cache.initialized = false;
+
+ spinlock_unlock(&user_cache.spinlock);
+}
+
+void cached_usernames_delete_old_versions(uint32_t version) {
+ if(!user_cache.initialized) return;
+
+ spinlock_lock(&user_cache.spinlock);
+
+ for(SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_first_read_only_USERNAMES_CACHE(&user_cache.ht);
+ sl;
+ sl = simple_hashtable_next_read_only_USERNAMES_CACHE(&user_cache.ht, sl)) {
+ CACHED_USERNAME *cu = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(cu && cu->version && cu->version < version) {
+ string_freez(cu->username);
+ freez(cu);
+ simple_hashtable_del_slot_USERNAMES_CACHE(&user_cache.ht, sl);
+ }
+ }
+
+ spinlock_unlock(&user_cache.spinlock);
+}
diff --git a/src/libnetdata/os/system-maps/cached-uid-username.h b/src/libnetdata/os/system-maps/cached-uid-username.h
new file mode 100644
index 000000000..b7c52c7c4
--- /dev/null
+++ b/src/libnetdata/os/system-maps/cached-uid-username.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CACHED_UID_USERNAME_H
+#define NETDATA_CACHED_UID_USERNAME_H
+
+#include "libnetdata/libnetdata.h"
+
+struct netdata_string;
+
+typedef struct {
+ uint32_t version;
+ uid_t uid;
+ struct netdata_string *username;
+} CACHED_USERNAME;
+
+void cached_username_populate_by_uid(uid_t uid, const char *username, uint32_t version);
+CACHED_USERNAME cached_username_get_by_uid(uid_t uid);
+void cached_username_release(CACHED_USERNAME cu);
+
+void cached_usernames_init(void);
+void cached_usernames_destroy(void);
+void cached_usernames_delete_old_versions(uint32_t version);
+
+#endif //NETDATA_CACHED_UID_USERNAME_H
diff --git a/src/libnetdata/os/system-maps/system-services.h b/src/libnetdata/os/system-maps/system-services.h
new file mode 100644
index 000000000..5d3592bbf
--- /dev/null
+++ b/src/libnetdata/os/system-maps/system-services.h
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_SYSTEM_SERVICES_H
+#define NETDATA_SYSTEM_SERVICES_H
+
+#include "libnetdata/libnetdata.h"
+#include <netdb.h>
+
+// --------------------------------------------------------------------------------------------------------------------
+// hashtable for caching port and protocol to service name mappings
+// key is the combination of protocol and port packed into an uint64_t, value is service name (STRING)
+
+#define SIMPLE_HASHTABLE_VALUE_TYPE STRING
+#define SIMPLE_HASHTABLE_NAME _SERVICENAMES_CACHE
+#include "libnetdata/simple_hashtable/simple_hashtable.h"
+
+typedef struct servicenames_cache {
+ SPINLOCK spinlock;
+ SIMPLE_HASHTABLE_SERVICENAMES_CACHE ht;
+} SERVICENAMES_CACHE;
+
+static inline const char *system_servicenames_ipproto2str(uint16_t ipproto) {
+ return (ipproto == IPPROTO_TCP) ? "tcp" : "udp";
+}
+
+static inline const char *static_portnames(uint16_t port, uint16_t ipproto) {
+ if(port == 19999 && ipproto == IPPROTO_TCP)
+ return "netdata";
+
+ if(port == 8125)
+ return "statsd";
+
+ return NULL;
+}
+
+static inline STRING *system_servicenames_cache_lookup(SERVICENAMES_CACHE *sc, uint16_t port, uint16_t ipproto) {
+ struct {
+ uint16_t ipproto;
+ uint16_t port;
+ } key = {
+ .ipproto = ipproto,
+ .port = port,
+ };
+ XXH64_hash_t hash = XXH3_64bits(&key, sizeof(key));
+
+ spinlock_lock(&sc->spinlock);
+
+ SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_get_slot_SERVICENAMES_CACHE(&sc->ht, hash, &key, true);
+ STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if (!s) {
+ const char *st = static_portnames(port, ipproto);
+ if(st) {
+ s = string_strdupz(st);
+ }
+ else {
+ struct servent *se = getservbyport(htons(port), system_servicenames_ipproto2str(ipproto));
+
+ if (!se || !se->s_name) {
+ char name[50];
+ snprintfz(name, sizeof(name), "%u/%s", port, system_servicenames_ipproto2str(ipproto));
+ s = string_strdupz(name);
+ }
+ else
+ s = string_strdupz(se->s_name);
+ }
+
+ simple_hashtable_set_slot_SERVICENAMES_CACHE(&sc->ht, sl, hash, s);
+ }
+
+ s = string_dup(s);
+ spinlock_unlock(&sc->spinlock);
+ return s;
+}
+
+static inline SERVICENAMES_CACHE *system_servicenames_cache_init(void) {
+ SERVICENAMES_CACHE *sc = callocz(1, sizeof(*sc));
+ spinlock_init(&sc->spinlock);
+ simple_hashtable_init_SERVICENAMES_CACHE(&sc->ht, 100);
+ return sc;
+}
+
+static inline void system_servicenames_cache_destroy(SERVICENAMES_CACHE *sc) {
+ spinlock_lock(&sc->spinlock);
+
+ for (SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_first_read_only_SERVICENAMES_CACHE(&sc->ht);
+ sl;
+ sl = simple_hashtable_next_read_only_SERVICENAMES_CACHE(&sc->ht, sl)) {
+ STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ string_freez(s);
+ }
+
+ simple_hashtable_destroy_SERVICENAMES_CACHE(&sc->ht);
+ freez(sc);
+}
+
+#endif //NETDATA_SYSTEM_SERVICES_H