blob: 7f89cc0e62748482d0a8cd36cbd26318dc5dca26 [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{
Michal Vaskode17be12023-09-29 14:09:53 +020039 const char *str1, *str2;
40 size_t *len1;
41
Michal Vaskoae130f52023-04-20 14:25:16 +020042 LY_CHECK_ARG_RET(NULL, val1_p, val2_p, cb_data, 0);
43
Michal Vaskode17be12023-09-29 14:09:53 +020044 str1 = ((struct ly_dict_rec *)val1_p)->value;
45 str2 = ((struct ly_dict_rec *)val2_p)->value;
46 len1 = cb_data;
Michal Vaskoae130f52023-04-20 14:25:16 +020047
48 LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0);
49 LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0);
50
Michal Vaskode17be12023-09-29 14:09:53 +020051 if (!strncmp(str1, str2, *len1) && !str2[*len1]) {
Michal Vaskoae130f52023-04-20 14:25:16 +020052 return 1;
53 }
54
55 return 0;
56}
57
58void
59lydict_init(struct ly_dict *dict)
60{
61 LY_CHECK_ARG_RET(NULL, dict, );
62
63 dict->hash_tab = lyht_new(LYDICT_MIN_SIZE, sizeof(struct ly_dict_rec), lydict_val_eq, NULL, 1);
64 LY_CHECK_ERR_RET(!dict->hash_tab, LOGINT(NULL), );
65 pthread_mutex_init(&dict->lock, NULL);
66}
67
68void
69lydict_clean(struct ly_dict *dict)
70{
71 struct ly_dict_rec *dict_rec = NULL;
72 struct ly_ht_rec *rec = NULL;
73
74 LY_CHECK_ARG_RET(NULL, dict, );
75
76 for (uint32_t i = 0; i < dict->hash_tab->size; i++) {
77 /* get ith record */
78 rec = (struct ly_ht_rec *)&dict->hash_tab->recs[i * dict->hash_tab->rec_size];
79 if (rec->hits == 1) {
80 /*
81 * this should not happen, all records inserted into
82 * dictionary are supposed to be removed using lydict_remove()
83 * before calling lydict_clean()
84 */
85 dict_rec = (struct ly_dict_rec *)rec->val;
86 LOGWRN(NULL, "String \"%s\" not freed from the dictionary, refcount %d", dict_rec->value, dict_rec->refcount);
87 /* if record wasn't removed before free string allocated for that record */
88#ifdef NDEBUG
89 free(dict_rec->value);
90#endif
91 }
92 }
93
94 /* free table and destroy mutex */
95 lyht_free(dict->hash_tab, NULL);
96 pthread_mutex_destroy(&dict->lock);
97}
98
99static ly_bool
Michal Vaskoc6a1fe62023-09-29 14:10:33 +0200100lydict_resize_val_eq(void *val1_p, void *val2_p, ly_bool mod, void *UNUSED(cb_data))
Michal Vaskoae130f52023-04-20 14:25:16 +0200101{
Michal Vaskoc6a1fe62023-09-29 14:10:33 +0200102 const char *str1, *str2;
103
Michal Vaskoae130f52023-04-20 14:25:16 +0200104 LY_CHECK_ARG_RET(NULL, val1_p, val2_p, 0);
105
Michal Vaskoc6a1fe62023-09-29 14:10:33 +0200106 str1 = ((struct ly_dict_rec *)val1_p)->value;
107 str2 = ((struct ly_dict_rec *)val2_p)->value;
Michal Vaskoae130f52023-04-20 14:25:16 +0200108
109 LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0);
110 LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0);
111
112 if (mod) {
113 /* used when inserting new values */
114 if (strcmp(str1, str2) == 0) {
115 return 1;
116 }
117 } else {
118 /* used when finding the original value again in the resized table */
Michal Vaskoc6a1fe62023-09-29 14:10:33 +0200119 if (str1 == str2) {
120 return 1;
121 }
Michal Vaskoae130f52023-04-20 14:25:16 +0200122 }
123
124 return 0;
125}
126
127LIBYANG_API_DEF LY_ERR
128lydict_remove(const struct ly_ctx *ctx, const char *value)
129{
130 LY_ERR ret = LY_SUCCESS;
131 size_t len;
132 uint32_t hash;
133 struct ly_dict_rec rec, *match = NULL;
134 char *val_p;
135
136 if (!ctx || !value) {
137 return LY_SUCCESS;
138 }
139
140 LOGDBG(LY_LDGDICT, "removing \"%s\"", value);
141
142 len = strlen(value);
143 hash = lyht_hash(value, len);
144
145 /* create record for lyht_find call */
146 rec.value = (char *)value;
147 rec.refcount = 0;
148
149 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
150 /* set len as data for compare callback */
151 lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len);
152 /* check if value is already inserted */
153 ret = lyht_find(ctx->dict.hash_tab, &rec, hash, (void **)&match);
154
155 if (ret == LY_SUCCESS) {
156 LY_CHECK_ERR_GOTO(!match, LOGINT(ctx), finish);
157
158 /* if value is already in dictionary, decrement reference counter */
159 match->refcount--;
160 if (match->refcount == 0) {
161 /*
162 * remove record
163 * save pointer to stored string before lyht_remove to
164 * free it after it is removed from hash table
165 */
166 val_p = match->value;
167 ret = lyht_remove_with_resize_cb(ctx->dict.hash_tab, &rec, hash, lydict_resize_val_eq);
168 free(val_p);
169 LY_CHECK_ERR_GOTO(ret, LOGINT(ctx), finish);
170 }
171 } else if (ret == LY_ENOTFOUND) {
172 LOGERR(ctx, LY_ENOTFOUND, "Value \"%s\" was not found in the dictionary.", value);
173 } else {
174 LOGINT(ctx);
175 }
176
177finish:
178 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
179 return ret;
180}
181
182LY_ERR
183dict_insert(const struct ly_ctx *ctx, char *value, size_t len, ly_bool zerocopy, const char **str_p)
184{
185 LY_ERR ret = LY_SUCCESS;
186 struct ly_dict_rec *match = NULL, rec;
187 uint32_t hash;
188
189 LOGDBG(LY_LDGDICT, "inserting \"%.*s\"", (int)len, value);
190
191 hash = lyht_hash(value, len);
192 /* set len as data for compare callback */
193 lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len);
194 /* create record for lyht_insert */
195 rec.value = value;
196 rec.refcount = 1;
197
198 ret = lyht_insert_with_resize_cb(ctx->dict.hash_tab, (void *)&rec, hash, lydict_resize_val_eq, (void **)&match);
199 if (ret == LY_EEXIST) {
200 match->refcount++;
201 if (zerocopy) {
202 free(value);
203 }
204 ret = LY_SUCCESS;
205 } else if (ret == LY_SUCCESS) {
206 if (!zerocopy) {
207 /*
208 * allocate string for new record
209 * record is already inserted in hash table
210 */
211 match->value = malloc(sizeof *match->value * (len + 1));
212 LY_CHECK_ERR_RET(!match->value, LOGMEM(ctx), LY_EMEM);
213 if (len) {
214 memcpy(match->value, value, len);
215 }
216 match->value[len] = '\0';
217 }
218 } else {
219 /* lyht_insert returned error */
220 if (zerocopy) {
221 free(value);
222 }
223 return ret;
224 }
225
226 if (str_p) {
227 *str_p = match->value;
228 }
229
230 return ret;
231}
232
233LIBYANG_API_DEF LY_ERR
234lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p)
235{
236 LY_ERR result;
237
238 LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL);
239
240 if (!value) {
241 *str_p = NULL;
242 return LY_SUCCESS;
243 }
244
245 if (!len) {
246 len = strlen(value);
247 }
248
249 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
250 result = dict_insert(ctx, (char *)value, len, 0, str_p);
251 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
252
253 return result;
254}
255
256LIBYANG_API_DEF LY_ERR
257lydict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p)
258{
259 LY_ERR result;
260
261 LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL);
262
263 if (!value) {
264 *str_p = NULL;
265 return LY_SUCCESS;
266 }
267
268 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
269 result = dict_insert(ctx, value, strlen(value), 1, str_p);
270 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
271
272 return result;
273}