c574b1b63764ea5e79a40198905e3cd17ba484b0
[protos/libecoli.git] / libecoli / ecoli_strvec.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
3  */
4
5 #define _GNU_SOURCE /* qsort_r */
6 #include <sys/types.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <errno.h>
10
11 #include <ecoli_malloc.h>
12 #include <ecoli_test.h>
13 #include <ecoli_log.h>
14 #include <ecoli_strvec.h>
15
16 EC_LOG_TYPE_REGISTER(strvec);
17
18 struct ec_strvec_elt {
19         unsigned int refcnt;
20         char *str;
21 };
22
23 struct ec_strvec {
24         size_t len;
25         struct ec_strvec_elt **vec;
26 };
27
28 struct ec_strvec *ec_strvec(void)
29 {
30         struct ec_strvec *strvec;
31
32         strvec = ec_calloc(1, sizeof(*strvec));
33         if (strvec == NULL)
34                 return NULL;
35
36         return strvec;
37 }
38
39 int ec_strvec_add(struct ec_strvec *strvec, const char *s)
40 {
41         struct ec_strvec_elt *elt, **new_vec;
42
43         new_vec = ec_realloc(strvec->vec,
44                 sizeof(*strvec->vec) * (strvec->len + 1));
45         if (new_vec == NULL)
46                 return -1;
47
48         strvec->vec = new_vec;
49
50         elt = ec_malloc(sizeof(*elt));
51         if (elt == NULL)
52                 return -1;
53
54         elt->str = ec_strdup(s);
55         if (elt->str == NULL) {
56                 ec_free(elt);
57                 return -1;
58         }
59         elt->refcnt = 1;
60
61         new_vec[strvec->len] = elt;
62         strvec->len++;
63         return 0;
64 }
65
66 struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
67         size_t n)
68 {
69         struct ec_strvec *strvec = NULL;
70         size_t i;
71
72         strvec = ec_strvec();
73         if (strvec == NULL)
74                 goto fail;
75
76         for (i = 0; i < n; i++) {
77                 if (ec_strvec_add(strvec, strarr[i]) < 0)
78                         goto fail;
79         }
80
81         return strvec;
82
83 fail:
84         ec_strvec_free(strvec);
85         return NULL;
86 }
87
88 int ec_strvec_del_last(struct ec_strvec *strvec)
89 {
90         struct ec_strvec_elt *elt;
91
92         if (strvec->len == 0) {
93                 errno = EINVAL;
94                 return -1;
95         }
96
97         elt = strvec->vec[strvec->len - 1];
98         elt->refcnt--;
99         if (elt->refcnt == 0) {
100                 ec_free(elt->str);
101                 ec_free(elt);
102         }
103         strvec->len--;
104         return 0;
105 }
106
107 struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t off,
108         size_t len)
109 {
110         struct ec_strvec *copy = NULL;
111         size_t i, veclen;
112
113         veclen = ec_strvec_len(strvec);
114         if (off + len > veclen)
115                 return NULL;
116
117         copy = ec_strvec();
118         if (copy == NULL)
119                 goto fail;
120
121         if (len == 0)
122                 return copy;
123
124         copy->vec = ec_calloc(len, sizeof(*copy->vec));
125         if (copy->vec == NULL)
126                 goto fail;
127
128         for (i = 0; i < len; i++) {
129                 copy->vec[i] = strvec->vec[i + off];
130                 copy->vec[i]->refcnt++;
131         }
132         copy->len = len;
133
134         return copy;
135
136 fail:
137         ec_strvec_free(copy);
138         return NULL;
139 }
140
141 struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec)
142 {
143         return ec_strvec_ndup(strvec, 0, ec_strvec_len(strvec));
144 }
145
146 void ec_strvec_free(struct ec_strvec *strvec)
147 {
148         struct ec_strvec_elt *elt;
149         size_t i;
150
151         if (strvec == NULL)
152                 return;
153
154         for (i = 0; i < ec_strvec_len(strvec); i++) {
155                 elt = strvec->vec[i];
156                 elt->refcnt--;
157                 if (elt->refcnt == 0) {
158                         ec_free(elt->str);
159                         ec_free(elt);
160                 }
161         }
162
163         ec_free(strvec->vec);
164         ec_free(strvec);
165 }
166
167 size_t ec_strvec_len(const struct ec_strvec *strvec)
168 {
169         return strvec->len;
170 }
171
172 const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx)
173 {
174         if (strvec == NULL || idx >= strvec->len)
175                 return NULL;
176
177         return strvec->vec[idx]->str;
178 }
179
180 int ec_strvec_cmp(const struct ec_strvec *strvec1,
181                 const struct ec_strvec *strvec2)
182 {
183         size_t i;
184
185         if (ec_strvec_len(strvec1) != ec_strvec_len(strvec2))
186                 return -1;
187
188         for (i = 0; i < ec_strvec_len(strvec1); i++) {
189                 if (strcmp(ec_strvec_val(strvec1, i),
190                                 ec_strvec_val(strvec2, i)))
191                         return -1;
192         }
193
194         return 0;
195 }
196
197 static int
198 cmp_vec_elt(const void *p1, const void *p2, void *arg)
199 {
200         int (*str_cmp)(const char *s1, const char *s2) = arg;
201         const struct ec_strvec_elt * const *e1 = p1, * const *e2 = p2;
202
203         return str_cmp((*e1)->str, (*e2)->str);
204 }
205
206 void ec_strvec_sort(struct ec_strvec *strvec,
207         int (*str_cmp)(const char *s1, const char *s2))
208 {
209         if (str_cmp == NULL)
210                 str_cmp = strcmp;
211         qsort_r(strvec->vec, ec_strvec_len(strvec),
212                 sizeof(*strvec->vec), cmp_vec_elt, str_cmp);
213 }
214
215 void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec)
216 {
217         size_t i;
218
219         if (strvec == NULL) {
220                 fprintf(out, "none\n");
221                 return;
222         }
223
224         fprintf(out, "strvec (len=%zu) [", strvec->len);
225         for (i = 0; i < ec_strvec_len(strvec); i++) {
226                 if (i == 0)
227                         fprintf(out, "%s", strvec->vec[i]->str);
228                 else
229                         fprintf(out, ", %s", strvec->vec[i]->str);
230         }
231         fprintf(out, "]\n");
232
233 }
234
235 /* LCOV_EXCL_START */
236 static int ec_strvec_testcase(void)
237 {
238         struct ec_strvec *strvec = NULL;
239         struct ec_strvec *strvec2 = NULL;
240         FILE *f = NULL;
241         char *buf = NULL;
242         size_t buflen = 0;
243         int testres = 0;
244
245         strvec = ec_strvec();
246         if (strvec == NULL) {
247                 EC_TEST_ERR("cannot create strvec\n");
248                 goto fail;
249         }
250         if (ec_strvec_len(strvec) != 0) {
251                 EC_TEST_ERR("bad strvec len (0)\n");
252                 goto fail;
253         }
254         if (ec_strvec_add(strvec, "0") < 0) {
255                 EC_TEST_ERR("cannot add (0) in strvec\n");
256                 goto fail;
257         }
258         if (ec_strvec_len(strvec) != 1) {
259                 EC_TEST_ERR("bad strvec len (1)\n");
260                 goto fail;
261         }
262         if (ec_strvec_add(strvec, "1") < 0) {
263                 EC_TEST_ERR("cannot add (1) in strvec\n");
264                 goto fail;
265         }
266         if (ec_strvec_len(strvec) != 2) {
267                 EC_TEST_ERR("bad strvec len (2)\n");
268                 goto fail;
269         }
270         if (strcmp(ec_strvec_val(strvec, 0), "0")) {
271                 EC_TEST_ERR("invalid element in strvec (0)\n");
272                 goto fail;
273         }
274         if (strcmp(ec_strvec_val(strvec, 1), "1")) {
275                 EC_TEST_ERR("invalid element in strvec (1)\n");
276                 goto fail;
277         }
278         if (ec_strvec_val(strvec, 2) != NULL) {
279                 EC_TEST_ERR("strvec val should be NULL\n");
280                 goto fail;
281         }
282
283         strvec2 = ec_strvec_dup(strvec);
284         if (strvec2 == NULL) {
285                 EC_TEST_ERR("cannot create strvec2\n");
286                 goto fail;
287         }
288         if (ec_strvec_len(strvec2) != 2) {
289                 EC_TEST_ERR("bad strvec2 len (2)\n");
290                 goto fail;
291         }
292         if (strcmp(ec_strvec_val(strvec2, 0), "0")) {
293                 EC_TEST_ERR("invalid element in strvec2 (0)\n");
294                 goto fail;
295         }
296         if (strcmp(ec_strvec_val(strvec2, 1), "1")) {
297                 EC_TEST_ERR("invalid element in strvec2 (1)\n");
298                 goto fail;
299         }
300         if (ec_strvec_val(strvec2, 2) != NULL) {
301                 EC_TEST_ERR("strvec2 val should be NULL\n");
302                 goto fail;
303         }
304         ec_strvec_free(strvec2);
305
306         strvec2 = ec_strvec_ndup(strvec, 0, 0);
307         if (strvec2 == NULL) {
308                 EC_TEST_ERR("cannot create strvec2\n");
309                 goto fail;
310         }
311         if (ec_strvec_len(strvec2) != 0) {
312                 EC_TEST_ERR("bad strvec2 len (0)\n");
313                 goto fail;
314         }
315         if (ec_strvec_val(strvec2, 0) != NULL) {
316                 EC_TEST_ERR("strvec2 val should be NULL\n");
317                 goto fail;
318         }
319         ec_strvec_free(strvec2);
320
321         strvec2 = ec_strvec_ndup(strvec, 1, 1);
322         if (strvec2 == NULL) {
323                 EC_TEST_ERR("cannot create strvec2\n");
324                 goto fail;
325         }
326         if (ec_strvec_len(strvec2) != 1) {
327                 EC_TEST_ERR("bad strvec2 len (1)\n");
328                 goto fail;
329         }
330         if (strcmp(ec_strvec_val(strvec2, 0), "1")) {
331                 EC_TEST_ERR("invalid element in strvec2 (1)\n");
332                 goto fail;
333         }
334         if (ec_strvec_val(strvec2, 1) != NULL) {
335                 EC_TEST_ERR("strvec2 val should be NULL\n");
336                 goto fail;
337         }
338         ec_strvec_free(strvec2);
339
340         strvec2 = ec_strvec_ndup(strvec, 3, 1);
341         if (strvec2 != NULL) {
342                 EC_TEST_ERR("strvec2 should be NULL\n");
343                 goto fail;
344         }
345         ec_strvec_free(strvec2);
346
347         strvec2 = EC_STRVEC("0", "1");
348         if (strvec2 == NULL) {
349                 EC_TEST_ERR("cannot create strvec from array\n");
350                 goto fail;
351         }
352         testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
353                 "strvec and strvec2 should be equal\n");
354         ec_strvec_free(strvec2);
355
356         f = open_memstream(&buf, &buflen);
357         if (f == NULL)
358                 goto fail;
359         ec_strvec_dump(f, strvec);
360         fclose(f);
361         f = NULL;
362         testres |= EC_TEST_CHECK(
363                 strstr(buf, "strvec (len=2) [0, 1]"), "bad dump\n");
364         free(buf);
365         buf = NULL;
366
367         ec_strvec_del_last(strvec);
368         strvec2 = EC_STRVEC("0");
369         if (strvec2 == NULL) {
370                 EC_TEST_ERR("cannot create strvec from array\n");
371                 goto fail;
372         }
373         testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
374                 "strvec and strvec2 should be equal\n");
375         ec_strvec_free(strvec2);
376         strvec2 = NULL;
377
378         f = open_memstream(&buf, &buflen);
379         if (f == NULL)
380                 goto fail;
381         ec_strvec_dump(f, NULL);
382         fclose(f);
383         f = NULL;
384         testres |= EC_TEST_CHECK(
385                 strstr(buf, "none"), "bad dump\n");
386         free(buf);
387         buf = NULL;
388
389         ec_strvec_free(strvec);
390
391         strvec = EC_STRVEC("e", "a", "f", "d", "b", "c");
392         if (strvec == NULL) {
393                 EC_TEST_ERR("cannot create strvec from array\n");
394                 goto fail;
395         }
396         ec_strvec_sort(strvec, NULL);
397         strvec2 = EC_STRVEC("a", "b", "c", "d", "e", "f");
398         if (strvec2 == NULL) {
399                 EC_TEST_ERR("cannot create strvec from array\n");
400                 goto fail;
401         }
402         testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
403                 "strvec and strvec2 should be equal\n");
404         ec_strvec_free(strvec);
405         strvec = NULL;
406         ec_strvec_free(strvec2);
407         strvec2 = NULL;
408
409         return testres;
410
411 fail:
412         if (f != NULL)
413                 fclose(f);
414         ec_strvec_free(strvec);
415         ec_strvec_free(strvec2);
416         free(buf);
417
418         return -1;
419 }
420 /* LCOV_EXCL_STOP */
421
422 static struct ec_test ec_node_str_test = {
423         .name = "strvec",
424         .test = ec_strvec_testcase,
425 };
426
427 EC_TEST_REGISTER(ec_node_str_test);