1 /*
2  * Copyright (c) 2012 Tim Ruehsen
3  * Copyright (c) 2015-2021 Free Software Foundation, Inc.
4  *
5  * This file is part of libwget.
6  *
7  * Libwget is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Libwget is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with libwget.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  *
21  * vector routines
22  *
23  * Changelog
24  * 25.04.2012  Tim Ruehsen  created
25  *
26  */
27 
28 #include <config.h>
29 
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdarg.h>
33 
34 #include <wget.h>
35 #include "private.h"
36 
37 struct wget_vector_st {
38 	wget_vector_compare_fn
39 		*cmp; //!< comparison function for sorting and searching
40 	wget_vector_destructor
41 		*destructor; //!< element destructor function
42 	void
43 		**entry; //!< pointer to array of pointers to elements
44 	int
45 		max,     //!< allocated elements
46 		cur;     //!< number of elements in use
47 	bool
48 		sorted : 1; //!< 1=list is sorted, 0=list is not sorted
49 	float
50 		resize_factor; //!< factor to calculate new vector size
51 };
52 
53 /**
54  * \file
55  * \brief Vector functions
56  * \defgroup libwget-vector Vector functions
57  * @{
58  *
59  * Functions to realize vectors (growable arrays).
60  */
61 
62 /**
63  * \param[in] max Initial number of pre-allocated entries.
64  * \param[in] cmp Comparison function for sorting/finding/sorted insertion or %NULL.
65  * \return New vector instance
66  *
67  * Create a new vector instance, to be free'd after use with wget_vector_free().
68  */
wget_vector_create(int max,wget_vector_compare_fn * cmp)69 wget_vector *wget_vector_create(int max, wget_vector_compare_fn *cmp)
70 {
71 	wget_vector *v = wget_calloc(1, sizeof(wget_vector));
72 
73 	if (!v)
74 		return NULL;
75 
76 	if (!(v->entry = wget_malloc(max * sizeof(void *)))) {
77 		xfree(v);
78 		return NULL;
79 	}
80 
81 	v->max = max;
82 	v->resize_factor = 2;
83 	v->cmp = cmp;
84 	v->destructor = free;
85 
86 	return v;
87 }
88 
89 /**
90  * \param[in] v Vector
91  * \param[in] factor Vector growth factor
92  *
93  * Set the factor for resizing the vector when it is full.
94  *
95  * The new size is 'factor * oldsize'. If the new size is less or equal the old size,
96  * the involved insertion function will return an error and the internal state of
97  * the vector will not change.
98  *
99  * Default is 2.
100  */
wget_vector_set_resize_factor(wget_vector * v,float factor)101 void wget_vector_set_resize_factor(wget_vector *v, float factor)
102 {
103 	if (v)
104 		v->resize_factor = factor;
105 }
106 
insert_element(wget_vector * v,const void * elem,int pos,int replace)107 static int WGET_GCC_NONNULL((2)) insert_element(wget_vector *v, const void *elem, int pos, int replace)
108 {
109 	if (pos < 0 || !v || pos > v->cur)
110 		return WGET_E_INVALID;
111 
112 	if (!replace) {
113 		if (v->max == v->cur) {
114 			int newsize = (int) (v->max * v->resize_factor);
115 
116 			if (newsize <= v->max)
117 				return WGET_E_INVALID;
118 
119 			void **tmp = wget_realloc(v->entry, newsize * sizeof(void *));
120 			if (!tmp)
121 				return WGET_E_MEMORY;
122 
123 			v->entry = tmp;
124 			v->max = newsize;
125 		}
126 
127 		memmove(&v->entry[pos + 1], &v->entry[pos], (v->cur - pos) * sizeof(void *));
128 		v->cur++;
129 	}
130 
131 	v->entry[pos] = (void *) elem;
132 
133 	if (v->cmp) {
134 		if (v->cur == 1) v->sorted = 1;
135 		else if (v->cur > 1 && v->sorted) {
136 			if (pos == 0) {
137 				if (v->cmp(elem, v->entry[1]) > 0) v->sorted = 0;
138 			} else if (pos == v->cur - 1) {
139 				if (v->cmp(elem, v->entry[v->cur - 2]) < 0) v->sorted = 0;
140 			} else {
141 				if (v->cmp(elem, v->entry[pos - 1]) < 0 ||
142 					v->cmp(elem, v->entry[pos + 1]) > 0) {
143 					v->sorted = 0;
144 				}
145 			}
146 		}
147 	}
148 
149 	return pos; // return position of new element
150 }
151 
152 /**
153  * \param[in] v Vector where \p elem is inserted into
154  * \param[in] elem Element to insert into \p v
155  * \param[in] pos Position to insert \p elem at
156  * \return Index of inserted element (>= 0) or WGET_E_*  on error (< 0)
157  *
158  * Insert \p elem of at index \p pos.
159  *
160  * \p elem is *not* cloned, the vector takes 'ownership' of the element.
161  *
162  * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
163  */
wget_vector_insert(wget_vector * v,const void * elem,int pos)164 int wget_vector_insert(wget_vector *v, const void *elem, int pos)
165 {
166 	return insert_element(v, elem, pos, 0);
167 }
168 
169 /**
170  * \param[in] v Vector where \p elem is inserted into
171  * \param[in] elem Element to insert into \p v
172  * \return Index of inserted element (>= 0) or WGET_E_*  on error (< 0)
173  *
174  * Insert \p elem of at a position that keeps the sort order of the elements.
175  * If the vector has no comparison function, \p elem will be inserted as the last element.
176  * If the elements in the vector are not sorted, they will be sorted after returning from this function.
177  *
178  * \p elem is *not* cloned, the vector takes 'ownership' of the element.
179  *
180  * An error is returned if \p v is %NULL.
181  */
wget_vector_insert_sorted(wget_vector * v,const void * elem)182 int wget_vector_insert_sorted(wget_vector *v, const void *elem)
183 {
184 	if (!v)
185 		return WGET_E_INVALID;
186 
187 	if (!v->cmp)
188 		return insert_element(v, elem, v->cur, 0);
189 
190 	if (!v->sorted)
191 		wget_vector_sort(v);
192 
193 	// binary search for element
194 	int l = 0, r = v->cur - 1, m = 0, res = 0;
195 
196 	while (l <= r) {
197 		m = (l + r) / 2;
198 		if ((res = v->cmp(elem, v->entry[m])) > 0) l = m + 1;
199 		else if (res < 0) r = m - 1;
200 		else return insert_element(v, elem, m, 0);
201 	}
202 	if (res > 0) m++;
203 
204 	return insert_element(v, elem, m, 0);
205 }
206 
207 /**
208  * \param[in] v Vector where \p elem is appended to
209  * \param[in] elem Element to append to a \p v
210  * \param[in] size Size of \p elem
211  * \return Index of inserted element (>= 0) or WGET_E_*  on error (< 0)
212  *
213  * Append \p elem of given \p size to vector \p v.
214  *
215  * \p elem is cloned / copied (shallow).
216  *
217  * An error is returned if \p v is %NULL.
218  */
wget_vector_add_memdup(wget_vector * v,const void * elem,size_t size)219 int wget_vector_add_memdup(wget_vector *v, const void *elem, size_t size)
220 {
221 	void *elemp;
222 	int rc;
223 
224 	if (!v)
225 		return WGET_E_INVALID;
226 
227 	if (!(elemp = wget_memdup(elem, size)))
228 		return WGET_E_MEMORY;
229 
230 	if ((rc = insert_element(v, elemp, v->cur, 0)) < 0)
231 		xfree(elemp);
232 
233 	return rc;
234 }
235 
236 /**
237  * \param[in] v Vector where \p elem is appended to
238  * \param[in] elem Element to append to a \p v
239  * \return Index of inserted element (>= 0) or WGET_E_*  on error (< 0)
240  *
241  * Append \p elem to vector \p v.
242  *
243  * \p elem is *not* cloned, the vector takes 'ownership' of the element.
244  *
245  * An error is returned if \p v is %NULL.
246  */
wget_vector_add(wget_vector * v,const void * elem)247 int wget_vector_add(wget_vector *v, const void *elem)
248 {
249 	return v ? insert_element(v, elem, v->cur, 0) : WGET_E_INVALID;
250 }
251 
252 /**
253  * \param[in] v Vector where \p s is appended to
254  * \param[in] fmt Printf-like format string
255  * \param[in] args Arguments for the \p fmt
256  * \return Index of appended element (>= 0) or WGET_E_* on error (< 0)
257  *
258  * Construct string in a printf-like manner and append it as an element to vector \p v.
259  *
260  * An error is returned if \p v or \p fmt is %NULL.
261  */
wget_vector_add_vprintf(wget_vector * v,const char * fmt,va_list args)262 int wget_vector_add_vprintf(wget_vector *v, const char *fmt, va_list args)
263 {
264 	if (!v || !fmt)
265 		return WGET_E_INVALID;
266 
267 	char *p = wget_vaprintf(fmt, args);
268 	if (!p)
269 		return WGET_E_MEMORY;
270 
271 	return insert_element(v, p, v->cur, 0);
272 }
273 
274 /**
275  * \param[in] v Vector where \p s is appended to
276  * \param[in] fmt Printf-like format string
277  * \param[in] ... Arguments for the \p fmt
278  * \return Index of appended element (>= 0) or WGET_E_* on error (< 0)
279  *
280  * Construct string in a printf-like manner and append it as an element to vector \p v.
281  *
282  * An error is returned if \p v or \p fmt is %NULL.
283  */
wget_vector_add_printf(wget_vector * v,const char * fmt,...)284 int wget_vector_add_printf(wget_vector *v, const char *fmt, ...)
285 {
286 	if (!v || !fmt)
287 		return WGET_E_INVALID;
288 
289 	va_list args;
290 
291 	va_start(args, fmt);
292 	char *p = wget_vaprintf(fmt, args);
293 	va_end(args);
294 
295 	if (!p)
296 		return WGET_E_MEMORY;
297 
298 	return insert_element(v, p, v->cur, 0);
299 }
300 
301 /**
302  * \param[in] v Vector where \p elem is inserted
303  * \param[in] elem Element to insert into \p v
304  * \param[in] pos Position to insert \p elem at
305  * \return Index of inserted element (same as \p pos) (>= 0) or WGET_E_* on error (< 0)
306  *
307  * Replace the element at position \p pos with \p elem.
308  * If the vector has an element destructor function, this is called.
309  * The old element is free'd.
310  *
311  * \p elem is *not* cloned, the vector takes 'ownership' of the element.
312  *
313  * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
314  */
wget_vector_replace(wget_vector * v,const void * elem,int pos)315 int wget_vector_replace(wget_vector *v, const void *elem, int pos)
316 {
317 	if (!v || pos < 0 || pos >= v->cur)
318 		return WGET_E_INVALID;
319 
320 	if (v->destructor)
321 		v->destructor(v->entry[pos]);
322 
323 	return insert_element(v, elem, pos, 1); // replace existing entry
324 }
325 
remove_element(wget_vector * v,int pos,int free_entry)326 static int remove_element(wget_vector *v, int pos, int free_entry)
327 {
328 	if (pos < 0 || !v || pos >= v->cur)
329 		return WGET_E_INVALID;
330 
331 	if (free_entry) {
332 		if (v->destructor)
333 			v->destructor(v->entry[pos]);
334 	}
335 
336 	memmove(&v->entry[pos], &v->entry[pos + 1], (v->cur - pos - 1) * sizeof(void *));
337 	v->cur--;
338 
339 	return pos;
340 }
341 
342 /**
343  * \param[in] v Vector to remove an element from
344  * \param[in] pos Position of element to remove
345  * \return Index of appended element (>= 0) or WGET_E_* on error (< 0)
346  *
347  * Remove the element at position \p pos.
348  * If the vector has an element destructor function, this is called.
349  * The element is free'd.
350  *
351  * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
352  */
wget_vector_remove(wget_vector * v,int pos)353 int wget_vector_remove(wget_vector *v, int pos)
354 {
355 	return remove_element(v, pos, 1);
356 }
357 
358 /**
359  * \param[in] v Vector to remove an element from
360  * \param[in] pos Position of element to remove
361  * \return Index of removed element (same as \p pos) (>= 0) or WGET_E_* on error (< 0)
362  *
363  * Remove the element at position \p pos.
364  * No element destructor function is called, the element is not free'd.
365  *
366  * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
367  */
wget_vector_remove_nofree(wget_vector * v,int pos)368 int wget_vector_remove_nofree(wget_vector *v, int pos)
369 {
370 	return remove_element(v, pos, 0);
371 }
372 
373 /**
374  * \param[in] v Vector to act on
375  * \param[in] old_pos Position to move element from
376  * \param[in] new_pos Position to move element to
377  * \return Index of new position (same as \p new_pos) (>= 0) or WGET_E_* on error (< 0)
378  *
379  * Move the element at position \p old_pos to \p new_pos.
380  *
381  * Other elements may change the position.
382  *
383  * An error is returned if \p v is %NULL or either \p old_pos or
384  * \p new_pos is out of range (< 0 or > # of entries).
385  */
wget_vector_move(wget_vector * v,int old_pos,int new_pos)386 int wget_vector_move(wget_vector *v, int old_pos, int new_pos)
387 {
388 	if (!v) return WGET_E_INVALID;
389 	if (old_pos < 0 || old_pos >= v->cur) return WGET_E_INVALID;
390 	if (new_pos < 0 || new_pos >= v->cur) return WGET_E_INVALID;
391 	if (old_pos == new_pos) return new_pos;
392 
393 	if (v->sorted && v->cmp && v->cmp(v->entry[old_pos], v->entry[new_pos]))
394 		v->sorted = 0;
395 
396 	if (old_pos < new_pos) {
397 		void *tmp = v->entry[old_pos];
398 		memmove(&v->entry[old_pos], &v->entry[old_pos + 1], (new_pos - old_pos) * sizeof(void *));
399 		v->entry[new_pos] = tmp;
400 	} else {
401 		void *tmp = v->entry[old_pos];
402 		memmove(&v->entry[new_pos + 1], &v->entry[new_pos], (old_pos - new_pos) * sizeof(void *));
403 		v->entry[new_pos] = tmp;
404 	}
405 
406 	return new_pos;
407 }
408 
409 /**
410  * \param[in] v Vector to act on
411  * \param[in] pos1 Position of element one
412  * \param[in] pos2 Position of element two
413  * \return Index of second position (same as \p pos2) (>= 0) or WGET_E_*  on error (< 0)
414  *
415  * Swap the two elements at position \p pos1 and \p pos2.
416  *
417  * An error is returned if \p v is %NULL or either \p pos1 or
418  * \p pos2 is out of range (< 0 or > # of entries).
419  */
wget_vector_swap(wget_vector * v,int pos1,int pos2)420 int wget_vector_swap(wget_vector *v, int pos1, int pos2)
421 {
422 	if (!v) return WGET_E_INVALID;
423 	if (pos1 < 0 || pos1 >= v->cur) return WGET_E_INVALID;
424 	if (pos2 < 0 || pos2 >= v->cur) return WGET_E_INVALID;
425 	if (pos1 == pos2) return pos2;
426 
427 	void *tmp = v->entry[pos1];
428 	v->entry[pos1] = v->entry[pos2];
429 	v->entry[pos2] = tmp;
430 
431 	if (v->sorted && v->cmp && v->cmp(v->entry[pos1], v->entry[pos2]))
432 		v->sorted = 0;
433 
434 	return pos2;
435 }
436 
437 /**
438  * \param[in] v Vector to be free'd
439  *
440  * Free the vector \p v and it's contents.
441  *
442  * For each element the destructor function is called and the element free'd thereafter.
443  * Then the vector itself is free'd and set to %NULL.
444  */
wget_vector_free(wget_vector ** v)445 void wget_vector_free(wget_vector **v)
446 {
447 	if (v && *v) {
448 		wget_vector_clear(*v);
449 		xfree((*v)->entry);
450 		xfree(*v);
451 	}
452 }
453 
454 /**
455  * \param[in] v Vector to be cleared
456  *
457  * Free all elements of the vector \p v but not the vector itself.
458  *
459  * For each element the destructor function is called and the element free'd thereafter.
460  * The vector is then empty and can be reused.
461  */
wget_vector_clear(wget_vector * v)462 void wget_vector_clear(wget_vector *v)
463 {
464 	if (v) {
465 		if (v->destructor) {
466 			for (int it = 0; it < v->cur; it++) {
467 				v->destructor(v->entry[it]);
468 				v->entry[it] = NULL;
469 			}
470 		}
471 
472 		v->cur = 0;
473 	}
474 }
475 
476 /**
477  * \param[in] v Vector to be cleared
478  *
479  * Remove all elements of the vector \p v without free'ing them.
480  * The caller is responsible to care for the elements.
481  *
482  * The vector is then empty and can be reused.
483  */
wget_vector_clear_nofree(wget_vector * v)484 void wget_vector_clear_nofree(wget_vector *v)
485 {
486 	if (v) {
487 		for (int it = 0; it < v->cur; it++)
488 			v->entry[it] = NULL;
489 		v->cur = 0;
490 	}
491 }
492 
493 /**
494  * \param[in] v Vector
495  * \return The number of elements in the vector \p v
496  *
497  * Retrieve the number of elements of the vector \p v.
498  * If \p v is %NULL, 0 is returned.
499  */
wget_vector_size(const wget_vector * v)500 int wget_vector_size(const wget_vector *v)
501 {
502 	return v ? v->cur : 0;
503 }
504 
505 /**
506  * \param[in] v Vector
507  * \param[in] pos Position of element to retrieve
508  * \return The element at position \p pos or %NULL on error
509  *
510  * Retrieve the element at position \p pos.
511  *
512  * %NULL is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
513  */
wget_vector_get(const wget_vector * v,int pos)514 void *wget_vector_get(const wget_vector *v, int pos)
515 {
516 	if (pos < 0 || !v || pos >= v->cur)
517 		return NULL;
518 
519 	return v->entry[pos];
520 }
521 
522 /**
523  * \param[in] v Vector
524  * \param[in] browse Function to be called for each element of \p v
525  * \param[in] ctx Context variable use as param to \p browse
526  * \return Return value of the last call to \p browse
527  *
528  * Call function \p browse for each element of vector \p v or until \p browse
529  * returns a value not equal to zero.
530  *
531  * \p browse is called with \p ctx and the pointer to the current element.
532  *
533  * The return value of the last call to \p browse is returned or 0 if \p v is %NULL.
534  */
wget_vector_browse(const wget_vector * v,wget_vector_browse_fn * browse,void * ctx)535 int wget_vector_browse(const wget_vector *v, wget_vector_browse_fn *browse, void *ctx)
536 {
537 	if (v) {
538 		for (int ret, it = 0; it < v->cur; it++)
539 			if ((ret = browse(ctx, v->entry[it])) != 0)
540 				return ret;
541 	}
542 
543 	return 0;
544 }
545 
546 /**
547  * \param[in] v Vector
548  * \param[in] cmp Function to compare elements
549  *
550  * Set the compare function used by wget_vector_sort().
551  */
wget_vector_setcmpfunc(wget_vector * v,wget_vector_compare_fn * cmp)552 void wget_vector_setcmpfunc(wget_vector *v, wget_vector_compare_fn *cmp)
553 {
554 	if (v) {
555 		v->cmp = cmp;
556 
557 		if (v->cur == 1)
558 			v->sorted = 1;
559 		else
560 			v->sorted = 0;
561 	}
562 }
563 
564 /**
565  * \param[in] v Vector
566  * \param[in] destructor Function to be called for element destruction
567  *
568  * Set the destructor function that is called for each element to be removed.
569  * It should not free the element (pointer) itself.
570  */
wget_vector_set_destructor(wget_vector * v,wget_vector_destructor * destructor)571 void wget_vector_set_destructor(wget_vector *v, wget_vector_destructor *destructor)
572 {
573 	if (v)
574 		v->destructor = destructor;
575 }
576 
577 WGET_GCC_NONNULL_ALL
compare_element(const void * p1,const void * p2,void * v)578 static int compare_element(const void *p1, const void *p2, void *v)
579 {
580 	return ((wget_vector *)v)->cmp(*((void **)p1), *((void **)p2));
581 }
582 
583 /**
584  * \param[in] v Vector
585  *
586  * Sort the elements in vector \p v using the compare function.
587  * Do nothing if \p v is %NULL or the compare function is not set.
588  */
wget_vector_sort(wget_vector * v)589 void wget_vector_sort(wget_vector *v)
590 {
591 	if (v && v->cmp) {
592 		qsort_r(v->entry, v->cur, sizeof(void *), compare_element, v);
593 		v->sorted = 1;
594 	}
595 }
596 
597 /**
598  * \param[in] v Vector
599  * \param[in] elem Element to search for
600  * \return Index of the found element, WGET_E_UNKNOWN if not found or WGET_E_INVALID
601  *         if v was %NULL or there was no comparison function set
602  *
603  * Searches for the given element using the compare function of the vector.
604  */
wget_vector_find(const wget_vector * v,const void * elem)605 int wget_vector_find(const wget_vector *v, const void *elem)
606 {
607 	if (!v || !v->cmp)
608 		return WGET_E_INVALID;
609 
610 	if (v->cur == 1) {
611 		if (v->cmp(elem, v->entry[0]) == 0) return 0;
612 	} else if (v->sorted) {
613 		// binary search for element (exact match)
614 		for (int l = 0, r = v->cur - 1; l <= r;) {
615 			int res, m = (l + r) / 2;
616 			if ((res = v->cmp(elem, v->entry[m])) > 0) l = m + 1;
617 			else if (res < 0) r = m - 1;
618 			else return m;
619 		}
620 	} else {
621 		// linear search for element
622 		for (int it = 0; it < v->cur; it++)
623 			if (v->cmp(elem, v->entry[it]) == 0) return it;
624 	}
625 
626 	return WGET_E_UNKNOWN; // not found
627 }
628 
629 /**
630  * \param[in] v Vector
631  * \param[in] elem Element to check for
632  * \return True if element exists, else false
633  *
634  * Checks whether the element \p elem exists or not.
635  */
wget_vector_contains(const wget_vector * v,const void * elem)636 bool wget_vector_contains(const wget_vector *v, const void *elem)
637 {
638 	return wget_vector_find(v, elem) >= 0;
639 }
640 
641 /**
642  * \param[in] v Vector
643  * \param[in] start Index to start search from
644  * \param[in] direction Direction of search
645  * \param[in] find Function to be called for each element
646  * \return Index of the found element, WGET_E_UNKNOWN if not found or WGET_E_INVALID
647  *         if v was %NULL or there was no comparison function set
648  *
649  * Call \p find for each element starting at \p start.
650  * If \p find returns 0 the current index is returned.
651  */
wget_vector_findext(const wget_vector * v,int start,int direction,wget_vector_find_fn * find)652 int wget_vector_findext(const wget_vector *v, int start, int direction, wget_vector_find_fn *find)
653 {
654 	if (!v)
655 		return WGET_E_INVALID;
656 
657 	if (direction) { // down
658 		if (start < v->cur) {
659 			for (int it = start; it >= 0; it--)
660 				if (find(v->entry[it]) == 0)
661 					return it;
662 		}
663 	} else { // up
664 		if (start >= 0) {
665 			for (int it = start; it < v->cur; it++)
666 				if (find(v->entry[it]) == 0)
667 					return it;
668 		}
669 	}
670 
671 	return WGET_E_UNKNOWN;
672 }
673 
674 /**@}*/
675