blob: 07631f6d1fd994724d9645fd7e41eae1f316e9a4 [file] [log] [blame]
Michal Vaskoae130f52023-04-20 14:25:16 +02001/**
2 * @file dict.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @author Michal Vasko <mvasko@cesnet.cz>
5 * @brief libyang dictionary for storing strings
6 *
7 * Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
8 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15
16#include "dict.h"
17
18#include <assert.h>
19#include <pthread.h>
20#include <stdint.h>
21#include <stdlib.h>
22#include <string.h>
23
24#include "common.h"
25#include "compat.h"
26#include "log.h"
27
28/* starting size of the dictionary */
29#define LYDICT_MIN_SIZE 1024
30
31/**
32 * @brief Comparison callback for dictionary's hash table
33 *
34 * Implementation of ::lyht_value_equal_cb.
35 */
36static ly_bool
37lydict_val_eq(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data)
38{
39 LY_CHECK_ARG_RET(NULL, val1_p, val2_p, cb_data, 0);
40
41 const char *str1 = ((struct ly_dict_rec *)val1_p)->value;
42 const char *str2 = ((struct ly_dict_rec *)val2_p)->value;
43
44 LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0);
45 LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0);
46
47 if (strncmp(str1, str2, *(size_t *)cb_data) == 0) {
48 return 1;
49 }
50
51 return 0;
52}
53
54void
55lydict_init(struct ly_dict *dict)
56{
57 LY_CHECK_ARG_RET(NULL, dict, );
58
59 dict->hash_tab = lyht_new(LYDICT_MIN_SIZE, sizeof(struct ly_dict_rec), lydict_val_eq, NULL, 1);
60 LY_CHECK_ERR_RET(!dict->hash_tab, LOGINT(NULL), );
61 pthread_mutex_init(&dict->lock, NULL);
62}
63
64void
65lydict_clean(struct ly_dict *dict)
66{
67 struct ly_dict_rec *dict_rec = NULL;
68 struct ly_ht_rec *rec = NULL;
69
70 LY_CHECK_ARG_RET(NULL, dict, );
71
72 for (uint32_t i = 0; i < dict->hash_tab->size; i++) {
73 /* get ith record */
74 rec = (struct ly_ht_rec *)&dict->hash_tab->recs[i * dict->hash_tab->rec_size];
75 if (rec->hits == 1) {
76 /*
77 * this should not happen, all records inserted into
78 * dictionary are supposed to be removed using lydict_remove()
79 * before calling lydict_clean()
80 */
81 dict_rec = (struct ly_dict_rec *)rec->val;
82 LOGWRN(NULL, "String \"%s\" not freed from the dictionary, refcount %d", dict_rec->value, dict_rec->refcount);
83 /* if record wasn't removed before free string allocated for that record */
84#ifdef NDEBUG
85 free(dict_rec->value);
86#endif
87 }
88 }
89
90 /* free table and destroy mutex */
91 lyht_free(dict->hash_tab, NULL);
92 pthread_mutex_destroy(&dict->lock);
93}
94
95static ly_bool
96lydict_resize_val_eq(void *val1_p, void *val2_p, ly_bool mod, void *cb_data)
97{
98 LY_CHECK_ARG_RET(NULL, val1_p, val2_p, 0);
99
100 const char *str1 = ((struct ly_dict_rec *)val1_p)->value;
101 const char *str2 = ((struct ly_dict_rec *)val2_p)->value;
102
103 LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0);
104 LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0);
105
106 if (mod) {
107 /* used when inserting new values */
108 if (strcmp(str1, str2) == 0) {
109 return 1;
110 }
111 } else {
112 /* used when finding the original value again in the resized table */
113 return lydict_val_eq(val1_p, val2_p, mod, cb_data);
114 }
115
116 return 0;
117}
118
119LIBYANG_API_DEF LY_ERR
120lydict_remove(const struct ly_ctx *ctx, const char *value)
121{
122 LY_ERR ret = LY_SUCCESS;
123 size_t len;
124 uint32_t hash;
125 struct ly_dict_rec rec, *match = NULL;
126 char *val_p;
127
128 if (!ctx || !value) {
129 return LY_SUCCESS;
130 }
131
132 LOGDBG(LY_LDGDICT, "removing \"%s\"", value);
133
134 len = strlen(value);
135 hash = lyht_hash(value, len);
136
137 /* create record for lyht_find call */
138 rec.value = (char *)value;
139 rec.refcount = 0;
140
141 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
142 /* set len as data for compare callback */
143 lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len);
144 /* check if value is already inserted */
145 ret = lyht_find(ctx->dict.hash_tab, &rec, hash, (void **)&match);
146
147 if (ret == LY_SUCCESS) {
148 LY_CHECK_ERR_GOTO(!match, LOGINT(ctx), finish);
149
150 /* if value is already in dictionary, decrement reference counter */
151 match->refcount--;
152 if (match->refcount == 0) {
153 /*
154 * remove record
155 * save pointer to stored string before lyht_remove to
156 * free it after it is removed from hash table
157 */
158 val_p = match->value;
159 ret = lyht_remove_with_resize_cb(ctx->dict.hash_tab, &rec, hash, lydict_resize_val_eq);
160 free(val_p);
161 LY_CHECK_ERR_GOTO(ret, LOGINT(ctx), finish);
162 }
163 } else if (ret == LY_ENOTFOUND) {
164 LOGERR(ctx, LY_ENOTFOUND, "Value \"%s\" was not found in the dictionary.", value);
165 } else {
166 LOGINT(ctx);
167 }
168
169finish:
170 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
171 return ret;
172}
173
174LY_ERR
175dict_insert(const struct ly_ctx *ctx, char *value, size_t len, ly_bool zerocopy, const char **str_p)
176{
177 LY_ERR ret = LY_SUCCESS;
178 struct ly_dict_rec *match = NULL, rec;
179 uint32_t hash;
180
181 LOGDBG(LY_LDGDICT, "inserting \"%.*s\"", (int)len, value);
182
183 hash = lyht_hash(value, len);
184 /* set len as data for compare callback */
185 lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len);
186 /* create record for lyht_insert */
187 rec.value = value;
188 rec.refcount = 1;
189
190 ret = lyht_insert_with_resize_cb(ctx->dict.hash_tab, (void *)&rec, hash, lydict_resize_val_eq, (void **)&match);
191 if (ret == LY_EEXIST) {
192 match->refcount++;
193 if (zerocopy) {
194 free(value);
195 }
196 ret = LY_SUCCESS;
197 } else if (ret == LY_SUCCESS) {
198 if (!zerocopy) {
199 /*
200 * allocate string for new record
201 * record is already inserted in hash table
202 */
203 match->value = malloc(sizeof *match->value * (len + 1));
204 LY_CHECK_ERR_RET(!match->value, LOGMEM(ctx), LY_EMEM);
205 if (len) {
206 memcpy(match->value, value, len);
207 }
208 match->value[len] = '\0';
209 }
210 } else {
211 /* lyht_insert returned error */
212 if (zerocopy) {
213 free(value);
214 }
215 return ret;
216 }
217
218 if (str_p) {
219 *str_p = match->value;
220 }
221
222 return ret;
223}
224
225LIBYANG_API_DEF LY_ERR
226lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p)
227{
228 LY_ERR result;
229
230 LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL);
231
232 if (!value) {
233 *str_p = NULL;
234 return LY_SUCCESS;
235 }
236
237 if (!len) {
238 len = strlen(value);
239 }
240
241 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
242 result = dict_insert(ctx, (char *)value, len, 0, str_p);
243 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
244
245 return result;
246}
247
248LIBYANG_API_DEF LY_ERR
249lydict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p)
250{
251 LY_ERR result;
252
253 LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL);
254
255 if (!value) {
256 *str_p = NULL;
257 return LY_SUCCESS;
258 }
259
260 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
261 result = dict_insert(ctx, value, strlen(value), 1, str_p);
262 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
263
264 return result;
265}