Enable ATTR_INTLIST to contain zeros. Use the same encoding as IDARRAY.
[platform/upstream/libsolv.git] / src / attr_store.c
1 /*
2  * Copyright (c) 2007, Novell Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 /* We need FNM_CASEFOLD and strcasestr.  */
9 #ifndef _GNU_SOURCE
10 #define _GNU_SOURCE
11 #endif
12
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <assert.h>
21 #include <fnmatch.h>
22 #include <regex.h>
23
24 #include "attr_store.h"
25 #include "pool.h"
26 #include "repo.h"
27 #include "util.h"
28
29 #include "attr_store_p.h"
30
31 #include "fastlz.c"
32
33 /* #define DEBUG_PAGING */
34
35 #define BLOB_BLOCK 65535
36
37 #define STRINGSPACE_BLOCK 1023
38 #define STRING_BLOCK 127
39 #define LOCALID_NULL  0
40 #define LOCALID_EMPTY 1
41
42 static Id add_key (Attrstore *s, Id name, unsigned type, unsigned size);
43
44 Attrstore *
45 new_store (Pool *pool)
46 {
47   static const char *predef_strings[] = {
48     "<NULL>",
49     "",
50     0
51   };
52   Attrstore *s = calloc (1, sizeof (Attrstore));
53   s->pool = pool;
54   stringpool_init (&s->ss, predef_strings);
55   add_key (s, 0, 0, 0);
56
57   return s;
58 }
59
60 LocalId
61 str2localid (Attrstore *s, const char *str, int create)
62 {
63   return stringpool_str2id (&s->ss, str, create);
64 }
65
66 const char *
67 localid2str(Attrstore *s, LocalId id)
68 {
69   return s->ss.stringspace + s->ss.strings[id];
70 }
71
72 static void
73 setup_dirs (Attrstore *s)
74 {
75   static const char *ss_init_strs[] =
76   {
77     "<NULL>",
78     "",
79     0
80   };
81
82   s->dirtree.dirs = calloc (1024, sizeof (s->dirtree.dirs[0]));
83   s->dirtree.ndirs = 2;
84   s->dirtree.dirs[0].child = 0;
85   s->dirtree.dirs[0].sibling = 0;
86   s->dirtree.dirs[0].name = 0;
87   s->dirtree.dirs[1].child = 0;
88   s->dirtree.dirs[1].sibling = 0;
89   s->dirtree.dirs[1].name = STRID_EMPTY;
90
91   s->dirtree.dirstack_size = 16;
92   s->dirtree.dirstack = malloc (s->dirtree.dirstack_size * sizeof (s->dirtree.dirstack[0]));
93   s->dirtree.ndirstack = 0;
94   s->dirtree.dirstack[s->dirtree.ndirstack++] = 1; //dir-id of /
95
96   stringpool_init (&s->dirtree.ss, ss_init_strs);
97 }
98
99 static unsigned
100 dir_lookup_1 (Attrstore *s, unsigned dir, const char *name, unsigned insert)
101 {
102   Id nameid;
103   while (*name == '/')
104     name++;
105   if (!*name)
106     return dir;
107   const char *end = strchrnul (name, '/');
108   nameid = stringpool_strn2id (&s->dirtree.ss, name, end - name, 1);
109   unsigned c, num = 0;
110   Dir *dirs = s->dirtree.dirs;
111   for (c = dirs[dir].child; c; c = dirs[c].sibling, num++)
112     if (nameid == dirs[c].name)
113       break;
114   if (!c && !insert)
115     return 0;
116   if (!c)
117     {
118       c = s->dirtree.ndirs++;
119       if (!(c & 1023))
120         dirs = realloc (dirs, (c + 1024) * sizeof (dirs[0]));
121       dirs[c].child = 0;
122       dirs[c].sibling = dirs[dir].child;
123       dirs[c].name = nameid;
124       dirs[c].parent = dir;
125       dirs[dir].child = c;
126       s->dirtree.dirs = dirs;
127     }
128   if (!(s->dirtree.ndirstack & 15))
129     {
130       s->dirtree.dirstack_size += 16;
131       s->dirtree.dirstack = realloc (s->dirtree.dirstack, s->dirtree.dirstack_size * sizeof (s->dirtree.dirstack[0]));
132     }
133   s->dirtree.dirstack[s->dirtree.ndirstack++] = c;
134   if (!*end)
135     return c;
136   unsigned ret = dir_lookup_1 (s, c, end + 1, insert);
137   return ret;
138 }
139
140 unsigned
141 dir_lookup (Attrstore *s, const char *name, unsigned insert)
142 {
143   if (!s->dirtree.ndirs)
144     setup_dirs (s);
145
146   /* Detect number of common path components.  Accept multiple // .  */
147   const char *new_start;
148   unsigned components;
149   for (components = 1, new_start = name; components < s->dirtree.ndirstack; )
150     {
151       int ofs;
152       const char *dirname;
153       while (*new_start == '/')
154         new_start++;
155       dirname = stringpool_id2str (&s->dirtree.ss, s->dirtree.dirs[s->dirtree.dirstack[components]].name);
156       for (ofs = 0;; ofs++)
157         {
158           char n = new_start[ofs];
159           char d = dirname[ofs];
160           if (d == 0 && (n == 0 || n == '/'))
161             {
162               new_start += ofs;
163               components++;
164               if (n == 0)
165                 goto endmatch2;
166               break;
167             }
168           if (n != d)
169             goto endmatch;
170         }
171     }
172 endmatch:
173   while (*new_start == '/')
174     new_start++;
175 endmatch2:
176
177   /* We have always / on the stack.  */
178   //assert (ndirstack);
179   //assert (ndirstack >= components);
180   s->dirtree.ndirstack = components;
181   unsigned ret = s->dirtree.dirstack[s->dirtree.ndirstack - 1];
182   if (*new_start)
183     ret = dir_lookup_1 (s, ret, new_start, insert);
184   //assert (ret == dirstack[ndirstack - 1]);
185   return ret;
186 }
187
188 unsigned
189 dir_parent (Attrstore *s, unsigned dir)
190 {
191   return s->dirtree.dirs[dir].parent;
192 }
193
194 void
195 dir2str (Attrstore *s, unsigned dir, char **str, unsigned *len)
196 {
197   unsigned l = 0;
198   Id ids[s->dirtree.dirstack_size + 1];
199   unsigned i, ii;
200   for (i = 0; dir > 1; dir = dir_parent (s, dir), i++)
201     ids[i] = s->dirtree.dirs[dir].name;
202   ii = i;
203   l = 1;
204   for (i = 0; i < ii; i++)
205     l += 1 + strlen (stringpool_id2str (&s->dirtree.ss, ids[i]));
206   l++;
207   if (l > *len)
208     {
209       *str = malloc (l);
210       *len = l;
211     }
212   char *dest = *str;
213   *dest++ = '/';
214   for (i = ii; i--;)
215     {
216       const char *name = stringpool_id2str (&s->dirtree.ss, ids[i]);
217       dest = mempcpy (dest, name, strlen (name));
218       *dest++ = '/';
219     }
220   *dest = 0;
221 }
222
223 void
224 ensure_entry (Attrstore *s, unsigned int entry)
225 {
226   unsigned int old_num = s->entries;
227   if (entry < s->entries)
228     return;
229   s->entries = entry + 1;
230   if (((old_num + 127) & ~127) != ((s->entries + 127) & ~127))
231     {
232       if (s->attrs)
233         s->attrs = realloc (s->attrs, (((s->entries+127) & ~127) * sizeof (s->attrs[0])));
234       else
235         s->attrs = malloc (((s->entries+127) & ~127) * sizeof (s->attrs[0]));
236     }
237   memset (s->attrs + old_num, 0, (s->entries - old_num) * sizeof (s->attrs[0]));
238 }
239
240 unsigned int
241 new_entry (Attrstore *s)
242 {
243   if ((s->entries & 127) == 0)
244     {
245       if (s->attrs)
246         s->attrs = realloc (s->attrs, ((s->entries+128) * sizeof (s->attrs[0])));
247       else
248         s->attrs = malloc ((s->entries+128) * sizeof (s->attrs[0]));
249     }
250   s->attrs[s->entries++] = 0;
251   return s->entries - 1;
252 }
253
254 static LongNV *
255 find_attr (Attrstore *s, unsigned int entry, Id name)
256 {
257   LongNV *nv;
258   if (entry >= s->entries)
259     return 0;
260   nv = s->attrs[entry];
261   if (nv)
262     {
263       while (nv->key && s->keys[nv->key].name != name)
264         nv++;
265       if (nv->key)
266         return nv;
267     }
268   return 0;
269 }
270
271 static void
272 add_attr (Attrstore *s, unsigned int entry, LongNV attr)
273 {
274   LongNV *nv;
275   unsigned int len;
276   ensure_entry (s, entry);
277   if (attr.key >= s->nkeys)
278     return;
279   nv = s->attrs[entry];
280   len = 0;
281   if (nv)
282     {
283       while (nv->key && nv->key != attr.key)
284         nv++;
285       if (nv->key)
286         return;
287       len = nv - s->attrs[entry];
288     }
289   len += 2;
290   if (s->attrs[entry])
291     s->attrs[entry] = realloc (s->attrs[entry], len * sizeof (LongNV));
292   else
293     s->attrs[entry] = malloc (len * sizeof (LongNV));
294   nv = s->attrs[entry] + len - 2;
295   *nv++ = attr;
296   nv->key = 0;
297 }
298
299 void
300 add_attr_int (Attrstore *s, unsigned int entry, Id name, unsigned int val)
301 {
302   LongNV nv;
303   nv.key = add_key (s, name, TYPE_ATTR_INT, 0);
304   nv.v.i[0] = val;
305   add_attr (s, entry, nv);
306 }
307
308 void
309 add_attr_special_int (Attrstore *s, unsigned int entry, Id name, unsigned int val)
310 {
311   if (val > (TYPE_ATTR_SPECIAL_END - TYPE_ATTR_SPECIAL_START))
312     add_attr_int (s, entry, name, val);
313   else
314     {
315       LongNV nv;
316       nv.key = add_key (s, name, TYPE_ATTR_SPECIAL_START + val, 0);
317       add_attr (s, entry, nv);
318     }
319 }
320
321 static void
322 add_attr_chunk (Attrstore *s, unsigned int entry, Id name, unsigned int ofs, unsigned int len)
323 {
324   LongNV nv;
325   nv.key = add_key (s, name, TYPE_ATTR_CHUNK, 0);
326   nv.v.i[0] = ofs;
327   nv.v.i[1] = len;
328   add_attr (s, entry, nv);
329 }
330
331 void
332 add_attr_blob (Attrstore *s, unsigned int entry, Id name, const void *ptr, unsigned int len)
333 {
334   if (((s->blob_next_free + BLOB_BLOCK) & ~BLOB_BLOCK)
335       != ((s->blob_next_free + len + BLOB_BLOCK) & ~BLOB_BLOCK))
336     {
337       unsigned int blobsz = (s->blob_next_free + len + BLOB_BLOCK) &~BLOB_BLOCK;
338       s->blob_store = xrealloc (s->blob_store, blobsz);
339     }
340   memcpy (s->blob_store + s->blob_next_free, ptr, len);
341   add_attr_chunk (s, entry, name, s->blob_next_free, len);
342   s->blob_next_free += len;
343
344   unsigned int npages = (s->blob_next_free + BLOB_PAGESIZE - 1) / BLOB_PAGESIZE;
345   if (npages != s->num_pages)
346     {
347       Attrblobpage *p;
348       s->pages = xrealloc (s->pages, npages * sizeof (s->pages[0]));
349       for (p = s->pages + s->num_pages; s->num_pages < npages;
350            p++, s->num_pages++)
351         {
352           p->mapped_at = s->num_pages * BLOB_PAGESIZE;
353           p->file_offset = 0;
354           p->file_size = 0;
355         }
356     }
357 }
358
359 void
360 add_attr_string (Attrstore *s, unsigned int entry, Id name, const char *val)
361 {
362   LongNV nv;
363   nv.key = add_key (s, name, TYPE_ATTR_STRING, 0);
364   nv.v.str = strdup (val);
365   add_attr (s, entry, nv);
366 }
367
368 void
369 add_attr_intlist_int (Attrstore *s, unsigned int entry, Id name, int val)
370 {
371   LongNV *nv = find_attr (s, entry, name);
372   if (nv)
373     {
374       unsigned len = nv->v.intlist[0]++;
375       nv->v.intlist = realloc (nv->v.intlist, (len + 2) * sizeof (nv->v.intlist[0]));
376       nv->v.intlist[len+1] = val;
377     }
378   else
379     {
380       LongNV mynv;
381       mynv.key = add_key (s, name, TYPE_ATTR_INTLIST, 0);
382       mynv.v.intlist = malloc (2 * sizeof (mynv.v.intlist[0]));
383       mynv.v.intlist[0] = 1;
384       mynv.v.intlist[1] = val;
385       add_attr (s, entry, mynv);
386     }
387 }
388
389 void
390 add_attr_localids_id (Attrstore *s, unsigned int entry, Id name, LocalId id)
391 {
392   LongNV *nv = find_attr (s, entry, name);
393   if (nv)
394     {
395       unsigned len = 0;
396       while (nv->v.localids[len])
397         len++;
398       nv->v.localids = realloc (nv->v.localids, (len + 2) * sizeof (nv->v.localids[0]));
399       nv->v.localids[len] = id;
400       nv->v.localids[len+1] = 0;
401     }
402   else
403     {
404       LongNV mynv;
405       mynv.key = add_key (s, name, TYPE_ATTR_LOCALIDS, 0);
406       mynv.v.localids = malloc (2 * sizeof (mynv.v.localids[0]));
407       mynv.v.localids[0] = id;
408       mynv.v.localids[1] = 0;
409       add_attr (s, entry, mynv);
410     }
411 }
412
413 void
414 add_attr_void (Attrstore *s, unsigned int entry, Id name)
415 {
416   LongNV nv;
417   nv.key = add_key (s, name, TYPE_VOID, 0);
418   add_attr (s, entry, nv);
419 }
420
421 void
422 merge_attrs (Attrstore *s, unsigned dest, unsigned src)
423 {
424   LongNV *nv;
425   ensure_entry (s, dest);
426   nv = s->attrs[src];
427   if (nv)
428     {
429       for (; nv->key; nv++)
430         if (!find_attr (s, dest, s->keys[nv->key].name))
431           switch (s->keys[nv->key].type)
432             {
433               case TYPE_ATTR_INTLIST:
434                 {
435                   unsigned i, len = nv->v.intlist[0];
436                   for (i = 0; i < len; i++)
437                     add_attr_intlist_int (s, dest, s->keys[nv->key].name, nv->v.intlist[i + 1]);
438                 }
439                 break;
440               case TYPE_ATTR_LOCALIDS:
441                 {
442                   unsigned len = 0;
443                   while (nv->v.localids[len])
444                     add_attr_localids_id (s, dest, s->keys[nv->key].name, nv->v.localids[len++]);
445                 }
446                 break;
447               case TYPE_ATTR_STRING:
448                 add_attr_string (s, dest, s->keys[nv->key].name, nv->v.str);
449                 break;
450               default:
451                 add_attr (s, dest, *nv);
452                 break;
453             }
454     }
455 }
456
457 #define pool_debug(a,b,...) fprintf (stderr, __VA_ARGS__)
458
459 static Id read_id (FILE *fp, Id max);
460
461 /* This routine is used only when attributes are embedded into the
462    normal repo SOLV file.  */
463 void
464 add_attr_from_file (Attrstore *s, unsigned entry, Id name, int type, Id *idmap, unsigned maxid, FILE *fp)
465 {
466   Pool *pool = s->pool;
467   //fprintf (stderr, "%s: attribute in a repo SOLV?\n", id2str (pool, name));
468   switch (type)
469     {
470       case TYPE_VOID:
471         add_attr_void (s, entry, name);
472         break;
473       case TYPE_ATTR_CHUNK:
474         {
475           unsigned ofs = read_id (fp, 0);
476           unsigned len = read_id (fp, 0);
477           add_attr_chunk (s, entry, name, ofs, len);
478         }
479         break;
480       case TYPE_ATTR_INT:
481         {
482           unsigned i = read_id(fp, 0);
483           add_attr_int (s, entry, name, i);
484         }
485         break;
486       case TYPE_ATTR_STRING:
487         {
488           unsigned char localbuf[1024];
489           int c;
490           unsigned char *buf = localbuf;
491           unsigned len = sizeof (localbuf);
492           unsigned ofs = 0;
493           while((c = getc (fp)) != 0)
494             {
495               if (c == EOF)
496                 {
497                   pool_debug (mypool, SAT_FATAL, "unexpected EOF\n");
498                   exit (1);
499                 }
500               /* Plus 1 as we also want to add the 0.  */
501               if (ofs + 1 >= len)
502                 {
503                   len += 256;
504                   if (buf == localbuf)
505                     {
506                       buf = xmalloc (len);
507                       memcpy (buf, localbuf, len - 256);
508                     }
509                   else
510                     buf = xrealloc (buf, len);
511                 }
512               buf[ofs++] = c;
513             }
514           buf[ofs++] = 0;
515           add_attr_string (s, entry, name, (char*) buf);
516           if (buf != localbuf)
517             xfree (buf);
518         }
519         break;
520       case TYPE_ATTR_INTLIST:
521         {
522           int i;
523           while ((i = read_id(fp, 0)) & 64)
524             add_attr_intlist_int (s, entry, name, (i & 63) | ((i >> 1) & ~63));
525           add_attr_intlist_int (s, entry, name, (i & 63) | ((i >> 1) & ~63));
526         }
527         break;
528       case TYPE_ATTR_LOCALIDS:
529         {
530           Id i;
531           /* The read ID will be pool-based.  */
532           while ((i = read_id(fp, maxid)) != 0)
533             {
534               if (idmap)
535                 i = idmap[i];
536               add_attr_localids_id (s, entry, name, str2localid (s, id2str (pool, i), 1));
537             }
538         }
539         break;
540       default:
541         if (type >= TYPE_ATTR_SPECIAL_START && type <= TYPE_ATTR_SPECIAL_END)
542           {
543             add_attr_special_int (s, entry, name, type - TYPE_ATTR_SPECIAL_START);
544             break;
545           }
546         pool_debug(pool, SAT_FATAL, "unknown type %d\n", type);
547         exit(0);
548     }
549 }
550
551 /* Make sure all pages from PSTART to PEND (inclusive) are loaded,
552    and are consecutive.  Return a pointer to the mapping of PSTART.  */
553 static const void *
554 load_page_range (Attrstore *s, unsigned int pstart, unsigned int pend)
555 {
556   unsigned char buf[BLOB_PAGESIZE];
557   unsigned int i;
558
559   /* Quick check in case all pages are there already and consecutive.  */
560   for (i = pstart; i <= pend; i++)
561     if (s->pages[i].mapped_at == -1
562         || (i > pstart
563             && s->pages[i].mapped_at
564                != s->pages[i-1].mapped_at + BLOB_PAGESIZE))
565       break;
566   if (i > pend)
567     return s->blob_store + s->pages[pstart].mapped_at;
568
569   /* Ensure that we can map the numbers of pages we need at all.  */
570   if (pend - pstart + 1 > s->ncanmap)
571     {
572       unsigned int oldcan = s->ncanmap;
573       s->ncanmap = pend - pstart + 1;
574       if (s->ncanmap < 4)
575         s->ncanmap = 4;
576       s->mapped = xrealloc (s->mapped, s->ncanmap * sizeof (s->mapped[0]));
577       memset (s->mapped + oldcan, 0, (s->ncanmap - oldcan) * sizeof (s->mapped[0]));
578       s->blob_store = xrealloc (s->blob_store, s->ncanmap * BLOB_PAGESIZE);
579 #ifdef DEBUG_PAGING
580       fprintf (stderr, "PAGE: can map %d pages\n", s->ncanmap);
581 #endif
582     }
583
584   /* Now search for "cheap" space in our store.  Space is cheap if it's either
585      free (very cheap) or contains pages we search for anyway.  */
586
587   /* Setup cost array.  */
588   unsigned int cost[s->ncanmap];
589   for (i = 0; i < s->ncanmap; i++)
590     {
591       unsigned int pnum = s->mapped[i];
592       if (pnum == 0)
593         cost[i] = 0;
594       else
595         {
596           pnum--;
597           Attrblobpage *p = s->pages + pnum;
598           assert (p->mapped_at != -1);
599           if (pnum >= pstart && pnum <= pend)
600             cost[i] = 1;
601           else
602             cost[i] = 3;
603         }
604     }
605
606   /* And search for cheapest space.  */
607   unsigned int best_cost = -1;
608   unsigned int best = 0;
609   unsigned int same_cost = 0;
610   for (i = 0; i + pend - pstart < s->ncanmap; i++)
611     {
612       unsigned int c = cost[i];
613       unsigned int j;
614       for (j = 0; j < pend - pstart + 1; j++)
615         c += cost[i+j];
616       if (c < best_cost)
617         best_cost = c, best = i;
618       else if (c == best_cost)
619         same_cost++;
620       /* A null cost won't become better.  */
621       if (c == 0)
622         break;
623     }
624   /* If all places have the same cost we would thrash on slot 0.  Avoid
625      this by doing a round-robin strategy in this case.  */
626   if (same_cost == s->ncanmap - pend + pstart - 1)
627     best = s->rr_counter++ % (s->ncanmap - pend + pstart);
628
629   /* So we want to map our pages from [best] to [best+pend-pstart].
630      Use a very simple strategy, which doesn't make the best use of
631      our resources, but works.  Throw away all pages in that range
632      (even ours) then copy around ours (in case they were outside the 
633      range) or read them in.  */
634   for (i = best; i < best + pend - pstart + 1; i++)
635     {
636       unsigned int pnum = s->mapped[i];
637       if (pnum--
638           /* If this page is exactly at the right place already,
639              no need to evict it.  */
640           && pnum != pstart + i - best)
641         {
642           /* Evict this page.  */
643 #ifdef DEBUG_PAGING
644           fprintf (stderr, "PAGE: evict page %d from %d\n", pnum, i);
645 #endif
646           cost[i] = 0;
647           s->mapped[i] = 0;
648           s->pages[pnum].mapped_at = -1;
649         }
650     }
651
652   /* Everything is free now.  Read in the pages we want.  */
653   for (i = pstart; i <= pend; i++)
654     {
655       Attrblobpage *p = s->pages + i;
656       unsigned int pnum = i - pstart + best;
657       void *dest = s->blob_store + pnum * BLOB_PAGESIZE;
658       if (p->mapped_at != -1)
659         {
660           if (p->mapped_at != pnum * BLOB_PAGESIZE)
661             {
662 #ifdef DEBUG_PAGING
663               fprintf (stderr, "PAGECOPY: %d to %d\n", i, pnum);
664 #endif
665               /* Still mapped somewhere else, so just copy it from there.  */
666               memcpy (dest, s->blob_store + p->mapped_at, BLOB_PAGESIZE);
667               s->mapped[p->mapped_at / BLOB_PAGESIZE] = 0;
668             }
669         }
670       else
671         {
672           unsigned int in_len = p->file_size;
673           unsigned int compressed = in_len & 1;
674           in_len >>= 1;
675 #ifdef DEBUG_PAGING
676           fprintf (stderr, "PAGEIN: %d to %d", i, pnum);
677 #endif
678           /* Not mapped, so read in this page.  */
679           if (fseek (s->file, p->file_offset, SEEK_SET) < 0)
680             {
681               perror ("mapping fseek");
682               exit (1);
683             }
684           if (fread (compressed ? buf : dest, in_len, 1, s->file) != 1)
685             {
686               perror ("mapping fread");
687               exit (1);
688             }
689           if (compressed)
690             {
691               unsigned int out_len;
692               out_len = unchecked_decompress_buf (buf, in_len,
693                                                   dest, BLOB_PAGESIZE);
694               if (out_len != BLOB_PAGESIZE
695                   && i < s->num_pages - 1)
696                 {
697                   fprintf (stderr, "can't decompress\n");
698                   exit (1);
699                 }
700 #ifdef DEBUG_PAGING
701               fprintf (stderr, " (expand %d to %d)", in_len, out_len);
702 #endif
703             }
704 #ifdef DEBUG_PAGING
705           fprintf (stderr, "\n");
706 #endif
707         }
708       p->mapped_at = pnum * BLOB_PAGESIZE;
709       s->mapped[pnum] = i + 1;
710     }
711
712   return s->blob_store + best * BLOB_PAGESIZE;
713 }
714
715 const void *
716 attr_retrieve_blob (Attrstore *s, unsigned int ofs, unsigned int len)
717 {
718   if (s->file)
719     {
720       /* Paging!  Yeah!  */
721       unsigned int pstart = ofs / BLOB_PAGESIZE;
722       unsigned int pend = (ofs + len - 1) / BLOB_PAGESIZE;
723       const void *m = load_page_range (s, pstart, pend);
724       return m + (ofs & (BLOB_PAGESIZE - 1));
725     }
726   if (!s->blob_store)
727     return 0;
728   if (ofs >= s->blob_next_free)
729     return 0;
730   return s->blob_store + ofs;
731 }
732
733 #define FLAT_ATTR_BLOCK 127
734 #define KEY_BLOCK 127
735 #define SCHEMA_BLOCK 127
736
737 #define add_elem(buf,ofs,val,block) do { \
738   if (((ofs) & (block)) == 0) \
739     buf = xrealloc (buf, ((ofs) + (block) + 1) * sizeof((buf)[0])); \
740   (buf)[(ofs)++] = val; \
741 } while (0)
742 #define add_u16(buf,ofs,val,block) do {\
743   typedef int __wrong_buf__[(1-sizeof((buf)[0])) * (sizeof((buf)[0])-1)];\
744   add_elem(buf,ofs,(val) & 0xFF,block); \
745   add_elem(buf,ofs,((val) >> 8) & 0xFF,block); \
746 } while (0)
747 #define add_num(buf,ofs,val,block) do {\
748   typedef int __wrong_buf__[(1-sizeof((buf)[0])) * (sizeof((buf)[0])-1)];\
749   if ((val) >= (1 << 14)) \
750     { \
751       if ((val) >= (1 << 28)) \
752         add_elem (buf,ofs,((val) >> 28) | 128, block); \
753       if ((val) >= (1 << 21)) \
754         add_elem (buf,ofs,((val) >> 21) | 128, block); \
755       add_elem (buf,ofs,((val) >> 14) | 128, block); \
756     } \
757   if ((val) >= (1 << 7)) \
758     add_elem (buf,ofs,((val) >> 7) | 128, block); \
759   add_elem (buf,ofs,(val) & 127, block); \
760 } while (0)
761
762 static int
763 longnv_cmp (const void *pa, const void *pb)
764 {
765   const LongNV *a = (const LongNV *)pa;
766   const LongNV *b = (const LongNV *)pb;
767   return a->key - b->key;
768 }
769
770 static Id
771 add_key (Attrstore *s, Id name, unsigned type, unsigned size)
772 {
773   unsigned i;
774   for (i = 0; i < s->nkeys; i++)
775     if (s->keys[i].name == name && s->keys[i].type == type)
776       break;
777   if (i < s->nkeys)
778     {
779       s->keys[i].size += size;
780       return i;
781     }
782   if ((s->nkeys & KEY_BLOCK) == 0)
783     s->keys = xrealloc (s->keys, (s->nkeys + KEY_BLOCK + 1) * sizeof (s->keys[0]));
784   s->keys[i].name = name;
785   s->keys[i].type = type;
786   s->keys[i].size = size;
787   return s->nkeys++;
788 }
789
790 void
791 attr_store_pack (Attrstore *s)
792 {
793   unsigned i;
794   unsigned int old_mem = 0;
795   if (s->packed)
796     return;
797   s->ent2attr = xcalloc (s->entries, sizeof (s->ent2attr[0]));
798   s->flat_attrs = 0;
799   s->attr_next_free = 0;
800   s->nschemata = 0;
801   s->szschemata = 0;
802   s->schemata = 0;
803   s->schemaofs = 0;
804
805   add_num (s->flat_attrs, s->attr_next_free, 0, FLAT_ATTR_BLOCK);
806   add_elem (s->schemata, s->szschemata, 0, SCHEMA_BLOCK);
807   add_elem (s->schemaofs, s->nschemata, 0, SCHEMA_BLOCK);
808
809   for (i = 0; i < s->entries; i++)
810     {
811       unsigned int num_attrs = 0, ofs;
812       LongNV *nv = s->attrs[i];
813       if (nv)
814         while (nv->key)
815           nv++, num_attrs++;
816       if (nv)
817         old_mem += (num_attrs + 1) * sizeof (LongNV);
818       if (!num_attrs)
819         continue;
820       nv = s->attrs[i];
821       qsort (s->attrs[i], num_attrs, sizeof (LongNV), longnv_cmp);
822       unsigned int this_schema;
823       for (this_schema = 0; this_schema < s->nschemata; this_schema++)
824         {
825           for (ofs = 0; ofs < num_attrs; ofs++)
826             {
827               Id key = nv[ofs].key;
828               assert (s->schemaofs[this_schema] + ofs < s->szschemata);
829               if (key != s->schemata[s->schemaofs[this_schema]+ofs])
830                 break;
831             }
832           if (ofs == num_attrs && !s->schemata[s->schemaofs[this_schema]+ofs])
833             break;
834         }
835       if (this_schema == s->nschemata)
836         {
837           /* This schema not found --> insert it.  */
838           add_elem (s->schemaofs, s->nschemata, s->szschemata, SCHEMA_BLOCK);
839           for (ofs = 0; ofs < num_attrs; ofs++)
840             {
841               Id key = nv[ofs].key;
842               add_elem (s->schemata, s->szschemata, key, SCHEMA_BLOCK);
843             }
844           add_elem (s->schemata, s->szschemata, 0, SCHEMA_BLOCK);
845         }
846       s->ent2attr[i] = s->attr_next_free;
847       add_num (s->flat_attrs, s->attr_next_free, this_schema, FLAT_ATTR_BLOCK);
848       for (ofs = 0; ofs < num_attrs; ofs++)
849         switch (s->keys[nv[ofs].key].type)
850           {
851             case TYPE_VOID:
852               break;
853             case TYPE_ATTR_INT:
854               {
855                 unsigned int i = nv[ofs].v.i[0];
856                 add_num (s->flat_attrs, s->attr_next_free, i, FLAT_ATTR_BLOCK);
857                 break;
858               }
859             case TYPE_ATTR_CHUNK:
860               {
861                 unsigned int i = nv[ofs].v.i[0];
862                 add_num (s->flat_attrs, s->attr_next_free, i, FLAT_ATTR_BLOCK);
863                 i = nv[ofs].v.i[1];
864                 add_num (s->flat_attrs, s->attr_next_free, i, FLAT_ATTR_BLOCK);
865                 break;
866               }
867             case TYPE_ATTR_STRING:
868               {
869                 const char *str = nv[ofs].v.str;
870                 for (; *str; str++)
871                   add_elem (s->flat_attrs, s->attr_next_free, *str, FLAT_ATTR_BLOCK);
872                 add_elem (s->flat_attrs, s->attr_next_free, 0, FLAT_ATTR_BLOCK);
873                 old_mem += strlen ((const char*)nv[ofs].v.str) + 1;
874                 xfree ((void*)nv[ofs].v.str);
875                 break;
876               }
877             case TYPE_ATTR_INTLIST:
878               {
879                 const int *il = nv[ofs].v.intlist;
880                 int len = *il++;
881                 //add_num (s->flat_attrs, s->attr_next_free, len, FLAT_ATTR_BLOCK);
882                 old_mem += 4 * (len + 1);
883                 while (len--)
884                   {
885                     int i = *il++;
886                     if (i >= 64)
887                       i = (i & 63) | ((i & ~63) << 1);
888                     if (len)
889                       i |= 64;
890                     add_num (s->flat_attrs, s->attr_next_free, i, FLAT_ATTR_BLOCK);
891                   }
892                 xfree (nv[ofs].v.intlist);
893                 break;
894               }
895             case TYPE_ATTR_LOCALIDS:
896               {
897                 const Id *il = nv[ofs].v.localids;
898                 Id i;
899                 for (; (i = *il) != 0; il++, old_mem += 4)
900                   add_num (s->flat_attrs, s->attr_next_free, i, FLAT_ATTR_BLOCK);
901                 add_num (s->flat_attrs, s->attr_next_free, 0, FLAT_ATTR_BLOCK);
902                 old_mem += 4;
903                 xfree (nv[ofs].v.localids);
904                 break;
905               }
906             default:
907               break;
908           }
909       xfree (nv);
910     }
911   old_mem += s->entries * sizeof (s->attrs[0]);
912   free (s->attrs);
913   s->attrs = 0;
914
915   /* Remove the hashtable too, it will be build on demand in str2localid
916      the next time we call it, which should not happen while in packed mode.  */
917   old_mem += (s->ss.stringhashmask + 1) * sizeof (s->ss.stringhashtbl[0]);
918   free (s->ss.stringhashtbl);
919   s->ss.stringhashtbl = 0;
920   s->ss.stringhashmask = 0;
921
922   fprintf (stderr, "%d\n", old_mem);
923   fprintf (stderr, "%zd\n", s->entries * sizeof(s->ent2attr[0]));
924   fprintf (stderr, "%d\n", s->attr_next_free);
925   fprintf (stderr, "%zd\n", s->nschemata * sizeof(s->schemaofs[0]));
926   fprintf (stderr, "%zd\n", s->szschemata * sizeof(s->schemata[0]));
927   fprintf (stderr, "pages %d\n", s->num_pages);
928   s->packed = 1;
929 }
930
931 /* Pages in all blob pages, and deactivates paging.  */
932 static void
933 pagein_all (Attrstore *s)
934 {
935   /* If we have no backing file everything is there already.  */
936   if (!s->file)
937     return;
938   /*fprintf (stderr, "Aieee!\n");
939   exit (1);*/
940 }
941
942 void
943 attr_store_unpack (Attrstore *s)
944 {
945   unsigned int i;
946   if (!s->packed)
947     return;
948
949   pagein_all (s);
950
951   /* Make the store writable right away, so we can use our adder functions.  */
952   s->packed = 0;
953   s->attrs = xcalloc (s->entries, sizeof (s->attrs[0]));
954
955   for (i = 0; i < s->entries; i++)
956     {
957       attr_iterator ai;
958       FOR_ATTRS (s, i, &ai)
959         {
960           switch (ai.type)
961             {
962             case TYPE_VOID:
963               add_attr_void (s, i, ai.name);
964               break;
965             case TYPE_ATTR_INT:
966               add_attr_int (s, i, ai.name, ai.as_int); 
967               break;
968             case TYPE_ATTR_CHUNK:
969               add_attr_chunk (s, i, ai.name, ai.as_chunk[0], ai.as_chunk[1]);
970               break;
971             case TYPE_ATTR_STRING:
972               add_attr_string (s, i, ai.name, ai.as_string);
973               break;
974             case TYPE_ATTR_INTLIST:
975               {
976                 while (1)
977                   {
978                     int val;
979                     get_num (ai.as_numlist, val);
980                     add_attr_intlist_int (s, i, ai.name, (val & 63) | ((val >> 1) & ~63));
981                     if (!(val & 64))
982                       break;
983                   }
984                 break;
985               }
986             case TYPE_ATTR_LOCALIDS:
987               {
988                 while (1)
989                   {
990                     Id val;
991                     get_num (ai.as_numlist, val);
992                     if (!val)
993                       break;
994                     add_attr_localids_id (s, i, ai.name, val);
995                   }
996                 break;
997               }
998             default:
999               if (ai.type >= TYPE_ATTR_SPECIAL_START
1000                   && ai.type <= TYPE_ATTR_SPECIAL_END)
1001                 add_attr_special_int (s, i, ai.name, ai.type - TYPE_ATTR_SPECIAL_START);
1002               break;
1003             }
1004         }
1005     }
1006
1007   xfree (s->ent2attr);
1008   s->ent2attr = 0;
1009   xfree (s->flat_attrs);
1010   s->flat_attrs = 0;
1011   s->attr_next_free = 0;
1012   xfree (s->schemaofs);
1013   s->schemaofs = 0;
1014   s->nschemata = 0;
1015   xfree (s->schemata);
1016   s->schemata = 0;
1017   s->szschemata = 0;
1018 }
1019
1020 static void
1021 write_u8(FILE *fp, unsigned int x)
1022 {
1023   if (putc(x, fp) == EOF)
1024     {
1025       perror("write error");
1026       exit(1);
1027     }
1028 }
1029
1030 static void
1031 write_u32(FILE *fp, unsigned int x)
1032 {
1033   if (putc(x >> 24, fp) == EOF ||
1034       putc(x >> 16, fp) == EOF ||
1035       putc(x >> 8, fp) == EOF ||
1036       putc(x, fp) == EOF)
1037     {
1038       perror("write error");
1039       exit(1);
1040     }
1041 }
1042
1043 static void
1044 write_id(FILE *fp, Id x)
1045 {
1046   if (x >= (1 << 14))
1047     {
1048       if (x >= (1 << 28))
1049         putc((x >> 28) | 128, fp);
1050       if (x >= (1 << 21))
1051         putc((x >> 21) | 128, fp);
1052       putc((x >> 14) | 128, fp);
1053     }
1054   if (x >= (1 << 7))
1055     putc((x >> 7) | 128, fp);
1056   if (putc(x & 127, fp) == EOF)
1057     {
1058       perror("write error");
1059       exit(1);
1060     }
1061 }
1062
1063 static Id *
1064 write_idarray(FILE *fp, Id *ids)
1065 {
1066   Id id;
1067   if (!ids)
1068     return ids;
1069   if (!*ids)
1070     {
1071       write_u8(fp, 0);
1072       return ids + 1;
1073     }
1074   for (;;)
1075     {
1076       id = *ids++;
1077       if (id >= 64)
1078         id = (id & 63) | ((id & ~63) << 1);
1079       if (!*ids)
1080         {
1081           write_id(fp, id);
1082           return ids + 1;
1083         }
1084       write_id(fp, id | 64);
1085     }
1086 }
1087
1088 static void
1089 write_pages (FILE *fp, Attrstore *s)
1090 {
1091   unsigned int i;
1092   unsigned char buf[BLOB_PAGESIZE];
1093
1094   /* The compressed pages in the file have different sizes, so we need
1095      to store these sizes somewhere, either in front of all page data,
1096      interleaved with the page data (in front of each page), or after
1097      the page data.  At this point we don't yet know the final compressed
1098      sizes.  These are the pros and cons:
1099      * in front of all page data
1100        + when reading back we only have to read this header, and know
1101          where every page data is placed
1102        - we have to compress all pages first before starting to write them.
1103          Our output stream might be unseekable, so we can't simply
1104          reserve space for the header, write all pages and then update the
1105          header.  This needs memory for all compressed pages at once.
1106      * interleaved with page data
1107        + we can compress and write per page, low memory overhead
1108        - when reading back we have to read at least those numbers,
1109          thereby either having to read all page data, or at least seek
1110          over it.
1111      * after all page data
1112        + we can do streamed writing, remembering the sizes per page,
1113          and emitting the header (which is a footer then) at the end
1114        - reading back is hardest: before the page data we don't know
1115          how long it is overall, so we have to put that information
1116          also at the end, but it needs a determinate position, so can
1117          only be at a known offset from the end.  But that means that
1118          we must be able to seek when reading back.  We have this
1119          wish anyway in case we want to use on-demand paging then, but
1120          it's optional.
1121
1122      Of all these it seems the best good/bad ratio is with the interleaved
1123      storage.  No memory overhead at writing and no unreasonable limitations
1124      for read back.  */
1125   write_u32 (fp, s->blob_next_free);
1126   write_u32 (fp, BLOB_PAGESIZE);
1127   assert (((s->blob_next_free + BLOB_PAGESIZE - 1) / BLOB_PAGESIZE) == s->num_pages);
1128   for (i = 0; i < s->num_pages; i++)
1129     {
1130       unsigned int in_len;
1131       unsigned int out_len;
1132       const void *in;
1133       if (i == s->num_pages - 1)
1134         in_len = s->blob_next_free & (BLOB_PAGESIZE - 1);
1135       else
1136         in_len = BLOB_PAGESIZE;
1137       if (in_len)
1138         {
1139           in = attr_retrieve_blob (s, i * BLOB_PAGESIZE, in_len);
1140           out_len = compress_buf (in, in_len, buf, in_len - 1);
1141           if (!out_len)
1142             {
1143               memcpy (buf, in, in_len);
1144               out_len = in_len;
1145             }
1146         }
1147       else
1148         out_len = 0;
1149 #ifdef DEBUG_PAGING
1150       fprintf (stderr, "page %d: %d -> %d\n", i, in_len, out_len);
1151 #endif
1152       write_u32 (fp, out_len * 2 + (out_len != in_len));
1153       if (out_len
1154           && fwrite (buf, out_len, 1, fp) != 1)
1155         {
1156           perror("write error");
1157           exit(1);
1158         }
1159     }
1160 }
1161
1162 void
1163 write_attr_store (FILE *fp, Attrstore *s)
1164 {
1165   unsigned i;
1166   unsigned local_ssize;
1167
1168   attr_store_pack (s);
1169
1170   /* Transform our attribute names (pool string IDs) into local IDs.  */
1171   for (i = 1; i < s->nkeys; i++)
1172     s->keys[i].name = str2localid (s, id2str (s->pool, s->keys[i].name), 1);
1173
1174   /* write file header */
1175   write_u32(fp, 'S' << 24 | 'O' << 16 | 'L' << 8 | 'V');
1176   write_u32(fp, SOLV_VERSION_2);
1177
1178   /* write counts */
1179   write_u32(fp, s->ss.nstrings);      // nstrings
1180   write_u32(fp, 0);                   // nrels
1181   write_u32(fp, s->entries);          // nsolvables
1182   write_u32(fp, s->nkeys);
1183   write_u32(fp, s->nschemata);
1184   write_u32(fp, 0);     /* no info block */
1185   unsigned solv_flags = 0;
1186   solv_flags |= SOLV_FLAG_PACKEDSIZES;
1187   //solv_flags |= SOLV_FLAG_PREFIX_POOL;
1188   write_u32(fp, solv_flags);
1189
1190   for (i = 1, local_ssize = 0; i < (unsigned)s->ss.nstrings; i++)
1191     local_ssize += strlen (localid2str (s, i)) + 1;
1192
1193   write_u32 (fp, local_ssize);
1194   for (i = 1; i < (unsigned)s->ss.nstrings; i++)
1195     {
1196       const char *str = localid2str (s, i);
1197       if (fwrite(str, strlen(str) + 1, 1, fp) != 1)
1198         {
1199           perror("write error");
1200           exit(1);
1201         }
1202     }
1203
1204   for (i = 1; i < s->nkeys; i++)
1205     {
1206       write_id (fp, s->keys[i].name);
1207       write_id (fp, s->keys[i].type);
1208       write_id (fp, s->keys[i].size);
1209
1210       /* Also transform back the names (now local IDs) into pool IDs,
1211          so we can use the pool also after writing.  */
1212       s->keys[i].name = str2id (s->pool, localid2str (s, s->keys[i].name), 0);
1213     }
1214
1215   write_id (fp, s->szschemata);
1216   Id *ids = s->schemata + 0;
1217   for (i = 0; i < s->nschemata; i++)
1218     ids = write_idarray (fp, ids);
1219   assert (ids == s->schemata + s->szschemata);
1220
1221   /* Convert our offsets into sizes.  */
1222   unsigned end = s->attr_next_free;
1223   for (i = s->entries; i > 0;)
1224     {
1225       i--;
1226       if (s->ent2attr[i])
1227         {
1228           s->ent2attr[i] = end - s->ent2attr[i];
1229           end = end - s->ent2attr[i];
1230         }
1231     }
1232   /* The first zero should not have been consumed, but everything else.  */
1233   assert (end == 1);
1234   /* Write the sizes and convert back to offsets.  */
1235   unsigned start = 1;
1236   for (i = 0; i < s->entries; i++)
1237     {
1238       write_id (fp, s->ent2attr[i]);
1239       if (s->ent2attr[i])
1240         s->ent2attr[i] += start, start = s->ent2attr[i];
1241     }
1242
1243   if (s->entries
1244       && fwrite (s->flat_attrs + 1, s->attr_next_free - 1, 1, fp) != 1)
1245     {
1246       perror ("write error");
1247       exit (1);
1248     }
1249
1250   write_pages (fp, s);
1251 }
1252
1253 static unsigned int
1254 read_u32(FILE *fp)
1255 {
1256   int c, i;
1257   unsigned int x = 0;
1258
1259   for (i = 0; i < 4; i++)
1260     {
1261       c = getc(fp);
1262       if (c == EOF)
1263         {
1264           fprintf(stderr, "unexpected EOF\n");
1265           exit(1);
1266         }
1267       x = (x << 8) | c;
1268     }
1269   return x;
1270 }
1271
1272 static Id
1273 read_id(FILE *fp, Id max)
1274 {
1275   unsigned int x = 0;
1276   int c, i;
1277
1278   for (i = 0; i < 5; i++)
1279     {
1280       c = getc(fp);
1281       if (c == EOF)
1282         {
1283           fprintf(stderr, "unexpected EOF\n");
1284           exit(1);
1285         }
1286       if (!(c & 128))
1287         {
1288           x = (x << 7) | c;
1289           if (max && x >= max)
1290             {
1291               fprintf(stderr, "read_id: id too large (%u/%u)\n", x, max);
1292               exit(1);
1293             }
1294           return x;
1295         }
1296       x = (x << 7) ^ c ^ 128;
1297     }
1298   fprintf(stderr, "read_id: id too long\n");
1299   exit(1);
1300 }
1301
1302 static Id *
1303 read_idarray(FILE *fp, Id max, Id *map, Id *store, Id *end, int relative)
1304 {
1305   unsigned int x = 0;
1306   int c;
1307   Id old = 0;
1308   for (;;)
1309     {
1310       c = getc(fp);
1311       if (c == EOF)
1312         {
1313           pool_debug(mypool, SAT_FATAL, "unexpected EOF\n");
1314           exit(1);
1315         }
1316       if ((c & 128) == 0)
1317         {
1318           x = (x << 6) | (c & 63);
1319           if (relative)
1320             {
1321               if (x == 0 && c == 0x40)
1322                 {
1323                   /* prereq hack */
1324                   if (store == end)
1325                     {
1326                       pool_debug(mypool, SAT_FATAL, "read_idarray: array overflow\n");
1327                       exit(1);
1328                     }
1329                   *store++ = SOLVABLE_PREREQMARKER;
1330                   old = 0;
1331                   x = 0;
1332                   continue;
1333                 }
1334               x = (x - 1) + old;
1335               old = x;
1336             }
1337           if (x >= max)
1338             {
1339               pool_debug(mypool, SAT_FATAL, "read_idarray: id too large (%u/%u)\n", x, max);
1340               exit(1);
1341             }
1342           if (map)
1343             x = map[x];
1344           if (store == end)
1345             {
1346               pool_debug(mypool, SAT_FATAL, "read_idarray: array overflow\n");
1347               exit(1);
1348             }
1349           *store++ = x;
1350           if ((c & 64) == 0)
1351             {
1352               if (x == 0)       /* already have trailing zero? */
1353                 return store;
1354               if (store == end)
1355                 {
1356                   pool_debug(mypool, SAT_FATAL, "read_idarray: array overflow\n");
1357                   exit(1);
1358                 }
1359               *store++ = 0;
1360               return store;
1361             }
1362           x = 0;
1363           continue;
1364         }
1365       x = (x << 7) ^ c ^ 128;
1366     }
1367 }
1368
1369 /* Try to either setup on-demand paging (using FP as backing
1370    file), or in case that doesn't work (FP not seekable) slurps in
1371    all pages and deactivates paging.  */
1372 void
1373 read_or_setup_pages (FILE *fp, Attrstore *s)
1374 {
1375   unsigned int blobsz;
1376   unsigned int pagesz;
1377   unsigned int npages;
1378   unsigned int i;
1379   unsigned int can_seek;
1380   long cur_file_ofs;
1381   unsigned char buf[BLOB_PAGESIZE];
1382   blobsz = read_u32 (fp);
1383   pagesz = read_u32 (fp);
1384   if (pagesz != BLOB_PAGESIZE)
1385     {
1386       /* We could handle this by slurping in everything.  */
1387       fprintf (stderr, "non matching page size\n");
1388       exit (1);
1389     }
1390   can_seek = 1;
1391   if ((cur_file_ofs = ftell (fp)) < 0)
1392     can_seek = 0;
1393   clearerr (fp);
1394   fprintf (stderr, "can %sseek\n", can_seek ? "" : "NOT ");
1395   npages = (blobsz + BLOB_PAGESIZE - 1) / BLOB_PAGESIZE;
1396
1397   s->num_pages = npages;
1398   s->pages = xmalloc (npages * sizeof (s->pages[0]));
1399
1400   /* If we can't seek on our input we have to slurp in everything.  */
1401   if (!can_seek)
1402     {
1403       s->blob_next_free = blobsz;
1404       s->blob_store = xrealloc (s->blob_store, (s->blob_next_free + BLOB_BLOCK) &~BLOB_BLOCK);
1405     }
1406   for (i = 0; i < npages; i++)
1407     {
1408       unsigned int in_len = read_u32 (fp);
1409       unsigned int compressed = in_len & 1;
1410       Attrblobpage *p = s->pages + i;
1411       in_len >>= 1;
1412 #ifdef DEBUG_PAGING
1413       fprintf (stderr, "page %d: len %d (%scompressed)\n",
1414                i, in_len, compressed ? "" : "not ");
1415 #endif
1416       if (can_seek)
1417         {
1418           cur_file_ofs += 4;
1419           p->mapped_at = -1;
1420           p->file_offset = cur_file_ofs;
1421           p->file_size = in_len * 2 + compressed;
1422           if (fseek (fp, in_len, SEEK_CUR) < 0)
1423             {
1424               perror ("fseek");
1425               fprintf (stderr, "can't seek after we thought we can\n");
1426               /* We can't fall back to non-seeking behaviour as we already
1427                  read over some data pages without storing them away.  */
1428               exit (1);
1429             }
1430           cur_file_ofs += in_len;
1431         }
1432       else
1433         {
1434           unsigned int out_len;
1435           void *dest = s->blob_store + i * BLOB_PAGESIZE;
1436           p->mapped_at = i * BLOB_PAGESIZE;
1437           p->file_offset = 0;
1438           p->file_size = 0;
1439           /* We can't seek, so suck everything in.  */
1440           if (fread (compressed ? buf : dest, in_len, 1, fp) != 1)
1441             {
1442               perror ("fread");
1443               exit (1);
1444             }
1445           if (compressed)
1446             {
1447               out_len = unchecked_decompress_buf (buf, in_len,
1448                                                   dest, BLOB_PAGESIZE);
1449               if (out_len != BLOB_PAGESIZE
1450                   && i < npages - 1)
1451                 {
1452                   fprintf (stderr, "can't decompress\n");
1453                   exit (1);
1454                 }
1455             }
1456         }
1457     }
1458
1459   if (can_seek)
1460     {
1461       /* If we are here we were able to seek to all page
1462          positions, so activate paging by copying FP into our structure.
1463          We dup() the file, so that our callers can fclose() it and we
1464          still have it open.  But this means that we share file positions
1465          with the input filedesc.  So in case our caller reads it after us,
1466          and calls back into us we might change the file position unexpectedly
1467          to him.  */
1468       int fd = dup (fileno (fp));
1469       if (fd < 0)
1470         {
1471           /* Jeez!  What a bloody system, we can't dup() anymore.  */
1472           perror ("dup");
1473           exit (1);
1474         }
1475       /* XXX we don't close this yet anywhere.  */
1476       s->file = fdopen (fd, "r");
1477       if (!s->file)
1478         {
1479           /* My God!  What happened now?  */
1480           perror ("fdopen");
1481           exit (1);
1482         }
1483     }
1484 }
1485
1486 Attrstore *
1487 attr_store_read (FILE *fp, Pool *pool)
1488 {
1489   unsigned nentries;
1490   unsigned i;
1491   unsigned local_ssize;
1492   unsigned nstrings, nschemata;
1493   Attrstore *s = new_store (pool);
1494
1495   if (read_u32(fp) != ('S' << 24 | 'O' << 16 | 'L' << 8 | 'V'))
1496     {
1497       pool_debug(pool, SAT_FATAL, "not a SOLV file\n");
1498       exit(1);
1499     }
1500   unsigned solvversion = read_u32(fp);
1501   switch (solvversion)
1502     {
1503       case SOLV_VERSION_2:
1504         break;
1505       default:
1506         pool_debug(pool, SAT_FATAL, "unsupported SOLV version\n");
1507         exit(1);
1508     }
1509
1510   nstrings = read_u32(fp);
1511   read_u32(fp); //nrels
1512   nentries = read_u32(fp);
1513   s->nkeys = read_u32(fp);
1514   nschemata = read_u32(fp);
1515   read_u32(fp); //ninfo
1516   unsigned solvflags = read_u32(fp);
1517   if (!(solvflags & SOLV_FLAG_PACKEDSIZES))
1518     {
1519       pool_debug(pool, SAT_FATAL, "invalid attribute store\n");
1520       exit (1);
1521     }
1522
1523   /* Slightly hacky.  Our local string pool already contains "<NULL>" and
1524      "".  We write out the "" too, so we have to read over it.  We write it
1525      out to be compatible with the SOLV file and to not have to introduce
1526      merging and mapping the string IDs.  */
1527   local_ssize = read_u32 (fp) - 1;
1528   char *strsp = (char *)xrealloc(s->ss.stringspace, s->ss.sstrings + local_ssize + 1);
1529   Offset *str = (Offset *)xrealloc(s->ss.strings, (nstrings) * sizeof(Offset));
1530
1531   s->ss.stringspace = strsp;
1532   s->ss.strings = str;
1533   strsp += s->ss.sstrings;
1534
1535   unsigned char ignore_char = 1;
1536   if (fread(&ignore_char, 1, 1, fp) != 1
1537       || (local_ssize && fread(strsp, local_ssize, 1, fp) != 1)
1538       || ignore_char != 0)
1539     {
1540       perror ("read error while reading strings");
1541       exit(1);
1542     }
1543   strsp[local_ssize] = 0;
1544
1545   /* Don't build hashtable here, it will be built on demand by str2localid
1546      should we call that.  */
1547
1548   strsp = s->ss.stringspace;
1549   s->ss.nstrings = nstrings;
1550   for (i = 0; i < nstrings; i++)
1551     {
1552       str[i] = strsp - s->ss.stringspace;
1553       strsp += strlen (strsp) + 1;
1554     }
1555   s->ss.sstrings = strsp - s->ss.stringspace;
1556
1557   s->keys = xrealloc (s->keys, ((s->nkeys + KEY_BLOCK) & ~KEY_BLOCK) * sizeof (s->keys[0]));
1558   /* s->keys[0] is initialized in new_store.  */
1559   for (i = 1; i < s->nkeys; i++)
1560     {
1561       s->keys[i].name = read_id (fp, nstrings);
1562       s->keys[i].type = read_id (fp, TYPE_ATTR_TYPE_MAX + 1);
1563       s->keys[i].size = read_id (fp, 0);
1564
1565       /* Globalize the attribute names (they are local IDs right now).  */
1566       s->keys[i].name = str2id (s->pool, localid2str (s, s->keys[i].name), 1);
1567     }
1568
1569   s->szschemata = read_id (fp, 0);
1570   s->nschemata = 0;
1571   s->schemata = xmalloc (((s->szschemata + SCHEMA_BLOCK) & ~SCHEMA_BLOCK) * sizeof (s->schemata[0]));
1572   s->schemaofs = 0;
1573   Id *ids = s->schemata;
1574   //add_elem (s->schemaofs, s->nschemata, 0, SCHEMA_BLOCK);
1575   //*ids++ = 0;
1576   while (ids < s->schemata + s->szschemata)
1577     {
1578       add_elem (s->schemaofs, s->nschemata, ids - s->schemata, SCHEMA_BLOCK);
1579       ids = read_idarray (fp, s->nkeys, 0, ids, s->schemata + s->szschemata, 0);
1580     }
1581   assert (ids == s->schemata + s->szschemata);
1582   assert (nschemata == s->nschemata);
1583
1584   s->entries = nentries;
1585
1586   s->ent2attr = xmalloc (s->entries * sizeof (s->ent2attr[0]));
1587   int start = 1;
1588   for (i = 0; i < s->entries; i++)
1589     {
1590       int d = read_id (fp, 0);
1591       if (d)
1592         s->ent2attr[i] = start, start += d;
1593       else
1594         s->ent2attr[i] = 0;
1595     }
1596
1597   s->attr_next_free = start;
1598   s->flat_attrs = xmalloc (((s->attr_next_free + FLAT_ATTR_BLOCK) & ~FLAT_ATTR_BLOCK) * sizeof (s->flat_attrs[0]));
1599   s->flat_attrs[0] = 0;
1600   if (s->entries && fread (s->flat_attrs + 1, s->attr_next_free - 1, 1, fp) != 1)
1601     {
1602       perror ("read error");
1603       exit (1);
1604     }
1605
1606   read_or_setup_pages (fp, s);
1607
1608   s->packed = 1;
1609
1610   return s;
1611 }
1612
1613 void
1614 attr_store_search_s (Attrstore *s, const char *pattern, int flags, Id name, cb_attr_search_s cb)
1615 {
1616   unsigned int i;
1617   attr_iterator ai;
1618   regex_t regex;
1619   /* If we search for a glob, but we don't have a wildcard pattern, make this
1620      an exact string search.  */
1621   if ((flags & 7) == SEARCH_GLOB
1622       && !strpbrk (pattern, "?*["))
1623     flags = SEARCH_STRING | (flags & ~7);
1624   if ((flags & 7) == SEARCH_REGEX)
1625     {
1626       /* We feed multiple lines eventually (e.g. authors or descriptions),
1627          so set REG_NEWLINE.  */
1628       if (regcomp (&regex, pattern,
1629                    REG_EXTENDED | REG_NOSUB | REG_NEWLINE
1630                    | ((flags & SEARCH_NOCASE) ? REG_ICASE : 0)) != 0)
1631         return;
1632     }
1633   for (i = 0; i < s->entries; i++)
1634     FOR_ATTRS (s, i, &ai)
1635       {
1636         const char *str;
1637         if (name && name != ai.name)
1638           continue;
1639         str = 0;
1640         switch (ai.type)
1641           {
1642           case TYPE_ATTR_INT:
1643           case TYPE_ATTR_INTLIST:
1644             continue;
1645           case TYPE_ATTR_CHUNK:
1646             if (!(flags & SEARCH_BLOBS))
1647               continue;
1648             str = attr_retrieve_blob (s, ai.as_chunk[0], ai.as_chunk[1]);
1649             break;
1650           case TYPE_ATTR_STRING:
1651             str = ai.as_string;
1652             break;
1653           case TYPE_ATTR_LOCALIDS:
1654             {
1655               Id val;
1656               get_num (ai.as_numlist, val);
1657               if (val)
1658                 str = localid2str (s, val);
1659               break;
1660             }
1661           default:
1662             break;
1663           }
1664         while (str)
1665           {
1666             unsigned int match = 0;
1667             switch (flags & 7)
1668             {
1669               case SEARCH_SUBSTRING:
1670                 if (flags & SEARCH_NOCASE)
1671                   match = !! strcasestr (str, pattern);
1672                 else
1673                   match = !! strstr (str, pattern);
1674                 break;
1675               case SEARCH_STRING:
1676                 if (flags & SEARCH_NOCASE)
1677                   match = ! strcasecmp (str, pattern);
1678                 else
1679                   match = ! strcmp (str, pattern);
1680                 break;
1681               case SEARCH_GLOB:
1682                 match = ! fnmatch (pattern, str,
1683                                    (flags & SEARCH_NOCASE) ? FNM_CASEFOLD : 0);
1684                 break;
1685               case SEARCH_REGEX:
1686                 match = ! regexec (&regex, str, 0, NULL, 0);
1687                 break;
1688               default:
1689                 break;
1690             }
1691             if (match)
1692               cb (s, i, ai.name, str);
1693             if (ai.type != TYPE_ATTR_LOCALIDS)
1694               break;
1695             Id val;
1696             get_num (ai.as_numlist, val);
1697             if (!val)
1698               break;
1699             str = localid2str (s, val);
1700           }
1701       }
1702   if ((flags & 7) == SEARCH_REGEX)
1703     regfree (&regex);
1704 }
1705
1706 #ifdef MAIN
1707 int
1708 main (void)
1709 {
1710   Pool *pool = pool_create ();
1711   Attrstore *s = new_store (pool);
1712   unsigned int id1 = new_entry (s);
1713   unsigned int id2 = new_entry (s);
1714   unsigned int id3 = new_entry (s);
1715   unsigned int id4 = new_entry (s);
1716   add_attr_int (s, id1, str2id (s, "name1", 1), 42);
1717   add_attr_chunk (s, id1, str2id (s->pool, "name2", 1), 9876, 1024);
1718   add_attr_string (s, id1, str2id (s->pool, "name3", 1), "hallo");
1719   add_attr_int (s, id1, str2id (s->pool, "name1", 1), 43);
1720   add_attr_intlist_int (s, id1, str2id (s->pool, "intlist1", 1), 3);
1721   add_attr_intlist_int (s, id1, str2id (s->pool, "intlist1", 1), 14);
1722   add_attr_intlist_int (s, id1, str2id (s->pool, "intlist1", 1), 1);
1723   add_attr_intlist_int (s, id1, str2id (s->pool, "intlist1", 1), 59);
1724   add_attr_localids_id (s, id1, str2id (s->pool, "l_ids1", 1), str2localid (s, "one", 1));
1725   add_attr_localids_id (s, id1, str2id (s->pool, "l_ids1", 1), str2localid (s, "two", 1));
1726   add_attr_localids_id (s, id1, str2id (s->pool, "l_ids1", 1), str2localid (s, "three", 1));
1727   add_attr_localids_id (s, id1, str2id (s->pool, "l_ids2", 1), str2localid (s, "three", 1));
1728   add_attr_localids_id (s, id1, str2id (s->pool, "l_ids2", 1), str2localid (s, "two", 1));
1729   add_attr_localids_id (s, id1, str2id (s->pool, "l_ids2", 1), str2localid (s, "one", 1));
1730   write_attr_store (stdout, s);
1731   return 0;
1732 }
1733 #endif