ef36e61eb938f7588d46c22bff32edb3a0a6e4cf
[platform/upstream/libsolv.git] / tools / repo_write.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  * repo_write.c
10  * 
11  * Write Repo data out to binary file
12  * 
13  * See doc/README.format for a description 
14  * of the binary file format
15  * 
16  */
17
18 #include <sys/types.h>
19 #include <limits.h>
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <assert.h>
25
26 #include "pool.h"
27 #include "util.h"
28 #include "repo_write.h"
29
30 /* #define IGNORE_NEED_FREQUENCY */
31
32 /*------------------------------------------------------------------*/
33 /* Id map optimizations */
34
35 typedef struct needid {
36   Id need;
37   Id map;
38 } NeedId;
39
40
41 #define RELOFF(id) (pool->ss.nstrings + GETRELID(id))
42
43 /*
44  * increment need Id
45  * idarray: array of Ids, ID_NULL terminated
46  * needid: array of Id->NeedId
47  * 
48  * return count
49  * 
50  */
51
52 static int
53 incneedid(Pool *pool, Id *idarray, NeedId *needid)
54 {
55   if (!idarray)
56     return 0;
57
58   Id id;
59   int n = 0;
60
61   while ((id = *idarray++) != 0)
62     {
63       n++;
64       while (ISRELDEP(id))
65         {
66           Reldep *rd = GETRELDEP(pool, id);
67           needid[RELOFF(id)].need++;
68           if (ISRELDEP(rd->evr))
69             {
70               Id ida[2];
71               ida[0] = rd->evr;
72               ida[1] = 0;
73               incneedid(pool, ida, needid);
74             }
75           else
76             needid[rd->evr].need++;
77           id = rd->name;
78         }
79       needid[id].need++;
80     }
81   return n + 1;
82 }
83
84
85 /*
86  * 
87  */
88
89 static int
90 needid_cmp_need(const void *ap, const void *bp)
91 {
92   const NeedId *a = ap;
93   const NeedId *b = bp;
94   int r;
95   r = b->need - a->need;
96   if (r)
97     return r;
98   return a->map - b->map;
99 }
100
101 static Pool *cmp_pool;
102 static int
103 needid_cmp_need_s(const void *ap, const void *bp)
104 {
105   const NeedId *a = ap;
106   const NeedId *b = bp;
107   if (a == b)
108     return 0;
109 #ifdef IGNORE_NEED_FREQUENCY
110   if (a->need == 0)
111     return 1;
112   else if (b->need == 0)
113     return -1;
114 #else
115   int r;
116   r = b->need - a->need;
117   if (r)
118     return r;
119 #endif
120   char *as = cmp_pool->ss.stringspace + cmp_pool->ss.strings[a->map];
121   char *bs = cmp_pool->ss.stringspace + cmp_pool->ss.strings[b->map];
122   size_t alen = strlen (as);
123   size_t blen = strlen (bs);
124   return memcmp (as, bs, alen < blen ? alen + 1 : blen + 1);
125 }
126
127
128 /*------------------------------------------------------------------*/
129 /* output routines */
130
131 /*
132  * unsigned 32-bit
133  */
134
135 static void
136 write_u32(FILE *fp, unsigned int x)
137 {
138   if (putc(x >> 24, fp) == EOF ||
139       putc(x >> 16, fp) == EOF ||
140       putc(x >> 8, fp) == EOF ||
141       putc(x, fp) == EOF)
142     {
143       perror("write error");
144       exit(1);
145     }
146 }
147
148
149 /*
150  * unsigned 8-bit
151  */
152
153 static void
154 write_u8(FILE *fp, unsigned int x)
155 {
156   if (putc(x, fp) == EOF)
157     {
158       perror("write error");
159       exit(1);
160     }
161 }
162
163
164 /*
165  * Id
166  */
167
168 static void
169 write_id(FILE *fp, Id x)
170 {
171   if (x >= (1 << 14))
172     {
173       if (x >= (1 << 28))
174         putc((x >> 28) | 128, fp);
175       if (x >= (1 << 21))
176         putc((x >> 21) | 128, fp);
177       putc((x >> 14) | 128, fp);
178     }
179   if (x >= (1 << 7))
180     putc((x >> 7) | 128, fp);
181   if (putc(x & 127, fp) == EOF)
182     {
183       perror("write error");
184       exit(1);
185     }
186 }
187
188 static void
189 write_str(FILE *fp, const char *str)
190 {
191   if (fputs (str, fp) == EOF
192       || putc (0, fp) == EOF)
193     {
194       perror("write error");
195       exit(1);
196     }
197 }
198
199 /*
200  * Array of Ids
201  */
202
203 static void
204 write_idarray(FILE *fp, Pool *pool, NeedId *needid, Id *ids)
205 {
206   Id id;
207   if (!ids)
208     return;
209   if (!*ids)
210     {
211       write_u8(fp, 0);
212       return;
213     }
214   for (;;)
215     {
216       id = *ids++;
217       if (needid)
218         id = needid[ISRELDEP(id) ? RELOFF(id) : id].need;
219       if (id >= 64)
220         id = (id & 63) | ((id & ~63) << 1);
221       if (!*ids)
222         {
223           write_id(fp, id);
224           return;
225         }
226       write_id(fp, id | 64);
227     }
228 }
229
230 static int
231 cmp_ids (const void *pa, const void *pb)
232 {
233   Id a = *(Id *)pa;
234   Id b = *(Id *)pb;
235   return a - b;
236 }
237
238 static void
239 write_idarray_sort(FILE *fp, Pool *pool, NeedId *needid, Id *ids)
240 {
241   int len, i;
242   if (!ids)
243     return;
244   if (!*ids)
245     {
246       write_u8 (fp, 0);
247       return;
248     }
249   /* If we ever share idarrays we can't do this in-place.  */
250   for (len = 0; ids[len]; len++)
251     {
252       Id id = ids[len];
253       if (needid)
254         ids[len] = needid[ISRELDEP(id) ? RELOFF(id) : id].need;
255     }
256
257   /* That bloody solvable:prereqmarker needs to stay in position :-(  */
258   Id prereq = SOLVABLE_PREREQMARKER;
259   if (needid)
260     prereq = needid[prereq].need;
261   for (i = 0; i < len; i++)
262     if (ids[i] == prereq)
263       break;
264   if (i > 1)
265     qsort (ids, i, sizeof (Id), cmp_ids);
266   if ((len - i) > 2)
267     qsort (ids + i + 1, len - i - 1, sizeof (Id), cmp_ids);
268
269   Id old = 0;
270   for (i = 0; i < len; i++)
271     /* Ugly PREREQ handling.  A "difference" of 0 is the prereq marker,
272        hence all real differences are offsetted by 1.  Otherwise we would
273        have to handle negative differences, which would cost code space for
274        the encoding of the sign.  We loose the exact mapping of prereq here,
275        but we know the result, so we can recover from that in the reader.  */
276     if (ids[i] == prereq)
277       old = ids[i] = 0;
278     else
279       {
280         ids[i] -= old;
281         old = ids[i] + old;
282         /* XXX If difference is zero we have multiple equal elements,
283            we might want to skip writing them out.  */
284         ids[i]++;
285       }
286
287   /* The differencing above produces many runs of ones and twos.  I tried
288      fairly elaborate schemes to RLE those, but they give only very mediocre
289      improvements in compression, as coding the escapes costs quite some
290      space.  Even if they are coded only as bits in IDs.  The best improvement
291      was about 2.7% for the whole .solv file.  It's probably better to
292      invest some complexity into sharing idarrays, than RLEing.  */
293   for (i = 0; i < len - 1; i++)
294     {
295       Id id = ids[i];
296       if (id >= 64)
297         id = (id & 63) | ((id & ~63) << 1);
298       write_id(fp, id | 64);
299     }
300   old = ids[i];
301   if (old >= 64)
302     old = (old & 63) | ((old & ~63) << 1);
303   write_id(fp, old);
304 }
305
306 /*
307  * Repo
308  */
309
310 void
311 repo_write(Repo *repo, FILE *fp)
312 {
313   Pool *pool = repo->pool;
314   int i, n;
315   Solvable *s;
316   NeedId *needid;
317   int nstrings, nrels, nkeys, nschemata;
318   unsigned int sizeid;
319   unsigned int solv_flags;
320   Reldep *ran;
321   Id *idarraydata;
322
323   int idsizes[RPM_RPMDBID + 1];
324   int id2key[RPM_RPMDBID + 1];
325   int nsolvables;
326
327   Id *schemadata, *schemadatap, *schema, *sp;
328   Id schemaid;
329   int schemadatalen;
330   Id *solvschema;       /* schema of our solvables */
331   Id lastschema[256];
332   Id lastschemakey[256];
333
334   /* For the info block.  */
335   Id repodata_id, hello_id;
336
337   repodata_id = str2id (pool, "repodata", 1);
338   hello_id = str2id (pool, "hello", 1);
339
340   nsolvables = 0;
341   idarraydata = repo->idarraydata;
342
343   needid = (NeedId *)xcalloc(pool->ss.nstrings + pool->nrels, sizeof(*needid));
344
345   needid[repodata_id].need++;
346   needid[hello_id].need++;
347   for (i = 0; i < repo->nrepodata; i++)
348     {
349       int j;
350       for (j = 0; j < repo->repodata[i].nkeys; j++)
351         needid[repo->repodata[i].keys[j].name].need++;
352     }
353
354   memset(idsizes, 0, sizeof(idsizes));
355
356   for (i = repo->start, s = pool->solvables + i; i < repo->end; i++, s++)
357     {
358       if (s->repo != repo)
359         continue;
360       nsolvables++;
361       needid[s->name].need++;
362       needid[s->arch].need++;
363       needid[s->evr].need++;
364       if (s->vendor)
365         {
366           needid[s->vendor].need++;
367           idsizes[SOLVABLE_VENDOR] = 1;
368         }
369       if (s->provides)
370         idsizes[SOLVABLE_PROVIDES]    += incneedid(pool, idarraydata + s->provides, needid);
371       if (s->requires)
372         idsizes[SOLVABLE_REQUIRES]    += incneedid(pool, idarraydata + s->requires, needid);
373       if (s->conflicts)
374         idsizes[SOLVABLE_CONFLICTS]   += incneedid(pool, idarraydata + s->conflicts, needid);
375       if (s->obsoletes)
376         idsizes[SOLVABLE_OBSOLETES]   += incneedid(pool, idarraydata + s->obsoletes, needid);
377       if (s->recommends)
378         idsizes[SOLVABLE_RECOMMENDS]  += incneedid(pool, idarraydata + s->recommends, needid);
379       if (s->suggests)
380         idsizes[SOLVABLE_SUGGESTS]    += incneedid(pool, idarraydata + s->suggests, needid);
381       if (s->supplements)
382         idsizes[SOLVABLE_SUPPLEMENTS] += incneedid(pool, idarraydata + s->supplements, needid);
383       if (s->enhances)
384         idsizes[SOLVABLE_ENHANCES]    += incneedid(pool, idarraydata + s->enhances, needid);
385       if (s->freshens)
386         idsizes[SOLVABLE_FRESHENS]    += incneedid(pool, idarraydata + s->freshens, needid);
387     }
388   if (nsolvables != repo->nsolvables)
389     abort();
390
391   idsizes[SOLVABLE_NAME] = 1;
392   idsizes[SOLVABLE_ARCH] = 1;
393   idsizes[SOLVABLE_EVR] = 1;
394   if (repo->rpmdbid)
395     idsizes[RPM_RPMDBID] = 1;
396
397   for (i = SOLVABLE_NAME; i <= RPM_RPMDBID; i++)
398     {
399       if (idsizes[i])
400         needid[i].need++;
401     }
402
403   needid[0].need = 0;
404   needid[pool->ss.nstrings].need = 0;
405   for (i = 0; i < pool->ss.nstrings + pool->nrels; i++)
406     needid[i].map = i;
407
408   cmp_pool = pool;
409   qsort(needid + 1, pool->ss.nstrings - 1, sizeof(*needid), needid_cmp_need_s);
410   qsort(needid + pool->ss.nstrings, pool->nrels, sizeof(*needid), needid_cmp_need);
411
412   sizeid = 0;
413   for (i = 1; i < pool->ss.nstrings; i++)
414     {
415       if (!needid[i].need)
416         break;
417       needid[i].need = 0;
418       sizeid += strlen(pool->ss.stringspace + pool->ss.strings[needid[i].map]) + 1;
419     }
420
421   nstrings = i;
422   for (i = 0; i < nstrings; i++)
423     needid[needid[i].map].need = i;
424
425   for (i = 0; i < pool->nrels; i++)
426     {
427       if (!needid[pool->ss.nstrings + i].need)
428         break;
429       else
430         needid[pool->ss.nstrings + i].need = 0;
431     }
432
433   nrels = i;
434   for (i = 0; i < nrels; i++)
435     {
436       needid[needid[pool->ss.nstrings + i].map].need = nstrings + i;
437     }
438
439   /* find the keys we need */
440   nkeys = 1;
441   memset(id2key, 0, sizeof(id2key));
442   for (i = SOLVABLE_NAME; i <= RPM_RPMDBID; i++)
443     if (idsizes[i])
444       id2key[i] = nkeys++;
445
446   /* find the schemata we need */
447   solvschema = xcalloc(repo->nsolvables, sizeof(Id));
448
449   memset(lastschema, 0, sizeof(lastschema));
450   memset(lastschemakey, 0, sizeof(lastschemakey));
451   schemadata = xmalloc(256 * sizeof(Id));
452   schemadatalen = 256;
453   schemadatap = schemadata;
454   *schemadatap++ = 0;
455   schemadatalen--;
456   nschemata = 0;
457   for (i = repo->start, s = pool->solvables + i, n = 0; i < repo->end; i++, s++)
458     {
459       unsigned int h;
460       Id *sp;
461
462       if (s->repo != repo)
463         continue;
464       if (schemadatalen < 32)
465         {
466           int l = schemadatap - schemadata;
467           fprintf(stderr, "growing schemadata\n");
468           schemadata = xrealloc(schemadata, (schemadatap - schemadata + 256) * sizeof(Id));
469           schemadatalen = 256;
470           schemadatap = schemadata + l;
471         }
472       schema = schemadatap;
473       *schema++ = SOLVABLE_NAME;
474       *schema++ = SOLVABLE_ARCH;
475       *schema++ = SOLVABLE_EVR;
476       if (s->vendor)
477         *schema++ = SOLVABLE_VENDOR;
478       if (s->provides)
479         *schema++ = SOLVABLE_PROVIDES;
480       if (s->obsoletes)
481         *schema++ = SOLVABLE_OBSOLETES;
482       if (s->conflicts)
483         *schema++ = SOLVABLE_CONFLICTS;
484       if (s->requires)
485         *schema++ = SOLVABLE_REQUIRES;
486       if (s->recommends)
487         *schema++ = SOLVABLE_RECOMMENDS;
488       if (s->suggests)
489         *schema++ = SOLVABLE_SUGGESTS;
490       if (s->supplements)
491         *schema++ = SOLVABLE_SUPPLEMENTS;
492       if (s->enhances)
493         *schema++ = SOLVABLE_ENHANCES;
494       if (s->freshens)
495         *schema++ = SOLVABLE_FRESHENS;
496       if (repo->rpmdbid)
497         *schema++ = RPM_RPMDBID;
498       *schema++ = 0;
499       for (sp = schemadatap, h = 0; *sp; )
500         h = h * 7 + *sp++;
501       h &= 255;
502       if (lastschema[h] && !memcmp(schemadata + lastschema[h], schemadatap, (schema - schemadatap) * sizeof(Id)))
503         {
504           solvschema[n++] = lastschemakey[h];
505           continue;
506         }
507       schemaid = 0;
508       for (sp = schemadata + 1; sp < schemadatap; )
509         {
510           if (!memcmp(sp, schemadatap, (schema - schemadatap) * sizeof(Id)))
511             break;
512           while (*sp++)
513             ;
514           schemaid++;
515         }
516       if (sp >= schemadatap)
517         {
518           if (schemaid != nschemata)
519             abort();
520           lastschema[h] = schemadatap - schemadata;
521           lastschemakey[h] = schemaid;
522           schemadatalen -= schema - schemadatap;
523           schemadatap = schema;
524           nschemata++;
525         }
526       solvschema[n++] = schemaid;
527     }
528   /* convert all schemas to keys */
529   for (sp = schemadata; sp < schemadatap; sp++)
530     *sp = id2key[*sp];
531
532   /* write file header */
533   write_u32(fp, 'S' << 24 | 'O' << 16 | 'L' << 8 | 'V');
534   write_u32(fp, SOLV_VERSION_3);
535
536   /* write counts */
537   write_u32(fp, nstrings);
538   write_u32(fp, nrels);
539   write_u32(fp, nsolvables);
540   write_u32(fp, nkeys);
541   write_u32(fp, nschemata);
542   write_u32(fp, 2);  /* Info block.  */
543   solv_flags = 0;
544   solv_flags |= SOLV_FLAG_PREFIX_POOL;
545 #if 0
546   solv_flags |= SOLV_FLAG_VERTICAL;
547 #endif
548   write_u32(fp, solv_flags);
549
550   /* Build the prefix-encoding of the string pool.  We need to know
551      the size of that before writing it to the file, so we have to
552      build a separate buffer for that.  As it's temporarily possible
553      that this actually is an expansion we can't easily reuse the 
554      stringspace for this.  The max expansion per string is 1 byte,
555      so it will fit into sizeid+nstrings bytes.  */
556   char *prefix = xmalloc (sizeid + nstrings);
557   char *pp = prefix;
558   char *old_str = "";
559   for (i = 1; i < nstrings; i++)
560     {
561       char *str = pool->ss.stringspace + pool->ss.strings[needid[i].map];
562       int same;
563       size_t len;
564       for (same = 0; same < 255; same++)
565         if (old_str[same] != str[same])
566           break;
567       *pp++ = same;
568       len = strlen (str + same) + 1;
569       memcpy (pp, str + same, len);
570       pp += len;
571       old_str = str;
572     }
573
574   /*
575    * write strings
576    */
577   write_u32(fp, sizeid);
578   write_u32(fp, pp - prefix);
579   if (fwrite(prefix, pp - prefix, 1, fp) != 1)
580     {
581       perror("write error");
582       exit(1);
583     }
584   xfree (prefix);
585
586   /*
587    * write RelDeps
588    */
589   for (i = 0; i < nrels; i++)
590     {
591       ran = pool->rels + (needid[pool->ss.nstrings + i].map - pool->ss.nstrings);
592       write_id(fp, needid[ISRELDEP(ran->name) ? RELOFF(ran->name) : ran->name].need);
593       write_id(fp, needid[ISRELDEP(ran->evr) ? RELOFF(ran->evr) : ran->evr].need);
594       write_u8( fp, ran->flags);
595     }
596
597   /*
598    * write keys
599    */
600   for (i = SOLVABLE_NAME; i <= RPM_RPMDBID; i++)
601     {
602       if (!idsizes[i])
603         continue;
604       write_id(fp, needid[i].need);
605       if (i >= SOLVABLE_PROVIDES && i <= SOLVABLE_FRESHENS)
606         write_id(fp, TYPE_REL_IDARRAY);
607       else if (i == RPM_RPMDBID)
608         write_id(fp, TYPE_U32);
609       else
610         write_id(fp, TYPE_ID);
611       write_id(fp, idsizes[i]);
612     }
613
614   /*
615    * write schemata
616    */
617   write_id(fp, schemadatap - schemadata - 1);
618   for (sp = schemadata + 1; sp < schemadatap; )
619     {
620       write_idarray(fp, pool, 0, sp);
621       while (*sp++)
622         ;
623     }
624
625   /*
626    * write info block
627    */
628   write_id (fp, needid[hello_id].need);
629   write_id (fp, TYPE_COUNT_NAMED);
630   write_id (fp, 1);
631     write_id (fp, 0); //name
632       write_id (fp, TYPE_STR);
633       write_str (fp, "doll");
634
635   write_id (fp, needid[repodata_id].need);
636   write_id (fp, TYPE_COUNT_NAMED);
637   write_id (fp, repo->nrepodata);
638   for (i = 0; i < repo->nrepodata; i++)
639     {
640       int j;
641       write_id (fp, 0);         /* no name, isn't important here */
642       write_id (fp, TYPE_COUNT_NAMED);
643       /* Don't emit the embedded attributes.  */
644       if (repo->repodata[i].name == 0)
645         {
646           write_id (fp, 0);     /* count */
647           continue;
648         }
649       write_id (fp, 2);         /* 2 items, the filename and the keys */
650         /* 1 filename */
651         write_id (fp, 0);       /* no name */
652         write_id (fp, TYPE_STR);
653         write_str (fp, repo->repodata[i].name);
654
655         /* 2 keys */
656         write_id (fp, 0);       /* no name */
657         write_id (fp, TYPE_COUNTED);
658         write_id (fp, repo->repodata[i].nkeys * 2);
659         write_id (fp, TYPE_ID);
660         for (j = 0; j < repo->repodata[i].nkeys; j++)
661           {
662             write_id (fp, needid[repo->repodata[i].keys[j].name].need);
663             write_id (fp, repo->repodata[i].keys[j].type);
664           }
665     }
666
667 #if 0
668   if (1)
669     {
670       Id id, key;
671
672       for (i = 0; i < nsolvables; i++)
673         write_id(fp, solvschema[i]);
674       unsigned char *used = xmalloc(nschemata);
675       for (id = SOLVABLE_NAME; id <= RPM_RPMDBID; id++)
676         {
677           key = id2key[id];
678           memset(used, 0, nschemata);
679           for (sp = schemadata + 1, i = 0; sp < schemadatap; sp++)
680             {
681               if (*sp == 0)
682                 i++;
683               else if (*sp == key)
684                 used[i] = 1;
685             }
686           for (i = repo->start, s = pool->solvables + i, n = 0; i < repo->end; i++, s++)
687             {
688               if (s->repo != repo)
689                 continue;
690               if (!used[solvschema[n++]])
691                 continue;
692               switch(id)
693                 {
694                 case SOLVABLE_NAME:
695                   write_id(fp, needid[s->name].need);
696                   break;
697                 case SOLVABLE_ARCH:
698                   write_id(fp, needid[s->arch].need);
699                   break;
700                 case SOLVABLE_EVR:
701                   write_id(fp, needid[s->evr].need);
702                   break;
703                 case SOLVABLE_VENDOR:
704                   write_id(fp, needid[s->vendor].need);
705                   break;
706                 case RPM_RPMDBID:
707                   write_u32(fp, repo->rpmdbid[i - repo->start]);
708                   break;
709                 case SOLVABLE_PROVIDES:
710                   write_idarray(fp, pool, needid, idarraydata + s->provides);
711                   break;
712                 case SOLVABLE_OBSOLETES:
713                   write_idarray(fp, pool, needid, idarraydata + s->obsoletes);
714                   break;
715                 case SOLVABLE_CONFLICTS:
716                   write_idarray(fp, pool, needid, idarraydata + s->conflicts);
717                   break;
718                 case SOLVABLE_REQUIRES:
719                   write_idarray(fp, pool, needid, idarraydata + s->requires);
720                   break;
721                 case SOLVABLE_RECOMMENDS:
722                   write_idarray(fp, pool, needid, idarraydata + s->recommends);
723                   break;
724                 case SOLVABLE_SUPPLEMENTS:
725                   write_idarray(fp, pool, needid, idarraydata + s->supplements);
726                   break;
727                 case SOLVABLE_SUGGESTS:
728                   write_idarray(fp, pool, needid, idarraydata + s->suggests);
729                   break;
730                 case SOLVABLE_ENHANCES:
731                   write_idarray(fp, pool, needid, idarraydata + s->enhances);
732                   break;
733                 case SOLVABLE_FRESHENS:
734                   write_idarray(fp, pool, needid, idarraydata + s->freshens);
735                   break;
736                 }
737             }
738         }
739       xfree(used);
740       xfree(needid);
741       xfree(solvschema);
742       xfree(schemadata);
743       return;
744     }
745   
746 #endif
747
748   /*
749    * write Solvables
750    */
751   for (i = repo->start, s = pool->solvables + i, n = 0; i < repo->end; i++, s++)
752     {
753       if (s->repo != repo)
754         continue;
755       /* keep in sync with schema generation! */
756       write_id(fp, solvschema[n++]);
757       write_id(fp, needid[s->name].need);
758       write_id(fp, needid[s->arch].need);
759       write_id(fp, needid[s->evr].need);
760       if (s->vendor)
761         write_id(fp, needid[s->vendor].need);
762       if (s->provides)
763         write_idarray_sort(fp, pool, needid, idarraydata + s->provides);
764       if (s->obsoletes)
765         write_idarray_sort(fp, pool, needid, idarraydata + s->obsoletes);
766       if (s->conflicts)
767         write_idarray_sort(fp, pool, needid, idarraydata + s->conflicts);
768       if (s->requires)
769         write_idarray_sort(fp, pool, needid, idarraydata + s->requires);
770       if (s->recommends)
771         write_idarray_sort(fp, pool, needid, idarraydata + s->recommends);
772       if (s->suggests)
773         write_idarray_sort(fp, pool, needid, idarraydata + s->suggests);
774       if (s->supplements)
775         write_idarray_sort(fp, pool, needid, idarraydata + s->supplements);
776       if (s->enhances)
777         write_idarray_sort(fp, pool, needid, idarraydata + s->enhances);
778       if (s->freshens)
779         write_idarray_sort(fp, pool, needid, idarraydata + s->freshens);
780       if (repo->rpmdbid)
781         write_u32(fp, repo->rpmdbid[i - repo->start]);
782     }
783
784   xfree(needid);
785   xfree(solvschema);
786   xfree(schemadata);
787 }
788
789 // EOF