1 /**
2 * gholder.c -- data structure to hold raw data
3 * ______ ___
4 * / ____/___ / | _____________ __________
5 * / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
6 * / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
7 * \____/\____/_/ |_\___/\___/\___/____/____/
8 *
9 * The MIT License (MIT)
10 * Copyright (c) 2009-2020 Gerardo Orellana <hello @ goaccess.io>
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a copy
13 * of this software and associated documentation files (the "Software"), to deal
14 * in the Software without restriction, including without limitation the rights
15 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 * copies of the Software, and to permit persons to whom the Software is
17 * furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be included in all
20 * copies or substantial portions of the Software.
21 *
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 * SOFTWARE.
29 */
30
31 #include <pthread.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/socket.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38
39 #include "gholder.h"
40
41 #include "error.h"
42 #include "gdns.h"
43 #include "gkhash.h"
44 #include "util.h"
45 #include "xmalloc.h"
46
47 #ifdef HAVE_GEOLOCATION
48 #include "geoip1.h"
49 #endif
50
51 typedef struct GPanel_ {
52 GModule module;
53 void (*insert) (GRawDataItem item, GHolder * h, datatype type, const struct GPanel_ *);
54 void (*holder_callback) (GHolder * h);
55 } GPanel;
56
57
58 /* *INDENT-OFF* */
59 static void add_data_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel);
60 static void add_host_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel);
61 static void add_root_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel);
62 static void add_host_child_to_holder (GHolder * h);
63
64 static GPanel paneling[] = {
65 {VISITORS , add_data_to_holder , NULL},
66 {REQUESTS , add_data_to_holder , NULL},
67 {REQUESTS_STATIC , add_data_to_holder , NULL},
68 {NOT_FOUND , add_data_to_holder , NULL},
69 {HOSTS , add_host_to_holder , add_host_child_to_holder} ,
70 {OS , add_root_to_holder , NULL},
71 {BROWSERS , add_root_to_holder , NULL},
72 {VISIT_TIMES , add_data_to_holder , NULL},
73 {VIRTUAL_HOSTS , add_data_to_holder , NULL},
74 {REFERRERS , add_data_to_holder , NULL},
75 {REFERRING_SITES , add_data_to_holder , NULL},
76 {KEYPHRASES , add_data_to_holder , NULL},
77 {STATUS_CODES , add_root_to_holder , NULL},
78 {REMOTE_USER , add_data_to_holder , NULL},
79 {CACHE_STATUS , add_data_to_holder , NULL},
80 #ifdef HAVE_GEOLOCATION
81 {GEO_LOCATION , add_root_to_holder , NULL},
82 #endif
83 {MIME_TYPE , add_root_to_holder, NULL} ,
84 {TLS_TYPE , add_root_to_holder, NULL} ,
85 };
86 /* *INDENT-ON* */
87
88
89 /* Get a panel from the GPanel structure given a module.
90 *
91 * On error, or if not found, NULL is returned.
92 * On success, the panel value is returned. */
93 static GPanel *
panel_lookup(GModule module)94 panel_lookup (GModule module) {
95 int i, num_panels = ARRAY_SIZE (paneling);
96
97 for (i = 0; i < num_panels; i++) {
98 if (paneling[i].module == module)
99 return &paneling[i];
100 }
101 return NULL;
102 }
103
104 /* Allocate memory for a new GHolder instance.
105 *
106 * On success, the newly allocated GHolder is returned . */
107 GHolder *
new_gholder(uint32_t size)108 new_gholder (uint32_t size) {
109 GHolder *holder = xmalloc (size * sizeof (GHolder));
110 memset (holder, 0, size * sizeof *holder);
111
112 return holder;
113 }
114
115 /* Allocate memory for a new GHolderItem instance.
116 *
117 * On success, the newly allocated GHolderItem is returned . */
118 static GHolderItem *
new_gholder_item(uint32_t size)119 new_gholder_item (uint32_t size) {
120 GHolderItem *item = xcalloc (size, sizeof (GHolderItem));
121
122 return item;
123 }
124
125 /* Allocate memory for a new double linked-list GSubList instance.
126 *
127 * On success, the newly allocated GSubList is returned . */
128 static GSubList *
new_gsublist(void)129 new_gsublist (void) {
130 GSubList *sub_list = xmalloc (sizeof (GSubList));
131 sub_list->head = NULL;
132 sub_list->tail = NULL;
133 sub_list->size = 0;
134
135 return sub_list;
136 }
137
138 /* Allocate memory for a new double linked-list GSubItem node.
139 *
140 * On success, the newly allocated GSubItem is returned . */
141 static GSubItem *
new_gsubitem(GModule module,GMetrics * nmetrics)142 new_gsubitem (GModule module, GMetrics * nmetrics) {
143 GSubItem *sub_item = xmalloc (sizeof (GSubItem));
144
145 sub_item->metrics = nmetrics;
146 sub_item->module = module;
147 sub_item->prev = NULL;
148 sub_item->next = NULL;
149
150 return sub_item;
151 }
152
153 /* Add an item to the end of a given sub list. */
154 static void
add_sub_item_back(GSubList * sub_list,GModule module,GMetrics * nmetrics)155 add_sub_item_back (GSubList * sub_list, GModule module, GMetrics * nmetrics) {
156 GSubItem *sub_item = new_gsubitem (module, nmetrics);
157 if (sub_list->tail) {
158 sub_list->tail->next = sub_item;
159 sub_item->prev = sub_list->tail;
160 sub_list->tail = sub_item;
161 } else {
162 sub_list->head = sub_item;
163 sub_list->tail = sub_item;
164 }
165 sub_list->size++;
166 }
167
168 /* Delete the entire given sub list. */
169 static void
delete_sub_list(GSubList * sub_list)170 delete_sub_list (GSubList * sub_list) {
171 GSubItem *item = NULL;
172 GSubItem *next = NULL;
173
174 if (sub_list->size == 0)
175 goto clear;
176
177 for (item = sub_list->head; item; item = next) {
178 next = item->next;
179 free (item->metrics->data);
180 free (item->metrics);
181 free (item);
182 }
183 clear:
184 sub_list->head = NULL;
185 sub_list->size = 0;
186 free (sub_list);
187 }
188
189 /* Free malloc'd holder fields. */
190 static void
free_holder_data(GHolderItem item)191 free_holder_data (GHolderItem item) {
192 if (item.sub_list != NULL)
193 delete_sub_list (item.sub_list);
194 free_gmetrics (item.metrics);
195 }
196
197 /* Free all memory allocated in holder for a given module. */
198 void
free_holder_by_module(GHolder ** holder,GModule module)199 free_holder_by_module (GHolder ** holder, GModule module) {
200 int j;
201
202 if ((*holder) == NULL)
203 return;
204
205 for (j = 0; j < (*holder)[module].idx; j++) {
206 free_holder_data ((*holder)[module].items[j]);
207 }
208 free ((*holder)[module].items);
209
210 (*holder)[module].holder_size = 0;
211 (*holder)[module].idx = 0;
212 (*holder)[module].sub_items_size = 0;
213 }
214
215 /* Free all memory allocated in holder for all modules. */
216 void
free_holder(GHolder ** holder)217 free_holder (GHolder ** holder) {
218 GModule module;
219 int j;
220 size_t idx = 0;
221
222 if ((*holder) == NULL)
223 return;
224
225 FOREACH_MODULE (idx, module_list) {
226 module = module_list[idx];
227
228 for (j = 0; j < (*holder)[module].idx; j++) {
229 free_holder_data ((*holder)[module].items[j]);
230 }
231 free ((*holder)[module].items);
232 }
233 free (*holder);
234 (*holder) = NULL;
235 }
236
237 /* Iterate over holder and get the key index.
238 *
239 * If the key does not exist, -1 is returned.
240 * On success, the key in holder is returned . */
241 static int
get_item_idx_in_holder(GHolder * holder,const char * k)242 get_item_idx_in_holder (GHolder * holder, const char *k) {
243 int i;
244 if (holder == NULL)
245 return KEY_NOT_FOUND;
246 if (holder->idx == 0)
247 return KEY_NOT_FOUND;
248 if (k == NULL || *k == '\0')
249 return KEY_NOT_FOUND;
250
251 for (i = 0; i < holder->idx; i++) {
252 if (strcmp (k, holder->items[i].metrics->data) == 0)
253 return i;
254 }
255
256 return KEY_NOT_FOUND;
257 }
258
259 /* Copy linked-list items to an array, sort, and move them back to the
260 * list. Should be faster than sorting the list */
261 static void
sort_sub_list(GHolder * h,GSort sort)262 sort_sub_list (GHolder * h, GSort sort) {
263 GHolderItem *arr;
264 GSubItem *iter;
265 GSubList *sub_list;
266 int i, j, k;
267
268 /* iterate over root-level nodes */
269 for (i = 0; i < h->idx; i++) {
270 sub_list = h->items[i].sub_list;
271 if (sub_list == NULL)
272 continue;
273
274 arr = new_gholder_item (sub_list->size);
275
276 /* copy items from the linked-list into an array */
277 for (j = 0, iter = sub_list->head; iter; iter = iter->next, j++) {
278 arr[j].metrics = new_gmetrics ();
279
280 arr[j].metrics->bw.nbw = iter->metrics->bw.nbw;
281 arr[j].metrics->data = xstrdup (iter->metrics->data);
282 arr[j].metrics->hits = iter->metrics->hits;
283 arr[j].metrics->id = iter->metrics->id;
284 arr[j].metrics->visitors = iter->metrics->visitors;
285 if (conf.serve_usecs) {
286 arr[j].metrics->avgts.nts = iter->metrics->avgts.nts;
287 arr[j].metrics->cumts.nts = iter->metrics->cumts.nts;
288 arr[j].metrics->maxts.nts = iter->metrics->maxts.nts;
289 }
290 }
291 sort_holder_items (arr, j, sort);
292 delete_sub_list (sub_list);
293
294 sub_list = new_gsublist ();
295 for (k = 0; k < j; k++) {
296 if (k > 0)
297 sub_list = h->items[i].sub_list;
298
299 add_sub_item_back (sub_list, h->module, arr[k].metrics);
300 h->items[i].sub_list = sub_list;
301 sub_list = NULL;
302 }
303
304 free (arr);
305 if (sub_list) {
306 delete_sub_list (sub_list);
307 sub_list = NULL;
308 }
309 }
310 }
311
312 /* Set the data metric field for the host panel.
313 *
314 * On success, the data field/metric is set. */
315 static int
set_host_child_metrics(char * data,uint8_t id,GMetrics ** nmetrics)316 set_host_child_metrics (char *data, uint8_t id, GMetrics ** nmetrics) {
317 GMetrics *metrics;
318
319 metrics = new_gmetrics ();
320 metrics->data = xstrdup (data);
321 metrics->id = id;
322 *nmetrics = metrics;
323
324 return 0;
325 }
326
327 /* Set host panel data, including sub items.
328 *
329 * On success, the host panel data is set. */
330 static void
set_host_sub_list(GHolder * h,GSubList * sub_list)331 set_host_sub_list (GHolder * h, GSubList * sub_list) {
332 GMetrics *nmetrics;
333 #ifdef HAVE_GEOLOCATION
334 char city[CITY_LEN] = "";
335 char continent[CONTINENT_LEN] = "";
336 char country[COUNTRY_LEN] = "";
337 #endif
338
339 char *host = h->items[h->idx].metrics->data, *hostname = NULL;
340 #ifdef HAVE_GEOLOCATION
341 /* add geolocation child nodes */
342 set_geolocation (host, continent, country, city);
343
344 /* country */
345 if (country[0] != '\0') {
346 set_host_child_metrics (country, MTRC_ID_COUNTRY, &nmetrics);
347 add_sub_item_back (sub_list, h->module, nmetrics);
348 h->items[h->idx].sub_list = sub_list;
349 h->sub_items_size++;
350
351 /* flag only */
352 conf.has_geocountry = 1;
353 }
354
355 /* city */
356 if (city[0] != '\0') {
357 set_host_child_metrics (city, MTRC_ID_CITY, &nmetrics);
358 add_sub_item_back (sub_list, h->module, nmetrics);
359 h->items[h->idx].sub_list = sub_list;
360 h->sub_items_size++;
361
362 /* flag only */
363 conf.has_geocity = 1;
364 }
365 #endif
366
367 /* hostname */
368 if (conf.enable_html_resolver && conf.output_stdout && !conf.no_ip_validation) {
369 hostname = reverse_ip (host);
370 set_host_child_metrics (hostname, MTRC_ID_HOSTNAME, &nmetrics);
371 add_sub_item_back (sub_list, h->module, nmetrics);
372 h->items[h->idx].sub_list = sub_list;
373 h->sub_items_size++;
374 free (hostname);
375 }
376 }
377
378 /* Set host panel data, including sub items.
379 *
380 * On success, the host panel data is set. */
381 static void
add_host_child_to_holder(GHolder * h)382 add_host_child_to_holder (GHolder * h) {
383 GMetrics *nmetrics;
384 GSubList *sub_list = new_gsublist ();
385
386 char *ip = h->items[h->idx].metrics->data;
387 char *hostname = NULL;
388 int n = h->sub_items_size;
389
390 /* add child nodes */
391 set_host_sub_list (h, sub_list);
392
393 pthread_mutex_lock (&gdns_thread.mutex);
394 hostname = ht_get_hostname (ip);
395 pthread_mutex_unlock (&gdns_thread.mutex);
396
397 /* determine if we have the IP's hostname */
398 if (!hostname) {
399 dns_resolver (ip);
400 } else if (hostname) {
401 set_host_child_metrics (hostname, MTRC_ID_HOSTNAME, &nmetrics);
402 add_sub_item_back (sub_list, h->module, nmetrics);
403 h->items[h->idx].sub_list = sub_list;
404 h->sub_items_size++;
405 free (hostname);
406 }
407
408 /* did not add any items */
409 if (n == h->sub_items_size)
410 free (sub_list);
411 }
412
413 /* Given a GRawDataType, set the data and hits value.
414 *
415 * On error, no values are set and 1 is returned.
416 * On success, the data and hits values are set and 0 is returned. */
417 static int
map_data(GModule module,GRawDataItem item,datatype type,char ** data,uint32_t * hits)418 map_data (GModule module, GRawDataItem item, datatype type, char **data, uint32_t * hits) {
419 switch (type) {
420 case U32:
421 if (!(*data = ht_get_datamap (module, item.nkey)))
422 return 1;
423 *hits = item.hits;
424 break;
425 case STR:
426 if (!(*hits = ht_get_hits (module, item.nkey)))
427 return 1;
428 *data = xstrdup (item.data);
429 break;
430 }
431 return 0;
432 }
433
434 /* Given a data item, store it into a holder structure. */
435 static void
set_single_metrics(GRawDataItem item,GHolder * h,char * data,uint32_t hits)436 set_single_metrics (GRawDataItem item, GHolder * h, char *data, uint32_t hits) {
437 uint32_t visitors = 0;
438 uint64_t bw = 0, cumts = 0, maxts = 0;
439
440 bw = ht_get_bw (h->module, item.nkey);
441 cumts = ht_get_cumts (h->module, item.nkey);
442 maxts = ht_get_maxts (h->module, item.nkey);
443 visitors = ht_get_visitors (h->module, item.nkey);
444
445 h->items[h->idx].metrics = new_gmetrics ();
446 h->items[h->idx].metrics->hits = hits;
447 h->items[h->idx].metrics->data = data;
448 h->items[h->idx].metrics->visitors = visitors;
449 h->items[h->idx].metrics->bw.nbw = bw;
450 h->items[h->idx].metrics->avgts.nts = cumts / hits;
451 h->items[h->idx].metrics->cumts.nts = cumts;
452 h->items[h->idx].metrics->maxts.nts = maxts;
453
454 if (bw && !conf.bandwidth)
455 conf.bandwidth = 1;
456 if (cumts && !conf.serve_usecs)
457 conf.serve_usecs = 1;
458
459 if (conf.append_method) {
460 h->items[h->idx].metrics->method = ht_get_method (h->module, item.nkey);
461 }
462
463 if (conf.append_protocol) {
464 h->items[h->idx].metrics->protocol = ht_get_protocol (h->module, item.nkey);
465 }
466 }
467
468 /* Set all panel data. This will set data for panels that do not
469 * contain sub items. A function pointer is used for post data set. */
470 static void
add_data_to_holder(GRawDataItem item,GHolder * h,datatype type,const GPanel * panel)471 add_data_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel) {
472 char *data = NULL;
473 uint32_t hits = 0;
474
475 if (map_data (h->module, item, type, &data, &hits) == 1)
476 return;
477
478 set_single_metrics (item, h, data, hits);
479 if (panel->holder_callback)
480 panel->holder_callback (h);
481
482 h->idx++;
483 }
484
485 /* A wrapper to set a host item */
486 static void
set_host(GRawDataItem item,GHolder * h,const GPanel * panel,char * data,uint32_t hits)487 set_host (GRawDataItem item, GHolder * h, const GPanel * panel, char *data, uint32_t hits) {
488 set_single_metrics (item, h, xstrdup (data), hits);
489 if (panel->holder_callback)
490 panel->holder_callback (h);
491 h->idx++;
492 }
493
494 /* Set all panel data. This will set data for panels that do not
495 * contain sub items. A function pointer is used for post data set. */
496 static void
add_host_to_holder(GRawDataItem item,GHolder * h,datatype type,const GPanel * panel)497 add_host_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel) {
498 char buf4[INET_ADDRSTRLEN];
499 char buf6[INET6_ADDRSTRLEN];
500 char *data = NULL;
501 uint32_t hits = 0;
502 unsigned i;
503
504 struct in6_addr addr6, mask6, nwork6;
505 struct in_addr addr4, mask4, nwork4;
506
507 const char *m4 = "255.255.255.0";
508 const char *m6 = "ffff:ffff:ffff:ffff:0000:0000:0000:0000";
509
510 if (map_data (h->module, item, type, &data, &hits) == 1)
511 return;
512
513 if (!conf.anonymize_ip) {
514 add_data_to_holder (item, h, type, panel);
515 free (data);
516 return;
517 }
518
519 if (1 == inet_pton (AF_INET, data, &addr4)) {
520 if (1 == inet_pton (AF_INET, m4, &mask4)) {
521 memset (buf4, 0, sizeof *buf4);
522 nwork4.s_addr = addr4.s_addr & mask4.s_addr;
523
524 if (inet_ntop (AF_INET, &nwork4, buf4, INET_ADDRSTRLEN) != NULL) {
525 set_host (item, h, panel, buf4, hits);
526 free (data);
527 }
528 }
529 } else if (1 == inet_pton (AF_INET6, data, &addr6)) {
530 if (1 == inet_pton (AF_INET6, m6, &mask6)) {
531 memset (buf6, 0, sizeof *buf6);
532 for (i = 0; i < 16; i++) {
533 nwork6.s6_addr[i] = addr6.s6_addr[i] & mask6.s6_addr[i];
534 }
535
536 if (inet_ntop (AF_INET6, &nwork6, buf6, INET6_ADDRSTRLEN) != NULL) {
537 set_host (item, h, panel, buf6, hits);
538 free (data);
539 }
540 }
541 }
542 }
543
544 /* Set all root panel data. This will set the root nodes. */
545 static int
set_root_metrics(GRawDataItem item,GModule module,datatype type,GMetrics ** nmetrics)546 set_root_metrics (GRawDataItem item, GModule module, datatype type, GMetrics ** nmetrics) {
547 GMetrics *metrics;
548 char *data = NULL;
549 uint64_t bw = 0, cumts = 0, maxts = 0;
550 uint32_t hits = 0, visitors = 0;
551
552 if (map_data (module, item, type, &data, &hits) == 1)
553 return 1;
554
555 bw = ht_get_bw (module, item.nkey);
556 cumts = ht_get_cumts (module, item.nkey);
557 maxts = ht_get_maxts (module, item.nkey);
558 visitors = ht_get_visitors (module, item.nkey);
559
560 metrics = new_gmetrics ();
561 metrics->avgts.nts = cumts / hits;
562 metrics->cumts.nts = cumts;
563 metrics->maxts.nts = maxts;
564 metrics->bw.nbw = bw;
565 metrics->data = data;
566 metrics->hits = hits;
567 metrics->visitors = visitors;
568 *nmetrics = metrics;
569
570 return 0;
571 }
572
573 /* Set all root panel data, including sub list items. */
574 static void
add_root_to_holder(GRawDataItem item,GHolder * h,datatype type,GO_UNUSED const GPanel * panel)575 add_root_to_holder (GRawDataItem item, GHolder * h, datatype type,
576 GO_UNUSED const GPanel * panel) {
577 GSubList *sub_list;
578 GMetrics *metrics, *nmetrics;
579 char *root = NULL;
580 int root_idx = KEY_NOT_FOUND, idx = 0;
581
582 if (set_root_metrics (item, h->module, type, &nmetrics) == 1)
583 return;
584
585 if (!(root = ht_get_root (h->module, item.nkey))) {
586 free_gmetrics (nmetrics);
587 return;
588 }
589
590 /* add data as a child node into holder */
591 if (KEY_NOT_FOUND == (root_idx = get_item_idx_in_holder (h, root))) {
592 idx = h->idx;
593 sub_list = new_gsublist ();
594 metrics = new_gmetrics ();
595
596 h->items[idx].metrics = metrics;
597 h->items[idx].metrics->data = root;
598 h->idx++;
599 } else {
600 sub_list = h->items[root_idx].sub_list;
601 metrics = h->items[root_idx].metrics;
602
603 idx = root_idx;
604 free (root);
605 }
606
607 add_sub_item_back (sub_list, h->module, nmetrics);
608 h->items[idx].sub_list = sub_list;
609
610 h->items[idx].metrics = metrics;
611 h->items[idx].metrics->cumts.nts += nmetrics->cumts.nts;
612 h->items[idx].metrics->bw.nbw += nmetrics->bw.nbw;
613 h->items[idx].metrics->hits += nmetrics->hits;
614 h->items[idx].metrics->visitors += nmetrics->visitors;
615 h->items[idx].metrics->avgts.nts =
616 h->items[idx].metrics->cumts.nts / h->items[idx].metrics->hits;
617
618 if (nmetrics->maxts.nts > h->items[idx].metrics->maxts.nts)
619 h->items[idx].metrics->maxts.nts = nmetrics->maxts.nts;
620
621 h->sub_items_size++;
622 }
623
624 /* Load raw data into our holder structure */
625 void
load_holder_data(GRawData * raw_data,GHolder * h,GModule module,GSort sort)626 load_holder_data (GRawData * raw_data, GHolder * h, GModule module, GSort sort) {
627 int i;
628 uint32_t size = 0, max_choices = get_max_choices ();
629 const GPanel *panel = panel_lookup (module);
630
631 #ifdef _DEBUG
632 clock_t begin = clock ();
633 double taken;
634 char *modstr = NULL;
635 LOG_DEBUG (("== load_holder_data ==\n"));
636 #endif
637
638 size = raw_data->size;
639 h->holder_size = size > max_choices ? max_choices : size;
640 h->ht_size = size;
641 h->idx = 0;
642 h->module = module;
643 h->sub_items_size = 0;
644 h->items = new_gholder_item (h->holder_size);
645
646 for (i = 0; i < h->holder_size; i++) {
647 panel->insert (raw_data->items[i], h, raw_data->type, panel);
648 }
649 sort_holder_items (h->items, h->idx, sort);
650 if (h->sub_items_size)
651 sort_sub_list (h, sort);
652 free_raw_data (raw_data);
653
654 #ifdef _DEBUG
655 modstr = get_module_str (module);
656 taken = (double) (clock () - begin) / CLOCKS_PER_SEC;
657 LOG_DEBUG (("== %-30s%f\n\n", modstr, taken));
658 free (modstr);
659 #endif
660 }
661