17c2aad20Sopenharmony_ci// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 27c2aad20Sopenharmony_ci/* Copyright (c) 2021 Facebook */ 37c2aad20Sopenharmony_ci#include <stdint.h> 47c2aad20Sopenharmony_ci#include <stdlib.h> 57c2aad20Sopenharmony_ci#include <stdio.h> 67c2aad20Sopenharmony_ci#include <errno.h> 77c2aad20Sopenharmony_ci#include <linux/err.h> 87c2aad20Sopenharmony_ci#include "hashmap.h" 97c2aad20Sopenharmony_ci#include "libbpf_internal.h" 107c2aad20Sopenharmony_ci#include "strset.h" 117c2aad20Sopenharmony_ci 127c2aad20Sopenharmony_cistruct strset { 137c2aad20Sopenharmony_ci void *strs_data; 147c2aad20Sopenharmony_ci size_t strs_data_len; 157c2aad20Sopenharmony_ci size_t strs_data_cap; 167c2aad20Sopenharmony_ci size_t strs_data_max_len; 177c2aad20Sopenharmony_ci 187c2aad20Sopenharmony_ci /* lookup index for each unique string in strings set */ 197c2aad20Sopenharmony_ci struct hashmap *strs_hash; 207c2aad20Sopenharmony_ci}; 217c2aad20Sopenharmony_ci 227c2aad20Sopenharmony_cistatic size_t strset_hash_fn(long key, void *ctx) 237c2aad20Sopenharmony_ci{ 247c2aad20Sopenharmony_ci const struct strset *s = ctx; 257c2aad20Sopenharmony_ci const char *str = s->strs_data + key; 267c2aad20Sopenharmony_ci 277c2aad20Sopenharmony_ci return str_hash(str); 287c2aad20Sopenharmony_ci} 297c2aad20Sopenharmony_ci 307c2aad20Sopenharmony_cistatic bool strset_equal_fn(long key1, long key2, void *ctx) 317c2aad20Sopenharmony_ci{ 327c2aad20Sopenharmony_ci const struct strset *s = ctx; 337c2aad20Sopenharmony_ci const char *str1 = s->strs_data + key1; 347c2aad20Sopenharmony_ci const char *str2 = s->strs_data + key2; 357c2aad20Sopenharmony_ci 367c2aad20Sopenharmony_ci return strcmp(str1, str2) == 0; 377c2aad20Sopenharmony_ci} 387c2aad20Sopenharmony_ci 397c2aad20Sopenharmony_cistruct strset *strset__new(size_t max_data_sz, const char *init_data, size_t init_data_sz) 407c2aad20Sopenharmony_ci{ 417c2aad20Sopenharmony_ci struct strset *set = calloc(1, sizeof(*set)); 427c2aad20Sopenharmony_ci struct hashmap *hash; 437c2aad20Sopenharmony_ci int err = -ENOMEM; 447c2aad20Sopenharmony_ci 457c2aad20Sopenharmony_ci if (!set) 467c2aad20Sopenharmony_ci return ERR_PTR(-ENOMEM); 477c2aad20Sopenharmony_ci 487c2aad20Sopenharmony_ci hash = hashmap__new(strset_hash_fn, strset_equal_fn, set); 497c2aad20Sopenharmony_ci if (IS_ERR(hash)) 507c2aad20Sopenharmony_ci goto err_out; 517c2aad20Sopenharmony_ci 527c2aad20Sopenharmony_ci set->strs_data_max_len = max_data_sz; 537c2aad20Sopenharmony_ci set->strs_hash = hash; 547c2aad20Sopenharmony_ci 557c2aad20Sopenharmony_ci if (init_data) { 567c2aad20Sopenharmony_ci long off; 577c2aad20Sopenharmony_ci 587c2aad20Sopenharmony_ci set->strs_data = malloc(init_data_sz); 597c2aad20Sopenharmony_ci if (!set->strs_data) 607c2aad20Sopenharmony_ci goto err_out; 617c2aad20Sopenharmony_ci 627c2aad20Sopenharmony_ci memcpy(set->strs_data, init_data, init_data_sz); 637c2aad20Sopenharmony_ci set->strs_data_len = init_data_sz; 647c2aad20Sopenharmony_ci set->strs_data_cap = init_data_sz; 657c2aad20Sopenharmony_ci 667c2aad20Sopenharmony_ci for (off = 0; off < set->strs_data_len; off += strlen(set->strs_data + off) + 1) { 677c2aad20Sopenharmony_ci /* hashmap__add() returns EEXIST if string with the same 687c2aad20Sopenharmony_ci * content already is in the hash map 697c2aad20Sopenharmony_ci */ 707c2aad20Sopenharmony_ci err = hashmap__add(hash, off, off); 717c2aad20Sopenharmony_ci if (err == -EEXIST) 727c2aad20Sopenharmony_ci continue; /* duplicate */ 737c2aad20Sopenharmony_ci if (err) 747c2aad20Sopenharmony_ci goto err_out; 757c2aad20Sopenharmony_ci } 767c2aad20Sopenharmony_ci } 777c2aad20Sopenharmony_ci 787c2aad20Sopenharmony_ci return set; 797c2aad20Sopenharmony_cierr_out: 807c2aad20Sopenharmony_ci strset__free(set); 817c2aad20Sopenharmony_ci return ERR_PTR(err); 827c2aad20Sopenharmony_ci} 837c2aad20Sopenharmony_ci 847c2aad20Sopenharmony_civoid strset__free(struct strset *set) 857c2aad20Sopenharmony_ci{ 867c2aad20Sopenharmony_ci if (IS_ERR_OR_NULL(set)) 877c2aad20Sopenharmony_ci return; 887c2aad20Sopenharmony_ci 897c2aad20Sopenharmony_ci hashmap__free(set->strs_hash); 907c2aad20Sopenharmony_ci free(set->strs_data); 917c2aad20Sopenharmony_ci free(set); 927c2aad20Sopenharmony_ci} 937c2aad20Sopenharmony_ci 947c2aad20Sopenharmony_cisize_t strset__data_size(const struct strset *set) 957c2aad20Sopenharmony_ci{ 967c2aad20Sopenharmony_ci return set->strs_data_len; 977c2aad20Sopenharmony_ci} 987c2aad20Sopenharmony_ci 997c2aad20Sopenharmony_ciconst char *strset__data(const struct strset *set) 1007c2aad20Sopenharmony_ci{ 1017c2aad20Sopenharmony_ci return set->strs_data; 1027c2aad20Sopenharmony_ci} 1037c2aad20Sopenharmony_ci 1047c2aad20Sopenharmony_cistatic void *strset_add_str_mem(struct strset *set, size_t add_sz) 1057c2aad20Sopenharmony_ci{ 1067c2aad20Sopenharmony_ci return libbpf_add_mem(&set->strs_data, &set->strs_data_cap, 1, 1077c2aad20Sopenharmony_ci set->strs_data_len, set->strs_data_max_len, add_sz); 1087c2aad20Sopenharmony_ci} 1097c2aad20Sopenharmony_ci 1107c2aad20Sopenharmony_ci/* Find string offset that corresponds to a given string *s*. 1117c2aad20Sopenharmony_ci * Returns: 1127c2aad20Sopenharmony_ci * - >0 offset into string data, if string is found; 1137c2aad20Sopenharmony_ci * - -ENOENT, if string is not in the string data; 1147c2aad20Sopenharmony_ci * - <0, on any other error. 1157c2aad20Sopenharmony_ci */ 1167c2aad20Sopenharmony_ciint strset__find_str(struct strset *set, const char *s) 1177c2aad20Sopenharmony_ci{ 1187c2aad20Sopenharmony_ci long old_off, new_off, len; 1197c2aad20Sopenharmony_ci void *p; 1207c2aad20Sopenharmony_ci 1217c2aad20Sopenharmony_ci /* see strset__add_str() for why we do this */ 1227c2aad20Sopenharmony_ci len = strlen(s) + 1; 1237c2aad20Sopenharmony_ci p = strset_add_str_mem(set, len); 1247c2aad20Sopenharmony_ci if (!p) 1257c2aad20Sopenharmony_ci return -ENOMEM; 1267c2aad20Sopenharmony_ci 1277c2aad20Sopenharmony_ci new_off = set->strs_data_len; 1287c2aad20Sopenharmony_ci memcpy(p, s, len); 1297c2aad20Sopenharmony_ci 1307c2aad20Sopenharmony_ci if (hashmap__find(set->strs_hash, new_off, &old_off)) 1317c2aad20Sopenharmony_ci return old_off; 1327c2aad20Sopenharmony_ci 1337c2aad20Sopenharmony_ci return -ENOENT; 1347c2aad20Sopenharmony_ci} 1357c2aad20Sopenharmony_ci 1367c2aad20Sopenharmony_ci/* Add a string s to the string data. If the string already exists, return its 1377c2aad20Sopenharmony_ci * offset within string data. 1387c2aad20Sopenharmony_ci * Returns: 1397c2aad20Sopenharmony_ci * - > 0 offset into string data, on success; 1407c2aad20Sopenharmony_ci * - < 0, on error. 1417c2aad20Sopenharmony_ci */ 1427c2aad20Sopenharmony_ciint strset__add_str(struct strset *set, const char *s) 1437c2aad20Sopenharmony_ci{ 1447c2aad20Sopenharmony_ci long old_off, new_off, len; 1457c2aad20Sopenharmony_ci void *p; 1467c2aad20Sopenharmony_ci int err; 1477c2aad20Sopenharmony_ci 1487c2aad20Sopenharmony_ci /* Hashmap keys are always offsets within set->strs_data, so to even 1497c2aad20Sopenharmony_ci * look up some string from the "outside", we need to first append it 1507c2aad20Sopenharmony_ci * at the end, so that it can be addressed with an offset. Luckily, 1517c2aad20Sopenharmony_ci * until set->strs_data_len is incremented, that string is just a piece 1527c2aad20Sopenharmony_ci * of garbage for the rest of the code, so no harm, no foul. On the 1537c2aad20Sopenharmony_ci * other hand, if the string is unique, it's already appended and 1547c2aad20Sopenharmony_ci * ready to be used, only a simple set->strs_data_len increment away. 1557c2aad20Sopenharmony_ci */ 1567c2aad20Sopenharmony_ci len = strlen(s) + 1; 1577c2aad20Sopenharmony_ci p = strset_add_str_mem(set, len); 1587c2aad20Sopenharmony_ci if (!p) 1597c2aad20Sopenharmony_ci return -ENOMEM; 1607c2aad20Sopenharmony_ci 1617c2aad20Sopenharmony_ci new_off = set->strs_data_len; 1627c2aad20Sopenharmony_ci memcpy(p, s, len); 1637c2aad20Sopenharmony_ci 1647c2aad20Sopenharmony_ci /* Now attempt to add the string, but only if the string with the same 1657c2aad20Sopenharmony_ci * contents doesn't exist already (HASHMAP_ADD strategy). If such 1667c2aad20Sopenharmony_ci * string exists, we'll get its offset in old_off (that's old_key). 1677c2aad20Sopenharmony_ci */ 1687c2aad20Sopenharmony_ci err = hashmap__insert(set->strs_hash, new_off, new_off, 1697c2aad20Sopenharmony_ci HASHMAP_ADD, &old_off, NULL); 1707c2aad20Sopenharmony_ci if (err == -EEXIST) 1717c2aad20Sopenharmony_ci return old_off; /* duplicated string, return existing offset */ 1727c2aad20Sopenharmony_ci if (err) 1737c2aad20Sopenharmony_ci return err; 1747c2aad20Sopenharmony_ci 1757c2aad20Sopenharmony_ci set->strs_data_len += len; /* new unique string, adjust data length */ 1767c2aad20Sopenharmony_ci return new_off; 1777c2aad20Sopenharmony_ci} 178