- add repodata_write and repo_empty functions
[platform/upstream/libsolv.git] / src / repodata.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 /*
9  * repodata.c
10  *
11  * Manage data coming from one repository
12  *
13  * a repository can contain multiple repodata entries, consisting of
14  * different sets of keys and different sets of solvables
15  */
16
17 #define _GNU_SOURCE
18 #include <string.h>
19 #include <fnmatch.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <assert.h>
25 #include <regex.h>
26
27 #include "repo.h"
28 #include "pool.h"
29 #include "poolid_private.h"
30 #include "util.h"
31 #include "hash.h"
32 #include "chksum.h"
33
34 #include "repopack.h"
35 #include "repopage.h"
36
37 extern unsigned int compress_buf (const unsigned char *in, unsigned int in_len,
38                                   unsigned char *out, unsigned int out_len);
39 extern unsigned int unchecked_decompress_buf (const unsigned char *in,
40                                               unsigned int in_len,
41                                               unsigned char *out,
42                                               unsigned int out_len);
43
44 #define REPODATA_BLOCK 255
45
46
47 void
48 repodata_initdata(Repodata *data, Repo *repo, int localpool)
49 {
50   memset(data, 0, sizeof (*data));
51   data->repo = repo;
52   data->localpool = localpool;
53   if (localpool)
54     stringpool_init_empty(&data->spool);
55   data->keys = sat_calloc(1, sizeof(Repokey));
56   data->nkeys = 1;
57   data->schemata = sat_calloc(1, sizeof(Id));
58   data->schemadata = sat_calloc(1, sizeof(Id));
59   data->nschemata = 1;
60   data->schemadatalen = 1;
61   repopagestore_init(&data->store);
62 }
63
64 void
65 repodata_freedata(Repodata *data)
66 {
67   int i;
68
69   sat_free(data->keys);
70
71   sat_free(data->schemata);
72   sat_free(data->schemadata);
73   sat_free(data->schematahash);
74
75   stringpool_free(&data->spool);
76   dirpool_free(&data->dirpool);
77
78   sat_free(data->mainschemaoffsets);
79   sat_free(data->incoredata);
80   sat_free(data->incoreoffset);
81   sat_free(data->verticaloffset);
82
83   repopagestore_free(&data->store);
84
85   sat_free(data->vincore);
86
87   if (data->attrs)
88     for (i = 0; i < data->end - data->start; i++)
89       sat_free(data->attrs[i]);
90   sat_free(data->attrs);
91   if (data->xattrs)
92     for (i = 0; i < data->nxattrs; i++)
93       sat_free(data->xattrs[i]);
94   sat_free(data->xattrs);
95
96   sat_free(data->attrdata);
97   sat_free(data->attriddata);
98 }
99
100 Repodata *
101 repodata_create(Repo *repo, int localpool)
102 {
103   Repodata *data;
104
105   repo->nrepodata++;
106   repo->repodata = sat_realloc2(repo->repodata, repo->nrepodata, sizeof(*data));
107   data = repo->repodata + repo->nrepodata - 1;
108   repodata_initdata(data, repo, localpool);
109   return data;
110 }
111
112 void
113 repodata_free(Repodata *data)
114 {
115   Repo *repo = data->repo;
116   int i = data - repo->repodata;
117   repodata_freedata(data);
118   if (i < repo->nrepodata - 1)
119     memmove(repo->repodata + i, repo->repodata + i + 1, (repo->nrepodata - 1 - i) * sizeof(Repodata));
120   repo->nrepodata--;
121 }
122
123
124 /***************************************************************
125  * key pool management
126  */
127
128 /* this is not so time critical that we need a hash, so we do a simple
129  * linear search */
130 Id
131 repodata_key2id(Repodata *data, Repokey *key, int create)
132 {
133   Id keyid;
134
135   for (keyid = 1; keyid < data->nkeys; keyid++)
136     if (data->keys[keyid].name == key->name && data->keys[keyid].type == key->type)
137       {
138         if ((key->type == REPOKEY_TYPE_CONSTANT || key->type == REPOKEY_TYPE_CONSTANTID) && key->size != data->keys[keyid].size)
139           continue;
140         break;
141       }
142   if (keyid == data->nkeys)
143     {
144       if (!create)
145         return 0;
146       /* allocate new key */
147       data->keys = sat_realloc2(data->keys, data->nkeys + 1, sizeof(Repokey));
148       data->keys[data->nkeys++] = *key;
149       if (data->verticaloffset)
150         {
151           data->verticaloffset = sat_realloc2(data->verticaloffset, data->nkeys, sizeof(Id));
152           data->verticaloffset[data->nkeys - 1] = 0;
153         }
154       data->keybits[(key->name >> 3) & (sizeof(data->keybits) - 1)] |= 1 << (key->name & 7);
155     }
156   return keyid;
157 }
158
159
160 /***************************************************************
161  * schema pool management
162  */
163
164 #define SCHEMATA_BLOCK 31
165 #define SCHEMATADATA_BLOCK 255
166
167 Id
168 repodata_schema2id(Repodata *data, Id *schema, int create)
169 {
170   int h, len, i;
171   Id *sp, cid;
172   Id *schematahash;
173
174   if ((schematahash = data->schematahash) == 0)
175     {
176       data->schematahash = schematahash = sat_calloc(256, sizeof(Id));
177       for (i = 0; i < data->nschemata; i++)
178         {
179           for (sp = data->schemadata + data->schemata[i], h = 0; *sp; len++)
180             h = h * 7 + *sp++;
181           h &= 255;
182           schematahash[h] = i + 1;
183         }
184       data->schemadata = sat_extend_resize(data->schemadata, data->schemadatalen, sizeof(Id), SCHEMATADATA_BLOCK);
185       data->schemata = sat_extend_resize(data->schemata, data->nschemata, sizeof(Id), SCHEMATA_BLOCK);
186     }
187
188   for (sp = schema, len = 0, h = 0; *sp; len++)
189     h = h * 7 + *sp++;
190   h &= 255;
191   len++;
192
193   cid = schematahash[h];
194   if (cid)
195     {
196       cid--;
197       if (!memcmp(data->schemadata + data->schemata[cid], schema, len * sizeof(Id)))
198         return cid;
199       /* cache conflict */
200       for (cid = 0; cid < data->nschemata; cid++)
201         if (!memcmp(data->schemadata + data->schemata[cid], schema, len * sizeof(Id)))
202           return cid;
203     }
204   /* a new one */
205   if (!create)
206     return 0;
207   data->schemadata = sat_extend(data->schemadata, data->schemadatalen, len, sizeof(Id), SCHEMATADATA_BLOCK);
208   data->schemata = sat_extend(data->schemata, data->nschemata, 1, sizeof(Id), SCHEMATA_BLOCK);
209   /* add schema */
210   memcpy(data->schemadata + data->schemadatalen, schema, len * sizeof(Id));
211   data->schemata[data->nschemata] = data->schemadatalen;
212   data->schemadatalen += len;
213   schematahash[h] = data->nschemata + 1;
214 #if 0
215 fprintf(stderr, "schema2id: new schema\n");
216 #endif
217   return data->nschemata++;
218 }
219
220 void
221 repodata_free_schemahash(Repodata *data)
222 {
223   data->schematahash = sat_free(data->schematahash);
224   /* shrink arrays */
225   data->schemata = sat_realloc2(data->schemata, data->nschemata, sizeof(Id));
226   data->schemadata = sat_realloc2(data->schemadata, data->schemadatalen, sizeof(Id));
227 }
228
229
230 /***************************************************************
231  * dir pool management
232  */
233
234 Id
235 repodata_str2dir(Repodata *data, const char *dir, int create)
236 {
237   Id id, parent;
238   const char *dire;
239
240   parent = 0;
241   while (*dir == '/' && dir[1] == '/')
242     dir++;
243   if (*dir == '/' && !dir[1])
244     return 1;
245   while (*dir)
246     {
247       dire = strchrnul(dir, '/');
248       if (data->localpool)
249         id = stringpool_strn2id(&data->spool, dir, dire - dir, create);
250       else
251         id = strn2id(data->repo->pool, dir, dire - dir, create);
252       if (!id)
253         return 0;
254       parent = dirpool_add_dir(&data->dirpool, parent, id, create);
255       if (!parent)
256         return 0;
257       if (!*dire)
258         break;
259       dir = dire + 1;
260       while (*dir == '/')
261         dir++;
262     }
263   return parent;
264 }
265
266 const char *
267 repodata_dir2str(Repodata *data, Id did, const char *suf)
268 {
269   Pool *pool = data->repo->pool;
270   int l = 0;
271   Id parent, comp;
272   const char *comps;
273   char *p;
274
275   if (!did)
276     return suf ? suf : "";
277   parent = did;
278   while (parent)
279     {
280       comp = dirpool_compid(&data->dirpool, parent);
281       comps = stringpool_id2str(data->localpool ? &data->spool : &pool->ss, comp);
282       l += strlen(comps);
283       parent = dirpool_parent(&data->dirpool, parent);
284       if (parent)
285         l++;
286     }
287   if (suf)
288     l += strlen(suf) + 1;
289   p = pool_alloctmpspace(pool, l + 1) + l;
290   *p = 0;
291   if (suf)
292     {
293       p -= strlen(suf);
294       strcpy(p, suf);
295       *--p = '/';
296     }
297   parent = did;
298   while (parent)
299     {
300       comp = dirpool_compid(&data->dirpool, parent);
301       comps = stringpool_id2str(data->localpool ? &data->spool : &pool->ss, comp);
302       l = strlen(comps);
303       p -= l;
304       strncpy(p, comps, l);
305       parent = dirpool_parent(&data->dirpool, parent);
306       if (parent)
307         *--p = '/';
308     }
309   return p;
310 }
311
312
313 /***************************************************************
314  * data management
315  */
316
317 static inline unsigned char *
318 data_skip_schema(Repodata *data, unsigned char *dp, Id schema)
319 {
320   Id *keyp = data->schemadata + data->schemata[schema];
321   for (; *keyp; keyp++)
322     dp = data_skip_key(data, dp, data->keys + *keyp);
323   return dp;
324 }
325
326 unsigned char *
327 data_skip_key(Repodata *data, unsigned char *dp, Repokey *key)
328 {
329   int nentries, schema;
330   switch(key->type)
331     {
332     case REPOKEY_TYPE_FIXARRAY:
333       dp = data_read_id(dp, &nentries);
334       if (!nentries)
335         return dp;
336       dp = data_read_id(dp, &schema);
337       while (nentries--)
338         dp = data_skip_schema(data, dp, schema);
339       return dp;
340     case REPOKEY_TYPE_FLEXARRAY:
341       dp = data_read_id(dp, &nentries);
342       while (nentries--)
343         {
344           dp = data_read_id(dp, &schema);
345           dp = data_skip_schema(data, dp, schema);
346         }
347       return dp;
348     default:
349       if (key->storage == KEY_STORAGE_INCORE)
350         dp = data_skip(dp, key->type);
351       else if (key->storage == KEY_STORAGE_VERTICAL_OFFSET)
352         {
353           dp = data_skip(dp, REPOKEY_TYPE_ID);
354           dp = data_skip(dp, REPOKEY_TYPE_ID);
355         }
356       return dp;
357     }
358 }
359
360 static unsigned char *
361 forward_to_key(Repodata *data, Id keyid, Id *keyp, unsigned char *dp)
362 {
363   Id k;
364
365   if (!keyid)
366     return 0;
367   if (data->mainschemaoffsets && dp == data->incoredata + data->mainschemaoffsets[0] && keyp == data->schemadata + data->schemata[data->mainschema])
368     {
369       int i;
370       for (i = 0; (k = *keyp++) != 0; i++)
371         if (k == keyid)
372           return data->incoredata + data->mainschemaoffsets[i];
373       return 0;
374     }
375   while ((k = *keyp++) != 0)
376     {
377       if (k == keyid)
378         return dp;
379       if (data->keys[k].storage == KEY_STORAGE_VERTICAL_OFFSET)
380         {
381           dp = data_skip(dp, REPOKEY_TYPE_ID);  /* skip offset */
382           dp = data_skip(dp, REPOKEY_TYPE_ID);  /* skip length */
383           continue;
384         }
385       if (data->keys[k].storage != KEY_STORAGE_INCORE)
386         continue;
387       dp = data_skip_key(data, dp, data->keys + k);
388     }
389   return 0;
390 }
391
392 static unsigned char *
393 get_vertical_data(Repodata *data, Repokey *key, Id off, Id len)
394 {
395   unsigned char *dp;
396   if (!len)
397     return 0;
398   if (off >= data->lastverticaloffset)
399     {
400       off -= data->lastverticaloffset;
401       if (off + len > data->vincorelen)
402         return 0;
403       return data->vincore + off;
404     }
405   if (off + len > key->size)
406     return 0;
407   /* we now have the offset, go into vertical */
408   off += data->verticaloffset[key - data->keys];
409   /* fprintf(stderr, "key %d page %d\n", key->name, off / BLOB_PAGESIZE); */
410   dp = repopagestore_load_page_range(&data->store, off / BLOB_PAGESIZE, (off + len - 1) / BLOB_PAGESIZE);
411   if (dp)
412     dp += off % BLOB_PAGESIZE;
413   return dp;
414 }
415
416 static inline unsigned char *
417 get_data(Repodata *data, Repokey *key, unsigned char **dpp, int advance)
418 {
419   unsigned char *dp = *dpp;
420
421   if (!dp)
422     return 0;
423   if (key->storage == KEY_STORAGE_INCORE)
424     {
425       if (advance)
426         *dpp = data_skip_key(data, dp, key);
427       return dp;
428     }
429   else if (key->storage == KEY_STORAGE_VERTICAL_OFFSET)
430     {
431       Id off, len;
432       dp = data_read_id(dp, &off);
433       dp = data_read_id(dp, &len);
434       if (advance)
435         *dpp = dp;
436       return get_vertical_data(data, key, off, len);
437     }
438   return 0;
439 }
440
441 static int
442 load_repodata(Repodata *data)
443 {
444   if (data->loadcallback)
445     {
446       data->loadcallback(data);
447       if (data->state == REPODATA_AVAILABLE)
448         return 1;
449     }
450   data->state = REPODATA_ERROR;
451   return 0;
452 }
453
454 static inline int
455 maybe_load_repodata(Repodata *data, Id keyname)
456 {
457   if (keyname && !repodata_precheck_keyname(data, keyname))
458     return 0;   /* do not bother... */
459   switch(data->state)
460     {
461     case REPODATA_STUB:
462       if (keyname)
463         {
464           int i;
465           for (i = 0; i < data->nkeys; i++)
466             if (keyname == data->keys[i].name)
467               break;
468           if (i == data->nkeys)
469             return 0;
470         }
471       return load_repodata(data);
472     case REPODATA_ERROR:
473       return 0;
474     case REPODATA_AVAILABLE:
475     case REPODATA_LOADING:
476       return 1;
477     default:
478       data->state = REPODATA_ERROR;
479       return 0;
480     }
481 }
482
483 static inline unsigned char *
484 solvid2data(Repodata *data, Id solvid, Id *schemap)
485 {
486   unsigned char *dp = data->incoredata;
487   if (!dp)
488     return 0;
489   if (solvid == SOLVID_META)    /* META */
490     dp += 1;
491   else if (solvid == SOLVID_POS)        /* META */
492     {
493       Pool *pool = data->repo->pool;
494       if (data->repo != pool->pos.repo)
495         return 0;
496       if (data != data->repo->repodata + pool->pos.repodataid)
497         return 0;
498       *schemap = pool->pos.schema;
499       return data->incoredata + pool->pos.dp;
500     }
501   else
502     {
503       if (solvid < data->start || solvid >= data->end)
504         return 0;
505       dp += data->incoreoffset[solvid - data->start];
506     }
507   return data_read_id(dp, schemap);
508 }
509
510 /************************************************************************
511  * data lookup
512  */
513
514 static inline unsigned char *
515 find_key_data(Repodata *data, Id solvid, Id keyname, Repokey **keypp)
516 {
517   unsigned char *dp;
518   Id schema, *keyp, *kp;
519   Repokey *key;
520
521   if (!maybe_load_repodata(data, keyname))
522     return 0;
523   dp = solvid2data(data, solvid, &schema);
524   if (!dp)
525     return 0;
526   keyp = data->schemadata + data->schemata[schema];
527   for (kp = keyp; *kp; kp++)
528     if (data->keys[*kp].name == keyname)
529       break;
530   if (!*kp)
531     return 0;
532   *keypp = key = data->keys + *kp;
533   if (key->type == REPOKEY_TYPE_VOID || key->type == REPOKEY_TYPE_CONSTANT || key->type == REPOKEY_TYPE_CONSTANTID)
534     return dp;  /* no need to forward... */
535   dp = forward_to_key(data, *kp, keyp, dp);
536   if (!dp)
537     return 0;
538   return get_data(data, key, &dp, 0);
539 }
540
541
542 Id
543 repodata_lookup_id(Repodata *data, Id solvid, Id keyname)
544 {
545   unsigned char *dp;
546   Repokey *key;
547   Id id;
548
549   dp = find_key_data(data, solvid, keyname, &key);
550   if (!dp)
551     return 0;
552   if (key->type == REPOKEY_TYPE_CONSTANTID)
553     return key->size;
554   if (key->type != REPOKEY_TYPE_ID)
555     return 0;
556   dp = data_read_id(dp, &id);
557   return id;
558 }
559
560 Id
561 repodata_globalize_id(Repodata *data, Id id, int create)
562 {
563   if (!id || !data || !data->localpool)
564     return id;
565   return str2id(data->repo->pool, stringpool_id2str(&data->spool, id), create);
566 }
567
568 const char *
569 repodata_lookup_str(Repodata *data, Id solvid, Id keyname)
570 {
571   unsigned char *dp;
572   Repokey *key;
573   Id id;
574
575   dp = find_key_data(data, solvid, keyname, &key);
576   if (!dp)
577     return 0;
578   if (key->type == REPOKEY_TYPE_STR)
579     return (const char *)dp;
580   if (key->type == REPOKEY_TYPE_CONSTANTID)
581     return id2str(data->repo->pool, key->size);
582   if (key->type == REPOKEY_TYPE_ID)
583     dp = data_read_id(dp, &id);
584   else
585     return 0;
586   if (data->localpool)
587     return data->spool.stringspace + data->spool.strings[id];
588   return id2str(data->repo->pool, id);
589 }
590
591 int
592 repodata_lookup_num(Repodata *data, Id solvid, Id keyname, unsigned int *value)
593 {
594   unsigned char *dp;
595   Repokey *key;
596   KeyValue kv;
597
598   *value = 0;
599   dp = find_key_data(data, solvid, keyname, &key);
600   if (!dp)
601     return 0;
602   if (key->type == REPOKEY_TYPE_NUM
603       || key->type == REPOKEY_TYPE_U32
604       || key->type == REPOKEY_TYPE_CONSTANT)
605     {
606       dp = data_fetch(dp, &kv, key);
607       *value = kv.num;
608       return 1;
609     }
610   return 0;
611 }
612
613 int
614 repodata_lookup_void(Repodata *data, Id solvid, Id keyname)
615 {
616   Id schema;
617   Id *keyp;
618   unsigned char *dp;
619
620   if (!maybe_load_repodata(data, keyname))
621     return 0;
622   dp = solvid2data(data, solvid, &schema);
623   if (!dp)
624     return 0;
625   /* can't use find_key_data as we need to test the type */
626   for (keyp = data->schemadata + data->schemata[schema]; *keyp; keyp++)
627     if (data->keys[*keyp].name == keyname && data->keys[*keyp].type == REPOKEY_TYPE_VOID)
628       return 1;
629   return 0;
630 }
631
632 const unsigned char *
633 repodata_lookup_bin_checksum(Repodata *data, Id solvid, Id keyname, Id *typep)
634 {
635   unsigned char *dp;
636   Repokey *key;
637
638   dp = find_key_data(data, solvid, keyname, &key);
639   if (!dp)
640     return 0;
641   *typep = key->type;
642   return dp;
643 }
644
645
646 /************************************************************************
647  * data search
648  */
649
650
651 int
652 repodata_stringify(Pool *pool, Repodata *data, Repokey *key, KeyValue *kv, int flags)
653 {
654   switch (key->type)
655     {
656     case REPOKEY_TYPE_ID:
657     case REPOKEY_TYPE_CONSTANTID:
658     case REPOKEY_TYPE_IDARRAY:
659       if (data && data->localpool)
660         kv->str = stringpool_id2str(&data->spool, kv->id);
661       else
662         kv->str = id2str(pool, kv->id);
663       if ((flags & SEARCH_SKIP_KIND) != 0 && key->storage == KEY_STORAGE_SOLVABLE)
664         {
665           const char *s;
666           for (s = kv->str; *s >= 'a' && *s <= 'z'; s++)
667             ;
668           if (*s == ':' && s > kv->str)
669             kv->str = s + 1;
670         }
671       return 1;
672     case REPOKEY_TYPE_STR:
673       return 1;
674     case REPOKEY_TYPE_DIRSTRARRAY:
675       if (!(flags & SEARCH_FILES))
676         return 1;       /* match just the basename */
677       /* Put the full filename into kv->str.  */
678       kv->str = repodata_dir2str(data, kv->id, kv->str);
679       /* And to compensate for that put the "empty" directory into
680          kv->id, so that later calls to repodata_dir2str on this data
681          come up with the same filename again.  */
682       kv->id = 0;
683       return 1;
684     case REPOKEY_TYPE_MD5:
685     case REPOKEY_TYPE_SHA1:
686     case REPOKEY_TYPE_SHA256:
687       if (!(flags & SEARCH_CHECKSUMS))
688         return 0;       /* skip em */
689       kv->str = repodata_chk2str(data, key->type, (const unsigned char *)kv->str);
690       return 1;
691     default:
692       return 0;
693     }
694 }
695
696
697 struct subschema_data {
698   Solvable *s;
699   void *cbdata;
700   KeyValue *parent;
701 };
702
703 /* search a specific repodata */
704 void
705 repodata_search(Repodata *data, Id solvid, Id keyname, int flags, int (*callback)(void *cbdata, Solvable *s, Repodata *data, Repokey *key, KeyValue *kv), void *cbdata)
706 {
707   Id schema;
708   Repokey *key;
709   Id keyid, *kp, *keyp;
710   unsigned char *dp, *ddp;
711   int onekey = 0;
712   int stop;
713   KeyValue kv;
714   Solvable *s;
715
716   if (!maybe_load_repodata(data, keyname))
717     return;
718   if (solvid == SOLVID_SUBSCHEMA)
719     {
720       struct subschema_data *subd = cbdata;
721       cbdata = subd->cbdata;
722       s = subd->s;
723       schema = subd->parent->id;
724       dp = (unsigned char *)subd->parent->str;
725       kv.parent = subd->parent;
726     }
727   else
728     {
729       schema = 0;
730       dp = solvid2data(data, solvid, &schema);
731       if (!dp)
732         return;
733       s = data->repo->pool->solvables + solvid;
734       kv.parent = 0;
735     }
736   keyp = data->schemadata + data->schemata[schema];
737   if (keyname)
738     {
739       /* search for a specific key */
740       for (kp = keyp; *kp; kp++)
741         if (data->keys[*kp].name == keyname)
742           break;
743       if (!*kp)
744         return;
745       dp = forward_to_key(data, *kp, keyp, dp);
746       if (!dp)
747         return;
748       keyp = kp;
749       onekey = 1;
750     }
751   while ((keyid = *keyp++) != 0)
752     {
753       stop = 0;
754       key = data->keys + keyid;
755       ddp = get_data(data, key, &dp, *keyp ? 1 : 0);
756
757       if (key->type == REPOKEY_TYPE_FLEXARRAY || key->type == REPOKEY_TYPE_FIXARRAY)
758         {
759           struct subschema_data subd;
760           int nentries;
761           Id schema = 0;
762
763           subd.cbdata = cbdata;
764           subd.s = s;
765           subd.parent = &kv;
766           ddp = data_read_id(ddp, &nentries);
767           kv.num = nentries;
768           kv.entry = 0;
769           kv.eof = 0;
770           while (ddp && nentries > 0)
771             {
772               if (!--nentries)
773                 kv.eof = 1;
774               if (key->type == REPOKEY_TYPE_FLEXARRAY || !kv.entry)
775                 ddp = data_read_id(ddp, &schema);
776               kv.id = schema;
777               kv.str = (char *)ddp;
778               stop = callback(cbdata, s, data, key, &kv);
779               if (stop > SEARCH_NEXT_KEY)
780                 return;
781               if (stop && stop != SEARCH_ENTERSUB)
782                 break;
783               if ((flags & SEARCH_SUB) != 0 || stop == SEARCH_ENTERSUB)
784                 repodata_search(data, SOLVID_SUBSCHEMA, 0, flags, callback, &subd);
785               ddp = data_skip_schema(data, ddp, schema);
786               kv.entry++;
787             }
788           if (!nentries && (flags & SEARCH_ARRAYSENTINEL) != 0)
789             {
790               /* sentinel */
791               kv.eof = 2;
792               kv.str = (char *)ddp;
793               stop = callback(cbdata, s, data, key, &kv);
794               if (stop > SEARCH_NEXT_KEY)
795                 return;
796             }
797           if (onekey)
798             return;
799           continue;
800         }
801       kv.entry = 0;
802       do
803         {
804           ddp = data_fetch(ddp, &kv, key);
805           if (!ddp)
806             break;
807           stop = callback(cbdata, s, data, key, &kv);
808           kv.entry++;
809         }
810       while (!kv.eof && !stop);
811       if (onekey || stop > SEARCH_NEXT_KEY)
812         return;
813     }
814 }
815
816 void
817 repodata_setpos_kv(Repodata *data, KeyValue *kv)
818 {
819   Pool *pool = data->repo->pool;
820   if (!kv)
821     pool_clear_pos(pool);
822   else
823     {
824       pool->pos.repo = data->repo;
825       pool->pos.repodataid = data - data->repo->repodata;
826       pool->pos.dp = (unsigned char *)kv->str - data->incoredata;
827       pool->pos.schema = kv->id;
828     }
829 }
830
831 /************************************************************************
832  * data iterator functions
833  */
834
835 static Repokey solvablekeys[RPM_RPMDBID - SOLVABLE_NAME + 1] = {
836   { SOLVABLE_NAME,        REPOKEY_TYPE_ID, 0, KEY_STORAGE_SOLVABLE },
837   { SOLVABLE_ARCH,        REPOKEY_TYPE_ID, 0, KEY_STORAGE_SOLVABLE },
838   { SOLVABLE_EVR,         REPOKEY_TYPE_ID, 0, KEY_STORAGE_SOLVABLE },
839   { SOLVABLE_VENDOR,      REPOKEY_TYPE_ID, 0, KEY_STORAGE_SOLVABLE },
840   { SOLVABLE_PROVIDES,    REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
841   { SOLVABLE_OBSOLETES,   REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
842   { SOLVABLE_CONFLICTS,   REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
843   { SOLVABLE_REQUIRES,    REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
844   { SOLVABLE_RECOMMENDS,  REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
845   { SOLVABLE_SUGGESTS,    REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
846   { SOLVABLE_SUPPLEMENTS, REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
847   { SOLVABLE_ENHANCES,    REPOKEY_TYPE_IDARRAY, 0, KEY_STORAGE_SOLVABLE },
848   { RPM_RPMDBID,          REPOKEY_TYPE_U32, 0, KEY_STORAGE_SOLVABLE },
849 };
850
851 static inline Id *
852 solvabledata_fetch(Solvable *s, KeyValue *kv, Id keyname)
853 {
854   kv->id = keyname;
855   switch (keyname)
856     {
857     case SOLVABLE_NAME:
858       kv->eof = 1;
859       return &s->name;
860     case SOLVABLE_ARCH:
861       kv->eof = 1;
862       return &s->arch;
863     case SOLVABLE_EVR:
864       kv->eof = 1;
865       return &s->evr;
866     case SOLVABLE_VENDOR:
867       kv->eof = 1;
868       return &s->vendor;
869     case SOLVABLE_PROVIDES:
870       kv->eof = 0;
871       return s->provides ? s->repo->idarraydata + s->provides : 0;
872     case SOLVABLE_OBSOLETES:
873       kv->eof = 0;
874       return s->obsoletes ? s->repo->idarraydata + s->obsoletes : 0;
875     case SOLVABLE_CONFLICTS:
876       kv->eof = 0;
877       return s->conflicts ? s->repo->idarraydata + s->conflicts : 0;
878     case SOLVABLE_REQUIRES:
879       kv->eof = 0;
880       return s->requires ? s->repo->idarraydata + s->requires : 0;
881     case SOLVABLE_RECOMMENDS:
882       kv->eof = 0;
883       return s->recommends ? s->repo->idarraydata + s->recommends : 0;
884     case SOLVABLE_SUPPLEMENTS:
885       kv->eof = 0;
886       return s->supplements ? s->repo->idarraydata + s->supplements : 0;
887     case SOLVABLE_SUGGESTS:
888       kv->eof = 0;
889       return s->suggests ? s->repo->idarraydata + s->suggests : 0;
890     case SOLVABLE_ENHANCES:
891       kv->eof = 0;
892       return s->enhances ? s->repo->idarraydata + s->enhances : 0;
893     case RPM_RPMDBID:
894       kv->eof = 1;
895       return s->repo->rpmdbid ? s->repo->rpmdbid + (s - s->repo->pool->solvables - s->repo->start) : 0;
896     default:
897       return 0;
898     }
899 }
900
901 int
902 datamatcher_init(Datamatcher *ma, const char *match, int flags)
903 {
904   ma->match = match;
905   ma->flags = flags;
906   ma->error = 0;
907   ma->matchdata = 0;
908   if ((flags & SEARCH_STRINGMASK) == SEARCH_REGEX)
909     {
910       ma->matchdata = sat_calloc(1, sizeof(regex_t));
911       ma->error = regcomp((regex_t *)ma->matchdata, match, REG_EXTENDED | REG_NOSUB | REG_NEWLINE | ((flags & SEARCH_NOCASE) ? REG_ICASE : 0));
912       if (ma->error)
913         {
914           sat_free(ma->matchdata);
915           ma->flags = (flags & ~SEARCH_STRINGMASK) | SEARCH_ERROR;
916         }
917     }
918   return ma->error;
919 }
920
921 void
922 datamatcher_free(Datamatcher *ma)
923 {
924   if ((ma->flags & SEARCH_STRINGMASK) == SEARCH_REGEX && ma->matchdata)
925     {
926       regfree(ma->matchdata);
927       ma->matchdata = sat_free(ma->matchdata);
928     }
929 }
930
931 int
932 datamatcher_match(Datamatcher *ma, const char *str)
933 {
934   int l;
935   switch ((ma->flags & SEARCH_STRINGMASK))
936     {
937     case SEARCH_SUBSTRING:
938       if (ma->flags & SEARCH_NOCASE)
939         {
940           if (!strcasestr(str, ma->match))
941             return 0;
942         }
943       else
944         {
945           if (!strstr(str, ma->match))
946             return 0;
947         }
948       break;
949     case SEARCH_STRING:
950       if (ma->flags & SEARCH_NOCASE)
951         {
952           if (strcasecmp(ma->match, str))
953             return 0;
954         }
955       else
956         {
957           if (strcmp(ma->match, str))
958             return 0;
959         }
960       break;
961     case SEARCH_STRINGSTART:
962       if (ma->flags & SEARCH_NOCASE)
963         {
964           if (strncasecmp(ma->match, str, strlen(ma->match)))
965             return 0;
966         }
967       else
968         {
969           if (strncmp(ma->match, str, strlen(ma->match)))
970             return 0;
971         }
972       break;
973     case SEARCH_STRINGEND:
974       l = strlen(str) - strlen(ma->match);
975       if (l < 0)
976         return 0;
977       if (ma->flags & SEARCH_NOCASE)
978         {
979           if (strcasecmp(ma->match, str + l))
980             return 0;
981         }
982       else
983         {
984           if (strcmp(ma->match, str + l))
985             return 0;
986         }
987       break;
988     case SEARCH_GLOB:
989       if (fnmatch(ma->match, str, (ma->flags & SEARCH_NOCASE) ? FNM_CASEFOLD : 0))
990         return 0;
991       break;
992     case SEARCH_REGEX:
993       if (regexec((const regex_t *)ma->matchdata, str, 0, NULL, 0))
994         return 0;
995       break;
996     default:
997       return 0;
998     }
999   return 1;
1000 }
1001
1002 int
1003 repodata_filelistfilter_matches(Repodata *data, const char *str)
1004 {
1005   /* '.*bin\/.*', '^\/etc\/.*', '^\/usr\/lib\/sendmail$' */
1006   /* for now hardcoded */
1007   if (strstr(str, "bin/"))
1008     return 1;
1009   if (!strncmp(str, "/etc/", 5))
1010     return 1;
1011   if (!strcmp(str, "/usr/lib/sendmail"))
1012     return 1;
1013   return 0;
1014 }
1015
1016
1017 enum {
1018   di_bye,
1019
1020   di_enterrepo,
1021   di_entersolvable,
1022   di_enterrepodata,
1023   di_enterschema,
1024   di_enterkey,
1025
1026   di_nextattr,
1027   di_nextkey,
1028   di_nextrepodata,
1029   di_nextsolvable,
1030   di_nextrepo,
1031
1032   di_enterarray,
1033   di_nextarrayelement,
1034
1035   di_entersub,
1036   di_leavesub,
1037
1038   di_nextsolvableattr,
1039   di_nextsolvablekey,
1040   di_entersolvablekey
1041 };
1042
1043 /* see repo.h for documentation */
1044 int
1045 dataiterator_init(Dataiterator *di, Pool *pool, Repo *repo, Id p, Id keyname, const char *match, int flags)
1046 {
1047   memset(di, 0, sizeof(*di));
1048   di->pool = pool;
1049   di->flags = flags & ~SEARCH_THISSOLVID;
1050   if (!pool || (repo && repo->pool != pool))
1051     {
1052       di->state = di_bye;
1053       return -1;
1054     }
1055   if (match)
1056     {
1057       int error;
1058       if ((error = datamatcher_init(&di->matcher, match, flags)) != 0)
1059         {
1060           di->state = di_bye;
1061           return error;
1062         }
1063     }
1064   di->keyname = keyname;
1065   di->keynames[0] = keyname;
1066   dataiterator_set_search(di, repo, p);
1067   return 0;
1068 }
1069
1070 void
1071 dataiterator_init_clone(Dataiterator *di, Dataiterator *from)
1072 {
1073   *di = *from;
1074   memset(&di->matcher, 0, sizeof(di->matcher));
1075   if (from->matcher.match)
1076     datamatcher_init(&di->matcher, from->matcher.match, from->matcher.flags);
1077   if (di->nparents)
1078     {
1079       /* fix pointers */
1080       int i;
1081       for (i = 1; i < di->nparents; i++)
1082         di->parents[i].kv.parent = &di->parents[i - 1].kv;
1083       di->kv.parent = &di->parents[di->nparents - 1].kv;
1084     }
1085 }
1086
1087 int
1088 dataiterator_set_match(Dataiterator *di, const char *match, int flags)
1089 {
1090   di->flags = (flags & ~SEARCH_THISSOLVID) | (di->flags & SEARCH_THISSOLVID);
1091   datamatcher_free(&di->matcher);
1092   memset(&di->matcher, 0, sizeof(di->matcher));
1093   if (match)
1094     {
1095       int error;
1096       if ((error = datamatcher_init(&di->matcher, match, flags)) != 0)
1097         {
1098           di->state = di_bye;
1099           return error;
1100         }
1101     }
1102   return 0;
1103 }
1104
1105 void
1106 dataiterator_set_search(Dataiterator *di, Repo *repo, Id p)
1107 {
1108   di->repo = repo;
1109   di->repoid = -1;
1110   di->flags &= ~SEARCH_THISSOLVID;
1111   di->nparents = 0;
1112   di->rootlevel = 0;
1113   di->repodataid = 0;
1114   if (!di->pool->nrepos)
1115     {
1116       di->state = di_bye;
1117       return;
1118     }
1119   if (!repo)
1120     {
1121       di->repoid = 0;
1122       di->repo = di->pool->repos[0];
1123     }
1124   di->state = di_enterrepo;
1125   if (p)
1126     dataiterator_jump_to_solvid(di, p);
1127 }
1128
1129 void
1130 dataiterator_set_keyname(Dataiterator *di, Id keyname)
1131 {
1132   di->nkeynames = 0;
1133   di->keyname = keyname;
1134   di->keynames[0] = keyname;
1135 }
1136
1137 void
1138 dataiterator_prepend_keyname(Dataiterator *di, Id keyname)
1139 {
1140   int i;
1141
1142   if (di->nkeynames >= sizeof(di->keynames)/sizeof(*di->keynames) - 2)
1143     {
1144       di->state = di_bye;       /* sorry */
1145       return;
1146     }
1147   for (i = di->nkeynames + 1; i > 0; i--)
1148     di->keynames[i] = di->keynames[i - 1];
1149   di->keynames[0] = di->keyname = keyname;
1150   di->nkeynames++;
1151 }
1152
1153 void
1154 dataiterator_free(Dataiterator *di)
1155 {
1156   if (di->matcher.match)
1157     datamatcher_free(&di->matcher);
1158 }
1159
1160 static inline unsigned char *
1161 dataiterator_find_keyname(Dataiterator *di, Id keyname)
1162 {
1163   Id *keyp = di->keyp;
1164   Repokey *keys = di->data->keys;
1165   unsigned char *dp;
1166
1167   for (keyp = di->keyp; *keyp; keyp++)
1168     if (keys[*keyp].name == keyname)
1169       break;
1170   if (!*keyp)
1171     return 0;
1172   dp = forward_to_key(di->data, *keyp, di->keyp, di->dp);
1173   if (!dp)
1174     return 0;
1175   di->keyp = keyp;
1176   return dp;
1177 }
1178
1179 static int
1180 dataiterator_filelistcheck(Dataiterator *di)
1181 {
1182   int j;
1183   int needcomplete = 0;
1184   Repodata *data = di->data;
1185
1186   if ((di->matcher.flags & SEARCH_COMPLETE_FILELIST) != 0)
1187     if (!di->matcher.match || (di->matcher.flags & (SEARCH_STRINGMASK|SEARCH_NOCASE)) != SEARCH_STRING || !repodata_filelistfilter_matches(di->data, di->matcher.match))
1188       needcomplete = 1;
1189   if (data->state != REPODATA_AVAILABLE)
1190     return needcomplete ? 1 : 0;
1191   for (j = 1; j < data->nkeys; j++)
1192     if (data->keys[j].name != REPOSITORY_SOLVABLES && data->keys[j].name != SOLVABLE_FILELIST)
1193       break;
1194   return j == data->nkeys && !needcomplete ? 0 : 1;
1195 }
1196
1197 int
1198 dataiterator_step(Dataiterator *di)
1199 {
1200   Id schema;
1201
1202   for (;;)
1203     {
1204       switch (di->state)
1205         {
1206         case di_enterrepo: di_enterrepo:
1207           if (!di->repo)
1208             goto di_bye;
1209           if (di->repo->disabled && !(di->flags & SEARCH_DISABLED_REPOS))
1210             goto di_nextrepo;
1211           if (!(di->flags & SEARCH_THISSOLVID))
1212             {
1213               di->solvid = di->repo->start - 1; /* reset solvid iterator */
1214               goto di_nextsolvable;
1215             }
1216           /* FALLTHROUGH */
1217
1218         case di_entersolvable: di_entersolvable:
1219           if (di->repodataid >= 0)
1220             {
1221               di->repodataid = 0;       /* reset repodata iterator */
1222               if (di->solvid > 0 && !(di->flags & SEARCH_NO_STORAGE_SOLVABLE) && (!di->keyname || (di->keyname >= SOLVABLE_NAME && di->keyname <= RPM_RPMDBID)) && di->nparents - di->rootlevel == di->nkeynames)
1223                 {
1224                   di->key = solvablekeys + (di->keyname ? di->keyname - SOLVABLE_NAME : 0);
1225                   di->data = 0;
1226                   goto di_entersolvablekey;
1227                 }
1228             }
1229           /* FALLTHROUGH */
1230
1231         case di_enterrepodata: di_enterrepodata:
1232           if (di->repodataid >= 0)
1233             {
1234               if (di->repodataid >= di->repo->nrepodata)
1235                 goto di_nextsolvable;
1236               di->data = di->repo->repodata + di->repodataid;
1237             }
1238           if (di->repodataid >= 0 && di->keyname == SOLVABLE_FILELIST && !dataiterator_filelistcheck(di))
1239             goto di_nextrepodata;
1240           if (!maybe_load_repodata(di->data, di->keyname))
1241             goto di_nextrepodata;
1242           di->dp = solvid2data(di->data, di->solvid, &schema);
1243           if (!di->dp)
1244             goto di_nextrepodata;
1245           if (di->solvid == SOLVID_POS)
1246             di->solvid = di->pool->pos.solvid;
1247           /* reset key iterator */
1248           di->keyp = di->data->schemadata + di->data->schemata[schema];
1249           /* FALLTHROUGH */
1250
1251         case di_enterschema: di_enterschema:
1252           if (di->keyname)
1253             di->dp = dataiterator_find_keyname(di, di->keyname);
1254           if (!di->dp || !*di->keyp)
1255             {
1256               if (di->kv.parent)
1257                 goto di_leavesub;
1258               goto di_nextrepodata;
1259             }
1260           /* FALLTHROUGH */
1261
1262         case di_enterkey: di_enterkey:
1263           di->kv.entry = -1;
1264           di->key = di->data->keys + *di->keyp;
1265           di->ddp = get_data(di->data, di->key, &di->dp, di->keyp[1] && (!di->keyname || (di->flags & SEARCH_SUB) != 0) ? 1 : 0);
1266           if (!di->ddp)
1267             goto di_nextkey;
1268           if (di->key->type == REPOKEY_TYPE_FIXARRAY || di->key->type == REPOKEY_TYPE_FLEXARRAY)
1269             goto di_enterarray;
1270           if (di->nkeynames && di->nparents - di->rootlevel < di->nkeynames)
1271             goto di_nextkey;
1272           /* FALLTHROUGH */
1273
1274         case di_nextattr:
1275           di->kv.entry++;
1276           di->ddp = data_fetch(di->ddp, &di->kv, di->key);
1277           if (di->kv.eof)
1278             di->state = di_nextkey;
1279           else
1280             di->state = di_nextattr;
1281           break;
1282
1283         case di_nextkey: di_nextkey:
1284           if (!di->keyname && *++di->keyp)
1285             goto di_enterkey;
1286           if (di->kv.parent)
1287             goto di_leavesub;
1288           /* FALLTHROUGH */
1289
1290         case di_nextrepodata: di_nextrepodata:
1291           if (di->repodataid >= 0 && ++di->repodataid < di->repo->nrepodata)
1292               goto di_enterrepodata;
1293           /* FALLTHROUGH */
1294
1295         case di_nextsolvable: di_nextsolvable:
1296           if (!(di->flags & SEARCH_THISSOLVID))
1297             {
1298               if (di->solvid < 0)
1299                 di->solvid = di->repo->start;
1300               else
1301                 di->solvid++;
1302               for (; di->solvid < di->repo->end; di->solvid++)
1303                 {
1304                   if (di->pool->solvables[di->solvid].repo == di->repo)
1305                     goto di_entersolvable;
1306                 }
1307             }
1308           /* FALLTHROUGH */
1309
1310         case di_nextrepo: di_nextrepo:
1311           if (di->repoid >= 0)
1312             {
1313               di->repoid++;
1314               di->repodataid = 0;
1315               if (di->repoid < di->pool->nrepos)
1316                 {
1317                   di->repo = di->pool->repos[di->repoid];
1318                   goto di_enterrepo;
1319                 }
1320             }
1321         /* FALLTHROUGH */
1322
1323         case di_bye: di_bye:
1324           di->state = di_bye;
1325           return 0;
1326
1327         case di_enterarray: di_enterarray:
1328           if (di->key->name == REPOSITORY_SOLVABLES)
1329             goto di_nextkey;
1330           di->ddp = data_read_id(di->ddp, &di->kv.num);
1331           di->kv.eof = 0;
1332           di->kv.entry = -1;
1333           /* FALLTHROUGH */
1334
1335         case di_nextarrayelement: di_nextarrayelement:
1336           di->kv.entry++;
1337           if (di->kv.entry)
1338             di->ddp = data_skip_schema(di->data, di->ddp, di->kv.id);
1339           if (di->kv.entry == di->kv.num)
1340             {
1341               if (di->nkeynames && di->nparents - di->rootlevel < di->nkeynames)
1342                 goto di_nextkey;
1343               if (!(di->flags & SEARCH_ARRAYSENTINEL))
1344                 goto di_nextkey;
1345               di->kv.str = (char *)di->ddp;
1346               di->kv.eof = 2;
1347               di->state = di_nextkey;
1348               break;
1349             }
1350           if (di->kv.entry == di->kv.num - 1)
1351             di->kv.eof = 1;
1352           if (di->key->type == REPOKEY_TYPE_FLEXARRAY || !di->kv.entry)
1353             di->ddp = data_read_id(di->ddp, &di->kv.id);
1354           di->kv.str = (char *)di->ddp;
1355           if (di->nkeynames && di->nparents - di->rootlevel < di->nkeynames)
1356             goto di_entersub;
1357           if ((di->flags & SEARCH_SUB) != 0)
1358             di->state = di_entersub;
1359           else
1360             di->state = di_nextarrayelement;
1361           break;
1362
1363         case di_entersub: di_entersub:
1364           if (di->nparents == sizeof(di->parents)/sizeof(*di->parents) - 1)
1365             goto di_nextarrayelement;   /* sorry, full */
1366           di->parents[di->nparents].kv = di->kv;
1367           di->parents[di->nparents].dp = di->dp;
1368           di->parents[di->nparents].keyp = di->keyp;
1369           di->dp = (unsigned char *)di->kv.str;
1370           di->keyp = di->data->schemadata + di->data->schemata[di->kv.id];
1371           memset(&di->kv, 0, sizeof(di->kv));
1372           di->kv.parent = &di->parents[di->nparents].kv;
1373           di->nparents++;
1374           di->keyname = di->keynames[di->nparents - di->rootlevel];
1375           goto di_enterschema;
1376
1377         case di_leavesub: di_leavesub:
1378           if (di->nparents - 1 < di->rootlevel)
1379             goto di_bye;
1380           di->nparents--;
1381           di->dp = di->parents[di->nparents].dp;
1382           di->kv = di->parents[di->nparents].kv;
1383           di->keyp = di->parents[di->nparents].keyp;
1384           di->key = di->data->keys + *di->keyp;
1385           di->ddp = (unsigned char *)di->kv.str;
1386           di->keyname = di->keynames[di->nparents - di->rootlevel];
1387           goto di_nextarrayelement;
1388
1389         /* special solvable attr handling follows */
1390
1391         case di_nextsolvableattr:
1392           di->kv.id = *di->idp++;
1393           di->kv.entry++;
1394           if (!*di->idp)
1395             {
1396               di->kv.eof = 1;
1397               di->state = di_nextsolvablekey;
1398             }
1399           break;
1400
1401         case di_nextsolvablekey: di_nextsolvablekey:
1402           if (di->keyname || di->key->name == RPM_RPMDBID)
1403             goto di_enterrepodata;
1404           di->key++;
1405           /* FALLTHROUGH */
1406
1407         case di_entersolvablekey: di_entersolvablekey:
1408           di->idp = solvabledata_fetch(di->pool->solvables + di->solvid, &di->kv, di->key->name);
1409           if (!di->idp || !di->idp[0])
1410             goto di_nextsolvablekey;
1411           di->kv.id = di->idp[0];
1412           di->kv.num = di->idp[0];
1413           di->idp++;
1414           if (!di->kv.eof && !di->idp[0])
1415             di->kv.eof = 1;
1416           di->kv.entry = 0;
1417           if (di->kv.eof)
1418             di->state = di_nextsolvablekey;
1419           else
1420             di->state = di_nextsolvableattr;
1421           break;
1422         }
1423
1424       if (di->matcher.match)
1425         {
1426           /* simple pre-check so that we don't need to stringify */
1427           if (di->keyname == SOLVABLE_FILELIST && di->key->type == REPOKEY_TYPE_DIRSTRARRAY && di->matcher.match && (di->matcher.flags & (SEARCH_FILES|SEARCH_NOCASE|SEARCH_STRINGMASK)) == (SEARCH_FILES|SEARCH_STRING))
1428             {
1429               int l = strlen(di->matcher.match) - strlen(di->kv.str);
1430               if (l < 0 || strcmp(di->matcher.match + l, di->kv.str))
1431                 continue;
1432             }
1433           if (!repodata_stringify(di->pool, di->data, di->key, &di->kv, di->flags))
1434             {
1435               if (di->keyname && (di->key->type == REPOKEY_TYPE_FIXARRAY || di->key->type == REPOKEY_TYPE_FLEXARRAY))
1436                 return 1;
1437               continue;
1438             }
1439           if (!datamatcher_match(&di->matcher, di->kv.str))
1440             continue;
1441         }
1442       /* found something! */
1443       return 1;
1444     }
1445 }
1446
1447 void
1448 dataiterator_entersub(Dataiterator *di)
1449 {
1450   if (di->state == di_nextarrayelement)
1451     di->state = di_entersub;
1452 }
1453
1454 void
1455 dataiterator_setpos(Dataiterator *di)
1456 {
1457   if (di->kv.eof == 2)
1458     {
1459       pool_clear_pos(di->pool);
1460       return;
1461     }
1462   di->pool->pos.solvid = di->solvid;
1463   di->pool->pos.repo = di->repo;
1464   di->pool->pos.repodataid = di->data - di->repo->repodata;
1465   di->pool->pos.schema = di->kv.id;
1466   di->pool->pos.dp = (unsigned char *)di->kv.str - di->data->incoredata;
1467 }
1468
1469 void
1470 dataiterator_setpos_parent(Dataiterator *di)
1471 {
1472   if (!di->kv.parent || di->kv.parent->eof == 2)
1473     {
1474       pool_clear_pos(di->pool);
1475       return;
1476     }
1477   di->pool->pos.solvid = di->solvid;
1478   di->pool->pos.repo = di->repo;
1479   di->pool->pos.repodataid = di->data - di->repo->repodata;
1480   di->pool->pos.schema = di->kv.parent->id;
1481   di->pool->pos.dp = (unsigned char *)di->kv.parent->str - di->data->incoredata;
1482 }
1483
1484 /* clones just the position, not the search keys/matcher */
1485 void
1486 dataiterator_clonepos(Dataiterator *di, Dataiterator *from)
1487 {
1488   di->state = from->state;
1489   di->flags &= ~SEARCH_THISSOLVID;
1490   di->flags |= (from->flags & SEARCH_THISSOLVID);
1491   di->repo = from->repo;
1492   di->data = from->data;
1493   di->dp = from->dp;
1494   di->ddp = from->ddp;
1495   di->idp = from->idp;
1496   di->keyp = from->keyp;
1497   di->key = from->key;
1498   di->kv = from->kv;
1499   di->repodataid = from->repodataid;
1500   di->solvid = from->solvid;
1501   di->repoid = from->repoid;
1502   di->rootlevel = from->rootlevel;
1503   memcpy(di->parents, from->parents, sizeof(from->parents));
1504   di->nparents = from->nparents;
1505   if (di->nparents)
1506     {
1507       int i;
1508       for (i = 1; i < di->nparents; i++)
1509         di->parents[i].kv.parent = &di->parents[i - 1].kv;
1510       di->kv.parent = &di->parents[di->nparents - 1].kv;
1511     }
1512 }
1513
1514 void
1515 dataiterator_seek(Dataiterator *di, int whence)
1516 {
1517   if ((whence & DI_SEEK_STAY) != 0)
1518     di->rootlevel = di->nparents;
1519   switch (whence & ~DI_SEEK_STAY)
1520     {
1521     case DI_SEEK_CHILD:
1522       if (di->state != di_nextarrayelement)
1523         break;
1524       if ((whence & DI_SEEK_STAY) != 0)
1525         di->rootlevel = di->nparents + 1;       /* XXX: dangerous! */
1526       di->state = di_entersub;
1527       break;
1528     case DI_SEEK_PARENT:
1529       if (!di->nparents)
1530         {
1531           di->state = di_bye;
1532           break;
1533         }
1534       di->nparents--;
1535       if (di->rootlevel > di->nparents)
1536         di->rootlevel = di->nparents;
1537       di->dp = di->parents[di->nparents].dp;
1538       di->kv = di->parents[di->nparents].kv;
1539       di->keyp = di->parents[di->nparents].keyp;
1540       di->key = di->data->keys + *di->keyp;
1541       di->ddp = (unsigned char *)di->kv.str;
1542       di->keyname = di->keynames[di->nparents - di->rootlevel];
1543       di->state = di_nextarrayelement;
1544       break;
1545     case DI_SEEK_REWIND:
1546       if (!di->nparents)
1547         {
1548           di->state = di_bye;
1549           break;
1550         }
1551       di->dp = (unsigned char *)di->kv.parent->str;
1552       di->keyp = di->data->schemadata + di->data->schemata[di->kv.parent->id];
1553       di->state = di_enterschema;
1554       break;
1555     default:
1556       break;
1557     }
1558 }
1559
1560 void
1561 dataiterator_skip_attribute(Dataiterator *di)
1562 {
1563   if (di->state == di_nextsolvableattr)
1564     di->state = di_nextsolvablekey;
1565   else
1566     di->state = di_nextkey;
1567 }
1568
1569 void
1570 dataiterator_skip_solvable(Dataiterator *di)
1571 {
1572   di->nparents = 0;
1573   di->rootlevel = 0;
1574   di->keyname = di->keynames[0];
1575   di->state = di_nextsolvable;
1576 }
1577
1578 void
1579 dataiterator_skip_repo(Dataiterator *di)
1580 {
1581   di->nparents = 0;
1582   di->rootlevel = 0;
1583   di->keyname = di->keynames[0];
1584   di->state = di_nextrepo;
1585 }
1586
1587 void
1588 dataiterator_jump_to_solvid(Dataiterator *di, Id solvid)
1589 {
1590   di->nparents = 0;
1591   di->rootlevel = 0;
1592   di->keyname = di->keynames[0];
1593   if (solvid == SOLVID_POS)
1594     {
1595       di->repo = di->pool->pos.repo;
1596       if (!di->repo)
1597         {
1598           di->state = di_bye;
1599           return;
1600         }
1601       di->repoid = -1;
1602       di->data = di->repo->repodata + di->pool->pos.repodataid;
1603       di->repodataid = -1;
1604       di->solvid = solvid;
1605       di->state = di_enterrepo;
1606       di->flags |= SEARCH_THISSOLVID;
1607       return;
1608     }
1609   if (solvid > 0)
1610     {
1611       di->repo = di->pool->solvables[solvid].repo;
1612       di->repoid = -1;
1613     }
1614   else if (di->repoid >= 0)
1615     {
1616       if (!di->pool->nrepos)
1617         {
1618           di->state = di_bye;
1619           return;
1620         }
1621       di->repo = di->pool->repos[0];
1622       di->repoid = 0;
1623     }
1624   di->repodataid = 0;
1625   di->solvid = solvid;
1626   if (solvid)
1627     di->flags |= SEARCH_THISSOLVID;
1628   di->state = di_enterrepo;
1629 }
1630
1631 void
1632 dataiterator_jump_to_repo(Dataiterator *di, Repo *repo)
1633 {
1634   di->nparents = 0;
1635   di->rootlevel = 0;
1636   di->repo = repo;
1637   di->repoid = -1;
1638   di->repodataid = 0;
1639   di->solvid = 0;
1640   di->flags &= ~SEARCH_THISSOLVID;
1641   di->state = di_enterrepo;
1642 }
1643
1644 int
1645 dataiterator_match(Dataiterator *di, Datamatcher *ma)
1646 {
1647   if (!repodata_stringify(di->pool, di->data, di->key, &di->kv, di->flags))
1648     return 0;
1649   if (!ma)
1650     return 1;
1651   return datamatcher_match(ma, di->kv.str);
1652 }
1653
1654 /************************************************************************
1655  * data modify functions
1656  */
1657
1658 /* extend repodata so that it includes solvables p */
1659 void
1660 repodata_extend(Repodata *data, Id p)
1661 {
1662   if (data->start == data->end)
1663     data->start = data->end = p;
1664   if (p >= data->end)
1665     {
1666       int old = data->end - data->start;
1667       int new = p - data->end + 1;
1668       if (data->attrs)
1669         {
1670           data->attrs = sat_extend(data->attrs, old, new, sizeof(Id *), REPODATA_BLOCK);
1671           memset(data->attrs + old, 0, new * sizeof(Id *));
1672         }
1673       data->incoreoffset = sat_extend(data->incoreoffset, old, new, sizeof(Id), REPODATA_BLOCK);
1674       memset(data->incoreoffset + old, 0, new * sizeof(Id));
1675       data->end = p + 1;
1676     }
1677   if (p < data->start)
1678     {
1679       int old = data->end - data->start;
1680       int new = data->start - p;
1681       if (data->attrs)
1682         {
1683           data->attrs = sat_extend_resize(data->attrs, old + new, sizeof(Id *), REPODATA_BLOCK);
1684           memmove(data->attrs + new, data->attrs, old * sizeof(Id *));
1685           memset(data->attrs, 0, new * sizeof(Id *));
1686         }
1687       data->incoreoffset = sat_extend_resize(data->incoreoffset, old + new, sizeof(Id), REPODATA_BLOCK);
1688       memmove(data->incoreoffset + new, data->incoreoffset, old * sizeof(Id));
1689       memset(data->incoreoffset, 0, new * sizeof(Id));
1690       data->start = p;
1691     }
1692 }
1693
1694 /* shrink end of repodata */
1695 void
1696 repodata_shrink(Repodata *data, int end)
1697 {
1698   int i;
1699
1700   if (data->end <= end)
1701     return;
1702   if (data->start >= end)
1703     {
1704       if (data->attrs)
1705         {
1706           for (i = 0; i < data->end - data->start; i++)
1707             sat_free(data->attrs[i]);
1708           data->attrs = sat_free(data->attrs);
1709         }
1710       data->incoreoffset = sat_free(data->incoreoffset);
1711       data->start = data->end = 0;
1712       return;
1713     }
1714   if (data->attrs)
1715     {
1716       for (i = end; i < data->end; i++)
1717         sat_free(data->attrs[i - data->start]);
1718       data->attrs = sat_extend_resize(data->attrs, end - data->start, sizeof(Id *), REPODATA_BLOCK);
1719     }
1720   if (data->incoreoffset)
1721     data->incoreoffset = sat_extend_resize(data->incoreoffset, end - data->start, sizeof(Id), REPODATA_BLOCK);
1722   data->end = end;
1723 }
1724
1725 /* extend repodata so that it includes solvables from start to start + num - 1 */
1726 void
1727 repodata_extend_block(Repodata *data, Id start, Id num)
1728 {
1729   if (!num)
1730     return;
1731   if (!data->incoreoffset)
1732     {
1733       data->incoreoffset = sat_calloc_block(num, sizeof(Id), REPODATA_BLOCK);
1734       data->start = start;
1735       data->end = start + num;
1736       return;
1737     }
1738   repodata_extend(data, start);
1739   if (num > 1)
1740     repodata_extend(data, start + num - 1);
1741 }
1742
1743 /**********************************************************************/
1744
1745
1746 #define REPODATA_ATTRS_BLOCK 63
1747 #define REPODATA_ATTRDATA_BLOCK 1023
1748 #define REPODATA_ATTRIDDATA_BLOCK 63
1749
1750
1751 Id
1752 repodata_new_handle(Repodata *data)
1753 {
1754   if (!data->nxattrs)
1755     {
1756       data->xattrs = sat_calloc_block(1, sizeof(Id *), REPODATA_BLOCK);
1757       data->nxattrs = 2;
1758     }
1759   data->xattrs = sat_extend(data->xattrs, data->nxattrs, 1, sizeof(Id *), REPODATA_BLOCK);
1760   data->xattrs[data->nxattrs] = 0;
1761   return -(data->nxattrs++);
1762 }
1763
1764 static inline Id **
1765 repodata_get_attrp(Repodata *data, Id handle)
1766 {
1767   if (handle == SOLVID_META)
1768     {
1769       if (!data->xattrs)
1770         {
1771           data->xattrs = sat_calloc_block(1, sizeof(Id *), REPODATA_BLOCK);
1772           data->nxattrs = 2;
1773         }
1774     }
1775   if (handle < 0)
1776     return data->xattrs - handle;
1777   if (handle < data->start || handle >= data->end)
1778     repodata_extend(data, handle);
1779   if (!data->attrs)
1780     data->attrs = sat_calloc_block(data->end - data->start, sizeof(Id *), REPODATA_BLOCK);
1781   return data->attrs + (handle - data->start);
1782 }
1783
1784 static void
1785 repodata_insert_keyid(Repodata *data, Id handle, Id keyid, Id val, int overwrite)
1786 {
1787   Id *pp;
1788   Id *ap, **app;
1789   int i;
1790
1791   app = repodata_get_attrp(data, handle);
1792   ap = *app;
1793   i = 0;
1794   if (ap)
1795     {
1796       /* Determine equality based on the name only, allows us to change
1797          type (when overwrite is set), and makes TYPE_CONSTANT work.  */
1798       for (pp = ap; *pp; pp += 2)
1799         if (data->keys[*pp].name == data->keys[keyid].name)
1800           break;
1801       if (*pp)
1802         {
1803           if (overwrite)
1804             {
1805               pp[0] = keyid;
1806               pp[1] = val;
1807             }
1808           return;
1809         }
1810       i = pp - ap;
1811     }
1812   ap = sat_extend(ap, i, 3, sizeof(Id), REPODATA_ATTRS_BLOCK);
1813   *app = ap;
1814   pp = ap + i;
1815   *pp++ = keyid;
1816   *pp++ = val;
1817   *pp = 0;
1818 }
1819
1820
1821 static void
1822 repodata_set(Repodata *data, Id solvid, Repokey *key, Id val)
1823 {
1824   Id keyid;
1825
1826   keyid = repodata_key2id(data, key, 1);
1827   repodata_insert_keyid(data, solvid, keyid, val, 1);
1828 }
1829
1830 void
1831 repodata_set_id(Repodata *data, Id solvid, Id keyname, Id id)
1832 {
1833   Repokey key;
1834   key.name = keyname;
1835   key.type = REPOKEY_TYPE_ID;
1836   key.size = 0;
1837   key.storage = KEY_STORAGE_INCORE;
1838   repodata_set(data, solvid, &key, id);
1839 }
1840
1841 void
1842 repodata_set_num(Repodata *data, Id solvid, Id keyname, unsigned int num)
1843 {
1844   Repokey key;
1845   key.name = keyname;
1846   key.type = REPOKEY_TYPE_NUM;
1847   key.size = 0;
1848   key.storage = KEY_STORAGE_INCORE;
1849   repodata_set(data, solvid, &key, (Id)num);
1850 }
1851
1852 void
1853 repodata_set_poolstr(Repodata *data, Id solvid, Id keyname, const char *str)
1854 {
1855   Repokey key;
1856   Id id;
1857   if (data->localpool)
1858     id = stringpool_str2id(&data->spool, str, 1);
1859   else
1860     id = str2id(data->repo->pool, str, 1);
1861   key.name = keyname;
1862   key.type = REPOKEY_TYPE_ID;
1863   key.size = 0;
1864   key.storage = KEY_STORAGE_INCORE;
1865   repodata_set(data, solvid, &key, id);
1866 }
1867
1868 void
1869 repodata_set_constant(Repodata *data, Id solvid, Id keyname, unsigned int constant)
1870 {
1871   Repokey key;
1872   key.name = keyname;
1873   key.type = REPOKEY_TYPE_CONSTANT;
1874   key.size = constant;
1875   key.storage = KEY_STORAGE_INCORE;
1876   repodata_set(data, solvid, &key, 0);
1877 }
1878
1879 void
1880 repodata_set_constantid(Repodata *data, Id solvid, Id keyname, Id id)
1881 {
1882   Repokey key;
1883   key.name = keyname;
1884   key.type = REPOKEY_TYPE_CONSTANTID;
1885   key.size = id;
1886   key.storage = KEY_STORAGE_INCORE;
1887   repodata_set(data, solvid, &key, 0);
1888 }
1889
1890 void
1891 repodata_set_void(Repodata *data, Id solvid, Id keyname)
1892 {
1893   Repokey key;
1894   key.name = keyname;
1895   key.type = REPOKEY_TYPE_VOID;
1896   key.size = 0;
1897   key.storage = KEY_STORAGE_INCORE;
1898   repodata_set(data, solvid, &key, 0);
1899 }
1900
1901 void
1902 repodata_set_str(Repodata *data, Id solvid, Id keyname, const char *str)
1903 {
1904   Repokey key;
1905   int l;
1906
1907   l = strlen(str) + 1;
1908   key.name = keyname;
1909   key.type = REPOKEY_TYPE_STR;
1910   key.size = 0;
1911   key.storage = KEY_STORAGE_INCORE;
1912   data->attrdata = sat_extend(data->attrdata, data->attrdatalen, l, 1, REPODATA_ATTRDATA_BLOCK);
1913   memcpy(data->attrdata + data->attrdatalen, str, l);
1914   repodata_set(data, solvid, &key, data->attrdatalen);
1915   data->attrdatalen += l;
1916 }
1917
1918 /* add an array element consisting of entrysize Ids to the repodata. modifies attriddata
1919  * so that the caller can append the new element there */
1920 static void
1921 repodata_add_array(Repodata *data, Id handle, Id keyname, Id keytype, int entrysize)
1922 {
1923   int oldsize;
1924   Id *ida, *pp, **ppp;
1925
1926   /* check if it is the same as last time, this speeds things up a lot */
1927   if (handle == data->lasthandle && data->keys[data->lastkey].name == keyname && data->keys[data->lastkey].type == keytype && data->attriddatalen == data->lastdatalen)
1928     {
1929       /* great! just append the new data */
1930       data->attriddata = sat_extend(data->attriddata, data->attriddatalen, entrysize, sizeof(Id), REPODATA_ATTRIDDATA_BLOCK);
1931       data->attriddatalen--;    /* overwrite terminating 0  */
1932       data->lastdatalen += entrysize;
1933       return;
1934     }
1935
1936   ppp = repodata_get_attrp(data, handle);
1937   pp = *ppp;
1938   if (pp)
1939     for (; *pp; pp += 2)
1940       if (data->keys[*pp].name == keyname && data->keys[*pp].type == keytype)
1941         break;
1942   if (!pp || !*pp)
1943     {
1944       /* not found. allocate new key */
1945       Repokey key;
1946       key.name = keyname;
1947       key.type = keytype;
1948       key.size = 0;
1949       key.storage = KEY_STORAGE_INCORE;
1950       data->attriddata = sat_extend(data->attriddata, data->attriddatalen, entrysize + 1, sizeof(Id), REPODATA_ATTRIDDATA_BLOCK);
1951       repodata_set(data, handle, &key, data->attriddatalen);
1952       data->lasthandle = 0;     /* next time... */
1953       return;
1954     }
1955   oldsize = 0;
1956   for (ida = data->attriddata + pp[1]; *ida; ida += entrysize)
1957     oldsize += entrysize;
1958   if (ida + 1 == data->attriddata + data->attriddatalen)
1959     {
1960       /* this was the last entry, just append it */
1961       data->attriddata = sat_extend(data->attriddata, data->attriddatalen, entrysize, sizeof(Id), REPODATA_ATTRIDDATA_BLOCK);
1962       data->attriddatalen--;    /* overwrite terminating 0  */
1963     }
1964   else
1965     {
1966       /* too bad. move to back. */
1967       data->attriddata = sat_extend(data->attriddata, data->attriddatalen,  oldsize + entrysize + 1, sizeof(Id), REPODATA_ATTRIDDATA_BLOCK);
1968       memcpy(data->attriddata + data->attriddatalen, data->attriddata + pp[1], oldsize * sizeof(Id));
1969       pp[1] = data->attriddatalen;
1970       data->attriddatalen += oldsize;
1971     }
1972   data->lasthandle = handle;
1973   data->lastkey = *pp;
1974   data->lastdatalen = data->attriddatalen + entrysize + 1;
1975 }
1976
1977 void
1978 repodata_set_bin_checksum(Repodata *data, Id solvid, Id keyname, Id type,
1979                       const unsigned char *str)
1980 {
1981   Repokey key;
1982   int l;
1983
1984   if (!(l = sat_chksum_len(type)))
1985     return;
1986   key.name = keyname;
1987   key.type = type;
1988   key.size = 0;
1989   key.storage = KEY_STORAGE_INCORE;
1990   data->attrdata = sat_extend(data->attrdata, data->attrdatalen, l, 1, REPODATA_ATTRDATA_BLOCK);
1991   memcpy(data->attrdata + data->attrdatalen, str, l);
1992   repodata_set(data, solvid, &key, data->attrdatalen);
1993   data->attrdatalen += l;
1994 }
1995
1996 static int
1997 hexstr2bytes(unsigned char *buf, const char *str, int buflen)
1998 {
1999   int i;
2000   for (i = 0; i < buflen; i++)
2001     {
2002 #define c2h(c) (((c)>='0' && (c)<='9') ? ((c)-'0')              \
2003                 : ((c)>='a' && (c)<='f') ? ((c)-('a'-10))       \
2004                 : ((c)>='A' && (c)<='F') ? ((c)-('A'-10))       \
2005                 : -1)
2006       int v = c2h(*str);
2007       str++;
2008       if (v < 0)
2009         return 0;
2010       buf[i] = v;
2011       v = c2h(*str);
2012       str++;
2013       if (v < 0)
2014         return 0;
2015       buf[i] = (buf[i] << 4) | v;
2016 #undef c2h
2017     }
2018   return buflen;
2019 }
2020
2021 void
2022 repodata_set_checksum(Repodata *data, Id solvid, Id keyname, Id type,
2023                       const char *str)
2024 {
2025   unsigned char buf[64];
2026   int l;
2027
2028   if (!(l = sat_chksum_len(type)))
2029     return;
2030   if (hexstr2bytes(buf, str, l) != l)
2031     return;
2032   repodata_set_bin_checksum(data, solvid, keyname, type, buf);
2033 }
2034
2035 const char *
2036 repodata_chk2str(Repodata *data, Id type, const unsigned char *buf)
2037 {
2038   int i, l;
2039   char *str, *s;
2040
2041   if (!(l = sat_chksum_len(type)))
2042     return "";
2043   s = str = pool_alloctmpspace(data->repo->pool, 2 * l + 1);
2044   for (i = 0; i < l; i++)
2045     {
2046       unsigned char v = buf[i];
2047       unsigned char w = v >> 4;
2048       *s++ = w >= 10 ? w + ('a' - 10) : w + '0';
2049       w = v & 15;
2050       *s++ = w >= 10 ? w + ('a' - 10) : w + '0';
2051     }
2052   *s = 0;
2053   return str;
2054 }
2055
2056 /* rpm filenames don't contain the epoch, so strip it */
2057 static inline const char *
2058 evrid2vrstr(Pool *pool, Id evrid)
2059 {
2060   const char *p, *evr = id2str(pool, evrid);
2061   if (!evr)
2062     return evr;
2063   for (p = evr; *p >= '0' && *p <= '9'; p++)
2064     ;
2065   return p != evr && *p == ':' ? p + 1 : evr;
2066 }
2067
2068 void
2069 repodata_set_location(Repodata *data, Id solvid, int medianr, const char *dir, const char *file)
2070 {
2071   Pool *pool = data->repo->pool;
2072   Solvable *s;
2073   const char *str, *fp;
2074   int l = 0;
2075
2076   if (medianr)
2077     repodata_set_constant(data, solvid, SOLVABLE_MEDIANR, medianr);
2078   if (!dir)
2079     {
2080       if ((dir = strrchr(file, '/')) != 0)
2081         {
2082           l = dir - file;
2083           dir = file;
2084           file = dir + l + 1;
2085           if (!l)
2086             l++;
2087         }
2088     }
2089   else
2090     l = strlen(dir);
2091   if (l >= 2 && dir[0] == '.' && dir[1] == '/' && (l == 2 || dir[2] != '/'))
2092     {
2093       dir += 2;
2094       l -= 2;
2095     }
2096   if (l == 1 && dir[0] == '.')
2097     l = 0;
2098   s = pool->solvables + solvid;
2099   if (dir && l)
2100     {
2101       str = id2str(pool, s->arch);
2102       if (!strncmp(dir, str, l) && !str[l])
2103         repodata_set_void(data, solvid, SOLVABLE_MEDIADIR);
2104       else if (!dir[l])
2105         repodata_set_str(data, solvid, SOLVABLE_MEDIADIR, dir);
2106       else
2107         {
2108           char *dir2 = strdup(dir);
2109           dir2[l] = 0;
2110           repodata_set_str(data, solvid, SOLVABLE_MEDIADIR, dir2);
2111           free(dir2);
2112         }
2113     }
2114   fp = file;
2115   str = id2str(pool, s->name);
2116   l = strlen(str);
2117   if ((!l || !strncmp(fp, str, l)) && fp[l] == '-')
2118     {
2119       fp += l + 1;
2120       str = evrid2vrstr(pool, s->evr);
2121       l = strlen(str);
2122       if ((!l || !strncmp(fp, str, l)) && fp[l] == '.')
2123         {
2124           fp += l + 1;
2125           str = id2str(pool, s->arch);
2126           l = strlen(str);
2127           if ((!l || !strncmp(fp, str, l)) && !strcmp(fp + l, ".rpm"))
2128             {
2129               repodata_set_void(data, solvid, SOLVABLE_MEDIAFILE);
2130               return;
2131             }
2132         }
2133     }
2134   repodata_set_str(data, solvid, SOLVABLE_MEDIAFILE, file);
2135 }
2136
2137 void
2138 repodata_add_dirnumnum(Repodata *data, Id solvid, Id keyname, Id dir, Id num, Id num2)
2139 {
2140   assert(dir);
2141 #if 0
2142 fprintf(stderr, "repodata_add_dirnumnum %d %d %d %d (%d)\n", solvid, dir, num, num2, data->attriddatalen);
2143 #endif
2144   repodata_add_array(data, solvid, keyname, REPOKEY_TYPE_DIRNUMNUMARRAY, 3);
2145   data->attriddata[data->attriddatalen++] = dir;
2146   data->attriddata[data->attriddatalen++] = num;
2147   data->attriddata[data->attriddatalen++] = num2;
2148   data->attriddata[data->attriddatalen++] = 0;
2149 }
2150
2151 void
2152 repodata_add_dirstr(Repodata *data, Id solvid, Id keyname, Id dir, const char *str)
2153 {
2154   Id stroff;
2155   int l;
2156
2157   assert(dir);
2158   l = strlen(str) + 1;
2159   data->attrdata = sat_extend(data->attrdata, data->attrdatalen, l, 1, REPODATA_ATTRDATA_BLOCK);
2160   memcpy(data->attrdata + data->attrdatalen, str, l);
2161   stroff = data->attrdatalen;
2162   data->attrdatalen += l;
2163
2164 #if 0
2165 fprintf(stderr, "repodata_add_dirstr %d %d %s (%d)\n", solvid, dir, str,  data->attriddatalen);
2166 #endif
2167   repodata_add_array(data, solvid, keyname, REPOKEY_TYPE_DIRSTRARRAY, 2);
2168   data->attriddata[data->attriddatalen++] = dir;
2169   data->attriddata[data->attriddatalen++] = stroff;
2170   data->attriddata[data->attriddatalen++] = 0;
2171 }
2172
2173 void
2174 repodata_add_idarray(Repodata *data, Id solvid, Id keyname, Id id)
2175 {
2176 #if 0
2177 fprintf(stderr, "repodata_add_idarray %d %d (%d)\n", solvid, id, data->attriddatalen);
2178 #endif
2179   repodata_add_array(data, solvid, keyname, REPOKEY_TYPE_IDARRAY, 1);
2180   data->attriddata[data->attriddatalen++] = id;
2181   data->attriddata[data->attriddatalen++] = 0;
2182 }
2183
2184 void
2185 repodata_add_poolstr_array(Repodata *data, Id solvid, Id keyname,
2186                            const char *str)
2187 {
2188   Id id;
2189   if (data->localpool)
2190     id = stringpool_str2id(&data->spool, str, 1);
2191   else
2192     id = str2id(data->repo->pool, str, 1);
2193   repodata_add_idarray(data, solvid, keyname, id);
2194 }
2195
2196 void
2197 repodata_add_fixarray(Repodata *data, Id solvid, Id keyname, Id ghandle)
2198 {
2199   repodata_add_array(data, solvid, keyname, REPOKEY_TYPE_FIXARRAY, 1);
2200   data->attriddata[data->attriddatalen++] = ghandle;
2201   data->attriddata[data->attriddatalen++] = 0;
2202 }
2203
2204 void
2205 repodata_add_flexarray(Repodata *data, Id solvid, Id keyname, Id ghandle)
2206 {
2207   repodata_add_array(data, solvid, keyname, REPOKEY_TYPE_FLEXARRAY, 1);
2208   data->attriddata[data->attriddatalen++] = ghandle;
2209   data->attriddata[data->attriddatalen++] = 0;
2210 }
2211
2212 /* add all attrs from src to dest */
2213 void
2214 repodata_merge_attrs(Repodata *data, Id dest, Id src)
2215 {
2216   Id *keyp;
2217   if (dest == src || !(keyp = data->attrs[src - data->start]))
2218     return;
2219   for (; *keyp; keyp += 2)
2220     repodata_insert_keyid(data, dest, keyp[0], keyp[1], 0);
2221 }
2222
2223 void
2224 repodata_merge_some_attrs(Repodata *data, Id dest, Id src, Map *keyidmap, int overwrite)
2225 {
2226   Id *keyp;
2227   if (dest == src || !(keyp = data->attrs[src - data->start]))
2228     return;
2229   for (; *keyp; keyp += 2)
2230     if (!keyidmap || MAPTST(keyidmap, keyp[0]))
2231       repodata_insert_keyid(data, dest, keyp[0], keyp[1], overwrite);
2232 }
2233
2234
2235
2236 /**********************************************************************/
2237
2238 /* TODO: unify with repo_write! */
2239
2240 #define EXTDATA_BLOCK 1023
2241
2242 struct extdata {
2243   unsigned char *buf;
2244   int len;
2245 };
2246
2247 static void
2248 data_addid(struct extdata *xd, Id x)
2249 {
2250   unsigned char *dp;
2251   xd->buf = sat_extend(xd->buf, xd->len, 5, 1, EXTDATA_BLOCK);
2252   dp = xd->buf + xd->len;
2253
2254   if (x >= (1 << 14))
2255     {
2256       if (x >= (1 << 28))
2257         *dp++ = (x >> 28) | 128;
2258       if (x >= (1 << 21))
2259         *dp++ = (x >> 21) | 128;
2260       *dp++ = (x >> 14) | 128;
2261     }
2262   if (x >= (1 << 7))
2263     *dp++ = (x >> 7) | 128;
2264   *dp++ = x & 127;
2265   xd->len = dp - xd->buf;
2266 }
2267
2268 static void
2269 data_addideof(struct extdata *xd, Id x, int eof)
2270 {
2271   if (x >= 64)
2272     x = (x & 63) | ((x & ~63) << 1);
2273   data_addid(xd, (eof ? x: x | 64));
2274 }
2275
2276 static void
2277 data_addblob(struct extdata *xd, unsigned char *blob, int len)
2278 {
2279   xd->buf = sat_extend(xd->buf, xd->len, len, 1, EXTDATA_BLOCK);
2280   memcpy(xd->buf + xd->len, blob, len);
2281   xd->len += len;
2282 }
2283
2284 /*********************************/
2285
2286 static void
2287 repodata_serialize_key(Repodata *data, struct extdata *newincore,
2288                        struct extdata *newvincore,
2289                        Id *schema,
2290                        Repokey *key, Id val)
2291 {
2292   /* Otherwise we have a new value.  Parse it into the internal
2293      form.  */
2294   Id *ida;
2295   struct extdata *xd;
2296   unsigned int oldvincorelen = 0;
2297   Id schemaid, *sp;
2298
2299   xd = newincore;
2300   if (key->storage == KEY_STORAGE_VERTICAL_OFFSET)
2301     {
2302       xd = newvincore;
2303       oldvincorelen = xd->len;
2304     }
2305   switch (key->type)
2306     {
2307     case REPOKEY_TYPE_VOID:
2308     case REPOKEY_TYPE_CONSTANT:
2309     case REPOKEY_TYPE_CONSTANTID:
2310       break;
2311     case REPOKEY_TYPE_STR:
2312       data_addblob(xd, data->attrdata + val, strlen((char *)(data->attrdata + val)) + 1);
2313       break;
2314     case REPOKEY_TYPE_MD5:
2315       data_addblob(xd, data->attrdata + val, SIZEOF_MD5);
2316       break;
2317     case REPOKEY_TYPE_SHA1:
2318       data_addblob(xd, data->attrdata + val, SIZEOF_SHA1);
2319       break;
2320     case REPOKEY_TYPE_SHA256:
2321       data_addblob(xd, data->attrdata + val, SIZEOF_SHA256);
2322       break;
2323     case REPOKEY_TYPE_ID:
2324     case REPOKEY_TYPE_NUM:
2325     case REPOKEY_TYPE_DIR:
2326       data_addid(xd, val);
2327       break;
2328     case REPOKEY_TYPE_IDARRAY:
2329       for (ida = data->attriddata + val; *ida; ida++)
2330         data_addideof(xd, ida[0], ida[1] ? 0 : 1);
2331       break;
2332     case REPOKEY_TYPE_DIRNUMNUMARRAY:
2333       for (ida = data->attriddata + val; *ida; ida += 3)
2334         {
2335           data_addid(xd, ida[0]);
2336           data_addid(xd, ida[1]);
2337           data_addideof(xd, ida[2], ida[3] ? 0 : 1);
2338         }
2339       break;
2340     case REPOKEY_TYPE_DIRSTRARRAY:
2341       for (ida = data->attriddata + val; *ida; ida += 2)
2342         {
2343           data_addideof(xd, ida[0], ida[2] ? 0 : 1);
2344           data_addblob(xd, data->attrdata + ida[1], strlen((char *)(data->attrdata + ida[1])) + 1);
2345         }
2346       break;
2347     case REPOKEY_TYPE_FIXARRAY:
2348       {
2349         int num = 0;
2350         schemaid = 0;
2351         for (ida = data->attriddata + val; *ida; ida++)
2352           {
2353 #if 0
2354             fprintf(stderr, "serialize struct %d\n", *ida);
2355 #endif
2356             sp = schema;
2357             Id *kp = data->xattrs[-*ida];
2358             if (!kp)
2359               continue;
2360             num++;
2361             for (;*kp; kp += 2)
2362               {
2363 #if 0
2364                 fprintf(stderr, "  %s:%d\n", id2str(data->repo->pool, data->keys[*kp].name), kp[1]);
2365 #endif
2366                 *sp++ = *kp;
2367               }
2368             *sp = 0;
2369             if (!schemaid)
2370               schemaid = repodata_schema2id(data, schema, 1);
2371             else if (schemaid != repodata_schema2id(data, schema, 0))
2372               {
2373                 pool_debug(data->repo->pool, SAT_FATAL, "fixarray substructs with different schemas\n");
2374                 exit(1);
2375               }
2376 #if 0
2377             fprintf(stderr, "  schema %d\n", schemaid);
2378 #endif
2379           }
2380         if (!num)
2381           break;
2382         data_addid(xd, num);
2383         data_addid(xd, schemaid);
2384         for (ida = data->attriddata + val; *ida; ida++)
2385           {
2386             Id *kp = data->xattrs[-*ida];
2387             if (!kp)
2388               continue;
2389             for (;*kp; kp += 2)
2390               {
2391                 repodata_serialize_key(data, newincore, newvincore,
2392                                        schema, data->keys + *kp, kp[1]);
2393               }
2394           }
2395         break;
2396       }
2397     case REPOKEY_TYPE_FLEXARRAY:
2398       {
2399         int num = 0;
2400         for (ida = data->attriddata + val; *ida; ida++)
2401           num++;
2402         data_addid(xd, num);
2403         for (ida = data->attriddata + val; *ida; ida++)
2404           {
2405             Id *kp = data->xattrs[-*ida];
2406             if (!kp)
2407               {
2408                 data_addid(xd, 0);      /* XXX */
2409                 continue;
2410               }
2411             sp = schema;
2412             for (;*kp; kp += 2)
2413               *sp++ = *kp;
2414             *sp = 0;
2415             schemaid = repodata_schema2id(data, schema, 1);
2416             data_addid(xd, schemaid);
2417             kp = data->xattrs[-*ida];
2418             for (;*kp; kp += 2)
2419               {
2420                 repodata_serialize_key(data, newincore, newvincore,
2421                                        schema, data->keys + *kp, kp[1]);
2422               }
2423           }
2424         break;
2425       }
2426     default:
2427       pool_debug(data->repo->pool, SAT_FATAL, "don't know how to handle type %d\n", key->type);
2428       exit(1);
2429     }
2430   if (key->storage == KEY_STORAGE_VERTICAL_OFFSET)
2431     {
2432       /* put offset/len in incore */
2433       data_addid(newincore, data->lastverticaloffset + oldvincorelen);
2434       oldvincorelen = xd->len - oldvincorelen;
2435       data_addid(newincore, oldvincorelen);
2436     }
2437 }
2438
2439 void
2440 repodata_internalize(Repodata *data)
2441 {
2442   Repokey *key, solvkey;
2443   Id entry, nentry;
2444   Id schemaid, *schema, *sp, oldschema, *keyp, *keypstart, *seen;
2445   unsigned char *dp, *ndp;
2446   int newschema, oldcount;
2447   struct extdata newincore;
2448   struct extdata newvincore;
2449   Id solvkeyid;
2450
2451   if (!data->attrs && !data->xattrs)
2452     return;
2453
2454   newvincore.buf = data->vincore;
2455   newvincore.len = data->vincorelen;
2456
2457   /* find the solvables key, create if needed */
2458   memset(&solvkey, 0, sizeof(solvkey));
2459   solvkey.name = REPOSITORY_SOLVABLES;
2460   solvkey.type = REPOKEY_TYPE_FLEXARRAY;
2461   solvkey.size = 0;
2462   solvkey.storage = KEY_STORAGE_INCORE;
2463   solvkeyid = repodata_key2id(data, &solvkey, data->end != data->start ? 1 : 0);
2464
2465   schema = sat_malloc2(data->nkeys, sizeof(Id));
2466   seen = sat_malloc2(data->nkeys, sizeof(Id));
2467
2468   /* Merge the data already existing (in data->schemata, ->incoredata and
2469      friends) with the new attributes in data->attrs[].  */
2470   nentry = data->end - data->start;
2471   memset(&newincore, 0, sizeof(newincore));
2472   data_addid(&newincore, 0);    /* start data at offset 1 */
2473
2474   data->mainschema = 0;
2475   data->mainschemaoffsets = sat_free(data->mainschemaoffsets);
2476
2477   /* join entry data */
2478   /* we start with the meta data, entry -1 */
2479   for (entry = -1; entry < nentry; entry++)
2480     {
2481       memset(seen, 0, data->nkeys * sizeof(Id));
2482       oldschema = 0;
2483       dp = data->incoredata;
2484       if (dp)
2485         {
2486           dp += entry >= 0 ? data->incoreoffset[entry] : 1;
2487           dp = data_read_id(dp, &oldschema);
2488         }
2489 #if 0
2490 fprintf(stderr, "oldschema %d\n", oldschema);
2491 fprintf(stderr, "schemata %d\n", data->schemata[oldschema]);
2492 fprintf(stderr, "schemadata %p\n", data->schemadata);
2493 #endif
2494       /* seen: -1: old data  0: skipped  >0: id + 1 */
2495       newschema = 0;
2496       oldcount = 0;
2497       sp = schema;
2498       for (keyp = data->schemadata + data->schemata[oldschema]; *keyp; keyp++)
2499         {
2500           if (seen[*keyp])
2501             {
2502               pool_debug(data->repo->pool, SAT_FATAL, "Inconsistent old data (key occured twice).\n");
2503               exit(1);
2504             }
2505           seen[*keyp] = -1;
2506           *sp++ = *keyp;
2507           oldcount++;
2508         }
2509       if (entry >= 0)
2510         keyp = data->attrs ? data->attrs[entry] : 0;
2511       else
2512         {
2513           /* strip solvables key */
2514           *sp = 0;
2515           for (sp = keyp = schema; *sp; sp++)
2516             if (*sp != solvkeyid)
2517               *keyp++ = *sp;
2518             else
2519               oldcount--;
2520           sp = keyp;
2521           seen[solvkeyid] = 0;
2522           keyp = data->xattrs ? data->xattrs[1] : 0;
2523         }
2524       if (keyp)
2525         for (; *keyp; keyp += 2)
2526           {
2527             if (!seen[*keyp])
2528               {
2529                 newschema = 1;
2530                 *sp++ = *keyp;
2531               }
2532             seen[*keyp] = keyp[1] + 1;
2533           }
2534       if (entry < 0 && data->end != data->start)
2535         {
2536           *sp++ = solvkeyid;
2537           newschema = 1;
2538         }
2539       *sp = 0;
2540       if (newschema)
2541         /* Ideally we'd like to sort the new schema here, to ensure
2542            schema equality independend of the ordering.  We can't do that
2543            yet.  For once see below (old ids need to come before new ids).
2544            An additional difficulty is that we also need to move
2545            the values with the keys.  */
2546         schemaid = repodata_schema2id(data, schema, 1);
2547       else
2548         schemaid = oldschema;
2549
2550
2551       /* Now create data blob.  We walk through the (possibly new) schema
2552          and either copy over old data, or insert the new.  */
2553       /* XXX Here we rely on the fact that the (new) schema has the form
2554          o1 o2 o3 o4 ... | n1 n2 n3 ...
2555          (oX being the old keyids (possibly overwritten), and nX being
2556           the new keyids).  This rules out sorting the keyids in order
2557          to ensure a small schema count.  */
2558       if (entry >= 0)
2559         data->incoreoffset[entry] = newincore.len;
2560       data_addid(&newincore, schemaid);
2561       if (entry == -1)
2562         {
2563           data->mainschema = schemaid;
2564           data->mainschemaoffsets = sat_calloc(sp - schema, sizeof(Id));
2565         }
2566       keypstart = data->schemadata + data->schemata[schemaid];
2567       for (keyp = keypstart; *keyp; keyp++)
2568         {
2569           if (entry == -1)
2570             data->mainschemaoffsets[keyp - keypstart] = newincore.len;
2571           if (*keyp == solvkeyid)
2572             {
2573               /* add flexarray entry count */
2574               data_addid(&newincore, data->end - data->start);
2575               break;
2576             }
2577           key = data->keys + *keyp;
2578 #if 0
2579           fprintf(stderr, "internalize %d(%d):%s:%s\n", entry, entry + data->start, id2str(data->repo->pool, key->name), id2str(data->repo->pool, key->type));
2580 #endif
2581           ndp = dp;
2582           if (oldcount)
2583             {
2584               /* Skip the data associated with this old key.  */
2585               if (key->storage == KEY_STORAGE_VERTICAL_OFFSET)
2586                 {
2587                   ndp = data_skip(dp, REPOKEY_TYPE_ID);
2588                   ndp = data_skip(ndp, REPOKEY_TYPE_ID);
2589                 }
2590               else if (key->storage == KEY_STORAGE_INCORE)
2591                 ndp = data_skip_key(data, dp, key);
2592               oldcount--;
2593             }
2594           if (seen[*keyp] == -1)
2595             {
2596               /* If this key was an old one _and_ was not overwritten with
2597                  a different value copy over the old value (we skipped it
2598                  above).  */
2599               if (dp != ndp)
2600                 data_addblob(&newincore, dp, ndp - dp);
2601               seen[*keyp] = 0;
2602             }
2603           else if (seen[*keyp])
2604             {
2605               /* Otherwise we have a new value.  Parse it into the internal
2606                  form.  */
2607               repodata_serialize_key(data, &newincore, &newvincore,
2608                                      schema, key, seen[*keyp] - 1);
2609             }
2610           dp = ndp;
2611         }
2612       if (entry >= 0 && data->attrs && data->attrs[entry])
2613         data->attrs[entry] = sat_free(data->attrs[entry]);
2614     }
2615   /* free all xattrs */
2616   for (entry = 0; entry < data->nxattrs; entry++)
2617     if (data->xattrs[entry])
2618       sat_free(data->xattrs[entry]);
2619   data->xattrs = sat_free(data->xattrs);
2620   data->nxattrs = 0;
2621
2622   data->lasthandle = 0;
2623   data->lastkey = 0;
2624   data->lastdatalen = 0;
2625   sat_free(schema);
2626   sat_free(seen);
2627   repodata_free_schemahash(data);
2628
2629   sat_free(data->incoredata);
2630   data->incoredata = newincore.buf;
2631   data->incoredatalen = newincore.len;
2632   data->incoredatafree = 0;
2633
2634   sat_free(data->vincore);
2635   data->vincore = newvincore.buf;
2636   data->vincorelen = newvincore.len;
2637
2638   data->attrs = sat_free(data->attrs);
2639   data->attrdata = sat_free(data->attrdata);
2640   data->attriddata = sat_free(data->attriddata);
2641   data->attrdatalen = 0;
2642   data->attriddatalen = 0;
2643 }
2644
2645 void
2646 repodata_disable_paging(Repodata *data)
2647 {
2648   if (maybe_load_repodata(data, 0))
2649     repopagestore_disable_paging(&data->store);
2650 }
2651
2652 static void
2653 repodata_load_stub(Repodata *data)
2654 {
2655   Repo *repo = data->repo;
2656   Pool *pool = repo->pool;
2657   int r;
2658
2659   if (!pool->loadcallback)
2660     {
2661       data->state = REPODATA_ERROR;
2662       return;
2663     }
2664   data->state = REPODATA_LOADING;
2665   r = pool->loadcallback(pool, data, pool->loadcallbackdata);
2666   if (!r)
2667     data->state = REPODATA_ERROR;
2668 }
2669
2670 void
2671 repodata_create_stubs(Repodata *data)
2672 {
2673   Repo *repo = data->repo;
2674   Pool *pool = repo->pool;
2675   Repodata *sdata;
2676   int *stubdataids;
2677   Dataiterator di;
2678   Id xkeyname = 0;
2679   int i, cnt = 0;
2680
2681   dataiterator_init(&di, pool, repo, SOLVID_META, REPOSITORY_EXTERNAL, 0, 0);
2682   while (dataiterator_step(&di))
2683     cnt++;
2684   dataiterator_free(&di);
2685   if (!cnt)
2686     return;
2687   stubdataids = sat_calloc(cnt, sizeof(*stubdataids));
2688   for (i = 0; i < cnt; i++)
2689     {
2690       sdata = repo_add_repodata(repo, 0);
2691       if (data->end > data->start)
2692         {
2693           repodata_extend(sdata, data->start);
2694           repodata_extend(sdata, data->end - 1);
2695         }
2696       stubdataids[i] = sdata - repo->repodata;
2697       sdata->state = REPODATA_STUB;
2698       sdata->loadcallback = repodata_load_stub;
2699     }
2700   i = 0;
2701   dataiterator_init(&di, pool, repo, SOLVID_META, REPOSITORY_EXTERNAL, 0, 0);
2702   sdata = 0;
2703   while (dataiterator_step(&di))
2704     {
2705       if (di.key->name == REPOSITORY_EXTERNAL && !di.nparents)
2706         {
2707           dataiterator_entersub(&di);
2708           sdata = repo->repodata + stubdataids[i++];
2709           xkeyname = 0;
2710           continue;
2711         }
2712       switch (di.key->type)
2713         {
2714         case REPOKEY_TYPE_ID:
2715           repodata_set_id(sdata, SOLVID_META, di.key->name, di.kv.id);
2716           break;
2717         case REPOKEY_TYPE_CONSTANTID:
2718           repodata_set_constantid(sdata, SOLVID_META, di.key->name, di.kv.id);
2719           break;
2720         case REPOKEY_TYPE_STR:
2721           repodata_set_str(sdata, SOLVID_META, di.key->name, di.kv.str);
2722           break;
2723         case REPOKEY_TYPE_VOID:
2724           repodata_set_void(sdata, SOLVID_META, di.key->name);
2725           break;
2726         case REPOKEY_TYPE_NUM:
2727           repodata_set_num(sdata, SOLVID_META, di.key->name, di.kv.num);
2728           break;
2729         case REPOKEY_TYPE_MD5:
2730         case REPOKEY_TYPE_SHA1:
2731         case REPOKEY_TYPE_SHA256:
2732           repodata_set_checksum(sdata, SOLVID_META, di.key->name, di.key->type, di.kv.str);
2733           break;
2734         case REPOKEY_TYPE_IDARRAY:
2735           repodata_add_idarray(sdata, SOLVID_META, di.key->name, di.kv.id);
2736           if (di.key->name == REPOSITORY_KEYS)
2737             {
2738               Repokey xkey;
2739
2740               if (!xkeyname)
2741                 {
2742                   if (!di.kv.eof)
2743                     xkeyname = di.kv.id;
2744                   continue;
2745                 }
2746               xkey.name = xkeyname;
2747               xkey.type = di.kv.id;
2748               xkey.storage = KEY_STORAGE_INCORE;
2749               xkey.size = 0; 
2750               repodata_key2id(sdata, &xkey, 1);
2751               xkeyname = 0;
2752             }
2753         }
2754     }
2755   dataiterator_free(&di);
2756   for (i = 0; i < cnt; i++)
2757     repodata_internalize(repo->repodata + stubdataids[i]);
2758   sat_free(stubdataids);
2759 }
2760
2761 /*
2762 vim:cinoptions={.5s,g0,p5,t0,(0,^-0.5s,n-0.5s:tw=78:cindent:sw=4:
2763 */