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