upgrade rpm version to 4.14.1
[platform/upstream/rpm.git] / lib / backend / ndb / rpmxdb.c
1 #define _GNU_SOURCE
2
3 #include "system.h"
4
5 #include <rpm/rpmlog.h>
6
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <sys/file.h>
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <time.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <sys/mman.h>
17 #include <endian.h>
18 #include <libgen.h>
19
20 #include "rpmxdb.h"
21
22 #define RPMRC_OK 0
23 #define RPMRC_NOTFOUND 1
24 #define RPMRC_FAIL 2
25
26 typedef struct rpmxdb_s {
27     rpmpkgdb pkgdb;             /* master database */
28     char *filename;
29     int fd;
30     int flags;
31     int mode;
32     int rdonly;
33     unsigned int pagesize;
34     unsigned int generation;
35     unsigned int slotnpages;
36     unsigned int usergeneration;
37
38     unsigned char *mapped;
39     unsigned int mappedlen;
40
41     struct xdb_slot {
42         unsigned int slotno;
43         unsigned int blobtag;
44         unsigned int subtag;
45         unsigned char *mapped;
46         int mapflags;
47         unsigned int startpage;
48         unsigned int pagecnt;
49         void (*mapcallback)(rpmxdb xdb, void *data, void *newaddr, size_t newsize);
50         void *mapcallbackdata;
51         unsigned int next;
52         unsigned int prev;
53     } *slots;
54     unsigned int nslots;
55     unsigned int firstfree;
56     unsigned int usedblobpages;
57     unsigned int systempagesize;
58     int dofsync;
59 } *rpmxdb;
60
61
62 static inline void h2le(unsigned int x, unsigned char *p)
63 {
64     p[0] = x;
65     p[1] = x >> 8;
66     p[2] = x >> 16;
67     p[3] = x >> 24;
68 }
69
70 /* aligned versions */
71 static inline unsigned int le2ha(unsigned char *p)
72 {
73     unsigned int x = *(unsigned int *)p;
74     return le32toh(x);
75 }
76
77 static inline void h2lea(unsigned int x, unsigned char *p)
78 {
79     *(unsigned int *)p = htole32(x);
80 }
81
82
83 #define XDB_MAGIC     ('R' | 'p' << 8 | 'm' << 16 | 'X' << 24)
84 #define XDB_VERSION     0
85
86 #define XDB_OFFSET_MAGIC        0
87 #define XDB_OFFSET_VERSION      4
88 #define XDB_OFFSET_GENERATION   8
89 #define XDB_OFFSET_SLOTNPAGES   12
90 #define XDB_OFFSET_PAGESIZE     16
91 #define XDB_OFFSET_USERGENERATION       20
92
93 /* must be multiple of SLOT_SIZE */
94 #define XDB_HEADER_SIZE         32
95
96 #define SLOT_MAGIC     ('S' | 'l' << 8 | 'o' << 16)
97
98 #define SLOT_SIZE 16
99 #define SLOT_START (XDB_HEADER_SIZE / SLOT_SIZE)
100
101 static void rpmxdbUnmap(rpmxdb xdb)
102 {
103     munmap(xdb->mapped, xdb->mappedlen);
104     xdb->mapped = 0;
105     xdb->mappedlen = 0;
106 }
107
108 /* slot mapping functions */
109 static int mapslot(rpmxdb xdb, struct xdb_slot *slot)
110 {
111     void *mapped;
112     size_t off, size, shift;
113
114     if (slot->mapped)
115         return RPMRC_FAIL;
116     size = slot->pagecnt * xdb->pagesize;
117     off = slot->startpage * xdb->pagesize;
118     shift = 0;
119     if (xdb->pagesize != xdb->systempagesize) {
120         shift = off & (xdb->systempagesize - 1);
121         off -= shift;
122         size += shift;
123         size = (size + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
124     }
125     mapped = mmap(0, size, slot->mapflags, MAP_SHARED, xdb->fd, off);
126     if (mapped == MAP_FAILED)
127         return RPMRC_FAIL;
128     slot->mapped = (unsigned char *)mapped + shift;
129     return RPMRC_OK;
130 }
131
132 static void unmapslot(rpmxdb xdb, struct xdb_slot *slot)
133 {
134     size_t size;
135     unsigned char *mapped = slot->mapped;
136     if (!mapped)
137         return;
138     size = slot->pagecnt * xdb->pagesize;
139     if (xdb->pagesize != xdb->systempagesize) {
140         size_t off = slot->startpage * xdb->pagesize;
141         size_t shift = off & (xdb->systempagesize - 1);
142         mapped -= shift;
143         size += shift;
144         size = (size + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
145     }
146     munmap(mapped, size);
147     slot->mapped = 0;
148 }
149
150 static int remapslot(rpmxdb xdb, struct xdb_slot *slot, unsigned int newpagecnt)
151 {
152     void *mapped;
153     size_t off, oldsize, newsize, shift;
154     oldsize = slot->pagecnt * xdb->pagesize;
155     newsize = newpagecnt * xdb->pagesize;
156     off = slot->startpage * xdb->pagesize;
157     shift = 0;
158     if (xdb->pagesize != xdb->systempagesize) {
159         off = slot->startpage * xdb->pagesize;
160         shift = off & (xdb->systempagesize - 1);
161         off -= shift;
162         oldsize += shift;
163         oldsize = (oldsize + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
164         newsize += shift;
165         newsize = (newsize + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
166     }
167     if (slot->mapped)
168         mapped = mremap(slot->mapped - shift, oldsize, newsize, MREMAP_MAYMOVE);
169     else
170         mapped = mmap(0, newsize, slot->mapflags, MAP_SHARED, xdb->fd, off);
171     if (mapped == MAP_FAILED)
172         return RPMRC_FAIL;
173     slot->mapped = (unsigned char *)mapped + shift;
174     slot->pagecnt = newpagecnt;
175     return RPMRC_OK;
176 }
177
178
179 static int usedslots_cmp(const void *a, const void *b)
180 {
181     struct xdb_slot *sa = *(struct xdb_slot **)a;
182     struct xdb_slot *sb = *(struct xdb_slot **)b;
183     if (sa->startpage == sb->startpage) {
184       return sa->pagecnt > sb->pagecnt ? 1 : sa->pagecnt < sb->pagecnt ? -1 : 0;
185     }
186     return sa->startpage > sb->startpage ? 1 : -1;
187 }
188
189 static int rpmxdbReadHeader(rpmxdb xdb)
190 {
191     struct xdb_slot *slot;
192     unsigned int header[XDB_HEADER_SIZE / sizeof(unsigned int)];
193     unsigned int slotnpages, pagesize, generation, usergeneration, version;
194     unsigned int page, *lastfreep;
195     unsigned char *pageptr;
196     struct xdb_slot *slots, **usedslots, *lastslot;
197     unsigned int nslots;
198     unsigned int usedblobpages;
199     int i, nused, slotno;
200     struct stat stb;
201     size_t mapsize;
202
203     if (xdb->mapped) {
204         if (le2ha(xdb->mapped + XDB_OFFSET_GENERATION) == xdb->generation) {
205             return RPMRC_OK;
206         }
207         rpmxdbUnmap(xdb);
208     }
209     if (fstat(xdb->fd, &stb)) {
210         return RPMRC_FAIL;
211     }
212     if (pread(xdb->fd, header, sizeof(header), 0) != sizeof(header)) {
213         return RPMRC_FAIL;
214     }
215     if (le2ha((unsigned char *)header + XDB_OFFSET_MAGIC) != XDB_MAGIC)
216         return RPMRC_FAIL;
217     version = le2ha((unsigned char *)header + XDB_OFFSET_VERSION);
218     if (version != XDB_VERSION) {
219         rpmlog(RPMLOG_ERR, _("rpmxdb: Version mismatch. Expected version: %u. "
220             "Found version: %u\n"), XDB_VERSION, version);
221         return RPMRC_FAIL;
222     }
223
224     generation = le2ha((unsigned char *)header + XDB_OFFSET_GENERATION);
225     slotnpages = le2ha((unsigned char *)header + XDB_OFFSET_SLOTNPAGES);
226     pagesize = le2ha((unsigned char *)header + XDB_OFFSET_PAGESIZE);
227     usergeneration = le2ha((unsigned char *)header + XDB_OFFSET_USERGENERATION);
228     if (!slotnpages || !pagesize || stb.st_size % pagesize != 0)
229         return RPMRC_FAIL;
230     xdb->pagesize = pagesize;
231
232     /* round up */
233     mapsize = slotnpages * pagesize;
234     mapsize = (mapsize + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
235     xdb->mapped = mmap(0, mapsize, xdb->rdonly ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, xdb->fd, 0);
236     if ((void *)xdb->mapped == MAP_FAILED) {
237         xdb->mapped = 0;
238         return RPMRC_FAIL;
239     }
240     xdb->mappedlen = mapsize;
241
242     /* read in all slots */
243     xdb->firstfree = 0;
244     nslots = slotnpages * (pagesize / SLOT_SIZE) - SLOT_START + 1;
245     slots = calloc(nslots + 1, sizeof(struct xdb_slot));
246     if (!slots) {
247         rpmxdbUnmap(xdb);
248         return RPMRC_FAIL;
249     }
250     usedslots = calloc(nslots + 1, sizeof(int));
251     if (!usedslots) {
252         rpmxdbUnmap(xdb);
253         free(slots);
254         return RPMRC_FAIL;
255     }
256     nused = 0;
257     slotno = 1;
258     slot = slots + 1;
259     usedblobpages = 0;
260     lastfreep = &xdb->firstfree;
261     for (page = 0, pageptr = xdb->mapped; page < slotnpages; page++, pageptr += pagesize) {
262         unsigned int o;
263         for (o = page ? 0 : SLOT_START * SLOT_SIZE; o < pagesize; o += SLOT_SIZE, slotno++, slot++) {
264             unsigned char *pp = pageptr + o;
265             slot->slotno = slotno;
266             slot->subtag = le2ha(pp);
267             if ((slot->subtag & 0x00ffffff) != SLOT_MAGIC) {
268                 free(slots);
269                 free(usedslots);
270                 rpmxdbUnmap(xdb);
271                 return RPMRC_FAIL;
272             }
273             slot->subtag = (slot->subtag >> 24) & 255;
274             slot->blobtag = le2ha(pp + 4);
275             slot->startpage = le2ha(pp + 8);
276             slot->pagecnt = le2ha(pp + 12);
277             if (slot->pagecnt == 0 && slot->startpage)  /* empty but used slot? */
278                 slot->startpage = slotnpages;
279             if (!slot->startpage) {
280                 *lastfreep = slotno;
281                 lastfreep = &slot->next;
282             } else {
283                 usedslots[nused++] = slot;
284                 usedblobpages += slot->pagecnt;
285             }
286         }
287     }
288     if (nused > 1) {
289         qsort(usedslots, nused, sizeof(*usedslots), usedslots_cmp);
290     }
291     /* now chain em */
292     slots[0].pagecnt = slotnpages;
293     lastslot = slots;
294     for (i = 0; i < nused; i++, lastslot = slot) {
295         slot = usedslots[i];
296         if (lastslot->startpage + lastslot->pagecnt > slot->startpage) {
297             free(slots);
298             free(usedslots);
299             rpmxdbUnmap(xdb);
300             return RPMRC_FAIL;
301         }
302         lastslot->next = slot->slotno;
303         slot->prev = lastslot->slotno;
304     }
305     lastslot->next = nslots;
306     slots[nslots].slotno = nslots;
307     slots[nslots].prev = lastslot->slotno;
308     slots[nslots].startpage = stb.st_size / pagesize;
309     free(usedslots);
310     /* now sync with the old slot data */
311     if (xdb->slots) {
312         for (i = 1, slot = xdb->slots + i; i < xdb->nslots; i++, slot++) {
313             if (slot->startpage && (slot->mapped || slot->mapcallback)) {
314                 struct xdb_slot *nslot;
315                 if (i >= nslots || !slots[i].startpage || slots[i].blobtag != slot->blobtag || slots[i].subtag != slot->subtag) {
316                     /* slot is gone */
317                     if (slot->mapped) {
318                         unmapslot(xdb, slot);
319                         slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
320                     }
321                     continue;
322                 }
323                 nslot = slots + i;
324                 if (slot->mapcallback) {
325                     nslot->mapflags = slot->mapflags;
326                     nslot->mapcallback = slot->mapcallback;
327                     nslot->mapcallbackdata = slot->mapcallbackdata;
328                 }
329                 if (slot->startpage != nslot->startpage || slot->pagecnt != nslot->pagecnt) {
330                     /* slot moved or was resized */
331                     if (slot->mapped)
332                         unmapslot(xdb, slot);
333                     if (nslot->mapcallback) {
334                         if (nslot->pagecnt) {
335                             mapslot(xdb, nslot);
336                             nslot->mapcallback(xdb, nslot->mapcallbackdata, nslot->mapped, nslot->mapped ? nslot->pagecnt * xdb->pagesize : 0);
337                         } else {
338                             nslot->mapcallback(xdb, nslot->mapcallbackdata, 0, 0);
339                         }
340                     }
341                 }
342             }
343         }
344         free(xdb->slots);
345     }
346     xdb->slots = slots;
347     xdb->nslots = nslots;
348     xdb->generation = generation;
349     xdb->slotnpages = slotnpages;
350     xdb->usergeneration = usergeneration;
351     xdb->usedblobpages = usedblobpages;
352     return RPMRC_OK;
353 }
354
355 static int rpmxdbWriteHeader(rpmxdb xdb)
356 {
357     if (!xdb->mapped)
358         return RPMRC_FAIL;
359     h2lea(XDB_MAGIC, xdb->mapped + XDB_OFFSET_MAGIC);
360     h2lea(XDB_VERSION, xdb->mapped + XDB_OFFSET_VERSION);
361     h2lea(xdb->generation, xdb->mapped + XDB_OFFSET_GENERATION);
362     h2lea(xdb->slotnpages, xdb->mapped + XDB_OFFSET_SLOTNPAGES);
363     h2lea(xdb->pagesize, xdb->mapped + XDB_OFFSET_PAGESIZE);
364     h2lea(xdb->usergeneration, xdb->mapped + XDB_OFFSET_USERGENERATION);
365     return RPMRC_OK;
366 }
367
368 static void rpmxdbUpdateSlot(rpmxdb xdb, struct xdb_slot *slot)
369 {
370     unsigned char *pp = xdb->mapped + (SLOT_START - 1 + slot->slotno) * SLOT_SIZE;
371     h2lea(SLOT_MAGIC | (slot->subtag << 24), pp);
372     h2lea(slot->blobtag, pp + 4);
373     if (slot->pagecnt || !slot->startpage)
374         h2lea(slot->startpage, pp + 8);
375     else
376         h2lea(1, pp + 8);       /* "empty but used" blobs always start at 1 */
377     h2lea(slot->pagecnt, pp + 12);
378     xdb->generation++;
379     h2lea(xdb->generation, xdb->mapped + XDB_OFFSET_GENERATION);
380 }
381
382 static int rpmxdbWriteEmptyPages(rpmxdb xdb, unsigned int pageno, unsigned int count)
383 {
384     unsigned char *page;
385     if (!count)
386         return RPMRC_OK;
387     page = malloc(xdb->pagesize);
388     if (!page)
389         return RPMRC_FAIL;
390     memset(page, 0, xdb->pagesize);
391     for (; count; count--, pageno++) {
392         if (pwrite(xdb->fd, page, xdb->pagesize, pageno * xdb->pagesize) != xdb->pagesize) {
393             free(page);
394             return RPMRC_FAIL;
395         }
396     }
397     free(page);
398     return RPMRC_OK;
399 }
400
401 static int rpmxdbWriteEmptySlotpage(rpmxdb xdb, int pageno)
402 {
403     unsigned char *page;
404     int i, spp;
405     page = malloc(xdb->pagesize);
406     if (!page)
407         return RPMRC_FAIL;
408     memset(page, 0, xdb->pagesize);
409     spp = xdb->pagesize / SLOT_SIZE;    /* slots per page */
410     for (i = pageno ? 0 : SLOT_START; i < spp; i++)
411         h2le(SLOT_MAGIC, page + i * SLOT_SIZE);
412     if (!pageno) {
413         /* only used when called from InitInternal */
414         if (xdb->mapped) {
415             free(page);
416             return RPMRC_FAIL;
417         }
418         xdb->mapped = page;
419         rpmxdbWriteHeader(xdb);
420         xdb->mapped = 0;
421     }
422     if (pwrite(xdb->fd, page, xdb->pagesize, pageno * xdb->pagesize) != xdb->pagesize) {
423         free(page);
424         return RPMRC_FAIL;
425     }
426     free(page);
427     return RPMRC_OK;
428 }
429
430 static int rpmxdbInitInternal(rpmxdb xdb)
431 {
432     struct stat stb;
433     if (fstat(xdb->fd, &stb)) {
434         return RPMRC_FAIL;
435     }
436     if (stb.st_size == 0) {
437         xdb->slotnpages = 1;
438         xdb->generation++;
439         xdb->pagesize = sysconf(_SC_PAGE_SIZE);
440         if (rpmxdbWriteEmptySlotpage(xdb, 0)) {
441             return RPMRC_FAIL;
442         }
443     }
444     return RPMRC_OK;
445 }
446
447 /* we use the master pdb for locking */
448 static int rpmxdbLockOnly(rpmxdb xdb, int excl)
449 {
450     if (excl && xdb->rdonly)
451         return RPMRC_FAIL;
452     return rpmpkgLock(xdb->pkgdb, excl);
453 }
454
455 /* this is the same as rpmxdbLockReadHeader. It does the
456  * ReadHeader to sync the mappings if xdb moved some blobs.
457  */
458 int rpmxdbLock(rpmxdb xdb, int excl)
459 {
460     if (rpmxdbLockOnly(xdb, excl))
461         return RPMRC_FAIL;
462     if (rpmxdbReadHeader(xdb)) {
463         rpmxdbUnlock(xdb, excl);
464         return RPMRC_FAIL;
465     }
466     return RPMRC_OK;
467 }
468
469 int rpmxdbUnlock(rpmxdb xdb, int excl)
470 {
471     return rpmpkgUnlock(xdb->pkgdb, excl);
472 }
473
474 static int rpmxdbLockReadHeader(rpmxdb xdb, int excl)
475 {
476     if (rpmxdbLockOnly(xdb, excl))
477         return RPMRC_FAIL;
478     if (rpmxdbReadHeader(xdb)) {
479         rpmxdbUnlock(xdb, excl);
480         return RPMRC_FAIL;
481     }
482     return RPMRC_OK;
483 }
484
485 static int rpmxdbInit(rpmxdb xdb)
486 {
487     int rc;
488
489     if (rpmxdbLockOnly(xdb, 1))
490         return RPMRC_FAIL;
491     rc = rpmxdbInitInternal(xdb);
492     rpmxdbUnlock(xdb, 1);
493     return rc;
494 }
495
496 int rpmxdbOpen(rpmxdb *xdbp, rpmpkgdb pkgdb, const char *filename, int flags, int mode)
497 {
498     struct stat stb;
499     rpmxdb xdb;
500
501     *xdbp = 0;
502     xdb = calloc(1, sizeof(*xdb));
503     xdb->pkgdb = pkgdb;
504     xdb->filename = strdup(filename);
505     xdb->systempagesize = sysconf(_SC_PAGE_SIZE);
506     if (!xdb->filename) {
507         free(xdb);
508         return RPMRC_FAIL;
509     }
510     if ((flags & (O_RDONLY|O_RDWR)) == O_RDONLY)
511         xdb->rdonly = 1;
512     if ((xdb->fd = open(filename, flags, mode)) == -1) {
513         free(xdb->filename);
514         free(xdb);
515         return RPMRC_FAIL;
516     }
517     if (flags & O_CREAT) {
518         char *filenameCopy;
519         DIR *pdir;
520
521         if ((filenameCopy = strdup(xdb->filename)) == NULL) {
522             close(xdb->fd);
523             free(xdb->filename);
524             free(xdb);
525             return RPMRC_FAIL;
526         }
527
528         if ((pdir = opendir(dirname(filenameCopy))) == NULL) {
529             free(filenameCopy);
530             close(xdb->fd);
531             free(xdb->filename);
532             free(xdb);
533             return RPMRC_FAIL;
534         }
535
536         if (fsync(dirfd(pdir)) == -1) {
537             closedir(pdir);
538             free(filenameCopy);
539             close(xdb->fd);
540             free(xdb->filename);
541             free(xdb);
542             return RPMRC_FAIL;
543         }
544         closedir(pdir);
545         free(filenameCopy);
546     }
547     if (fstat(xdb->fd, &stb)) {
548         close(xdb->fd);
549         free(xdb->filename);
550         free(xdb);
551         return RPMRC_FAIL;
552     }
553     if (stb.st_size == 0) {
554         if (rpmxdbInit(xdb)) {
555             close(xdb->fd);
556             free(xdb->filename);
557             free(xdb);
558             return RPMRC_FAIL;
559         }
560     }
561     xdb->flags = flags;
562     xdb->mode = mode;
563     xdb->dofsync = 1;
564     *xdbp = xdb;
565     return RPMRC_OK;
566 }
567
568 void rpmxdbClose(rpmxdb xdb)
569 {
570     struct xdb_slot *slot;
571     int i;
572
573     for (i = 1, slot = xdb->slots + 1; i < xdb->nslots; i++, slot++) {
574         if (slot->mapped) {
575             unmapslot(xdb, slot);
576             slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
577         }
578     }
579     if (xdb->slots)
580         free(xdb->slots);
581     if (xdb->fd >= 0)
582         close(xdb->fd);
583     if (xdb->filename)
584         free(xdb->filename);
585     free(xdb);
586 }
587
588 /* moves the blob to a given new location (possibly resizeing) */
589 static int moveblobto(rpmxdb xdb, struct xdb_slot *oldslot, struct xdb_slot *afterslot, unsigned int newpagecnt)
590 {
591     struct xdb_slot *nextslot;
592     unsigned int newstartpage, oldpagecnt;
593     unsigned int tocopy;
594     int didmap;
595
596     newstartpage = afterslot->startpage + afterslot->pagecnt;
597     nextslot = xdb->slots + afterslot->next;
598
599     /* make sure there's enough room */
600     if (newpagecnt > nextslot->startpage - newstartpage)
601         return RPMRC_FAIL;
602
603 #if 0
604     printf("moveblobto %d %d %d %d, afterslot %d\n", oldslot->startpage, oldslot->pagecnt, newstartpage, newpagecnt, afterslot->slotno);
605 #endif
606     /* map old content */
607     didmap = 0;
608     oldpagecnt = oldslot->pagecnt;
609     if (!oldslot->mapped && oldpagecnt) {
610         if (mapslot(xdb, oldslot))
611             return RPMRC_FAIL;
612         didmap = 1;
613     }
614
615     /* copy content */
616     tocopy = newpagecnt > oldpagecnt ? oldpagecnt : newpagecnt;
617     if (tocopy && pwrite(xdb->fd, oldslot->mapped, tocopy * xdb->pagesize, newstartpage * xdb->pagesize) != tocopy * xdb->pagesize) {
618         if (didmap)
619             unmapslot(xdb, oldslot);
620         return RPMRC_FAIL;
621     }
622     /* zero out new pages */
623     if (newpagecnt > oldpagecnt) {
624         if (rpmxdbWriteEmptyPages(xdb, newstartpage + oldpagecnt, newpagecnt - oldpagecnt)) {
625             if (didmap)
626                 unmapslot(xdb, oldslot);
627             return RPMRC_FAIL;
628         }
629     }
630
631     if (oldslot->mapped)
632         unmapslot(xdb, oldslot);
633
634     /* set new offset and position */
635     oldslot->startpage = newstartpage;
636     oldslot->pagecnt = newpagecnt;
637     rpmxdbUpdateSlot(xdb, oldslot);
638     xdb->usedblobpages -= oldpagecnt;
639     xdb->usedblobpages += newpagecnt;
640
641     if (afterslot != oldslot && nextslot != oldslot) {
642         /* remove from old chain */
643         xdb->slots[oldslot->prev].next = oldslot->next;
644         xdb->slots[oldslot->next].prev = oldslot->prev;
645
646         /* chain into new position, between lastslot and nextslot */
647         oldslot->prev = afterslot->slotno;
648         afterslot->next = oldslot->slotno;
649
650         oldslot->next = nextslot->slotno;
651         nextslot->prev = oldslot->slotno;
652     }
653
654     /* map again (if needed) */
655     if (oldslot->mapcallback) {
656         if (newpagecnt) {
657             if (mapslot(xdb, oldslot))
658                 oldslot->mapped = 0;    /* XXX: HELP, what can we do here? */
659         }
660         oldslot->mapcallback(xdb, oldslot->mapcallbackdata, oldslot->mapped, oldslot->mapped ? oldslot->pagecnt * xdb->pagesize : 0);
661     }
662     return RPMRC_OK;
663 }
664
665 /* moves the blob to a new location (possibly resizeing) */
666 static int moveblob(rpmxdb xdb, struct xdb_slot *oldslot, unsigned int newpagecnt)
667 {
668     struct xdb_slot *slot, *lastslot;
669     unsigned int nslots;
670     unsigned int freecnt;
671     int i;
672
673     nslots = xdb->nslots;
674     freecnt = 0;
675     lastslot = xdb->slots;
676     for (i = xdb->slots[0].next; ; lastslot = slot, i = slot->next) {
677         slot = xdb->slots + i;
678         freecnt = slot->startpage - (lastslot->startpage + lastslot->pagecnt);
679         if (freecnt >= newpagecnt)
680             break;
681         if (i == nslots)
682             break;
683     }
684     if (i == nslots && newpagecnt > freecnt) {
685         /* need to grow the file */
686         if (rpmxdbWriteEmptyPages(xdb, slot->startpage, newpagecnt - freecnt)) {
687             return RPMRC_FAIL;
688         }
689         slot->startpage += newpagecnt - freecnt;
690     }
691     return moveblobto(xdb, oldslot, lastslot, newpagecnt);
692 }
693
694 /* move the two blobs at the end of our file to the free area after the provided slot */
695 static int moveblobstofront(rpmxdb xdb, struct xdb_slot *afterslot)
696 {
697     struct xdb_slot *slot1, *slot2;
698     unsigned int freestart = afterslot->startpage + afterslot->pagecnt;
699     unsigned int freecount = xdb->slots[afterslot->next].startpage - freestart;
700
701     slot1 = xdb->slots + xdb->slots[xdb->nslots].prev;
702     if (slot1 == xdb->slots)
703         slot1 = slot2 = 0;
704     else {
705         slot2 = xdb->slots + slot1->prev;
706         if (slot2 == xdb->slots)
707             slot2 = 0;
708     }
709     if (slot1->pagecnt < slot2->pagecnt) {
710         struct xdb_slot *tmp = slot1;
711         slot1 = slot2;
712         slot2 = tmp;
713     }
714     if (slot1 && slot1->pagecnt && slot1->pagecnt <= freecount && slot1->startpage > freestart) {
715         if (moveblobto(xdb, slot1, afterslot, slot1->pagecnt))
716             return RPMRC_FAIL;
717         freestart += slot1->pagecnt;
718         freecount -= slot1->pagecnt;
719         afterslot = slot1;
720     }
721     if (slot2 && slot2->pagecnt && slot2->pagecnt <= freecount && slot2->startpage > freestart) {
722         if (moveblobto(xdb, slot2, afterslot, slot2->pagecnt))
723             return RPMRC_FAIL;
724     }
725     return RPMRC_OK;
726 }
727
728 /* add a single page containing empty slots */
729 static int addslotpage(rpmxdb xdb)
730 {
731     unsigned char *newaddr;
732     struct xdb_slot *slot;
733     int i, spp, nslots;
734     size_t newmappedlen;
735
736     if (xdb->firstfree)
737         return RPMRC_FAIL;
738
739     /* move first blob if needed */
740     nslots = xdb->nslots;
741     for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
742         slot = xdb->slots + i;
743         if (slot->pagecnt)
744             break;
745     }
746     if (i != nslots && slot->pagecnt && slot->startpage == xdb->slotnpages) {
747         /* the blob at this slot is in the way. move it. */
748         if (moveblob(xdb, slot, slot->pagecnt))
749             return RPMRC_FAIL;
750     }
751
752     spp = xdb->pagesize / SLOT_SIZE;    /* slots per page */
753     slot = realloc(xdb->slots, (nslots + 1 + spp) * sizeof(*slot));
754     if (!slot)
755         return RPMRC_FAIL;
756     xdb->slots = slot;
757
758     if (rpmxdbWriteEmptySlotpage(xdb, xdb->slotnpages)) {
759         return RPMRC_FAIL;
760     }
761     /* remap slots */
762     newmappedlen = xdb->slotnpages * xdb->pagesize + xdb->pagesize;
763     newmappedlen = (newmappedlen + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
764     newaddr = mremap(xdb->mapped, xdb->mappedlen, newmappedlen, MREMAP_MAYMOVE);
765     if (newaddr == MAP_FAILED)
766         return RPMRC_FAIL;
767     xdb->mapped = newaddr;
768     xdb->mappedlen = newmappedlen;
769
770     /* update the header */
771     xdb->slotnpages++;
772     xdb->generation++;
773     rpmxdbWriteHeader(xdb);
774
775     /* fixup empty but used slots */
776     for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
777         slot = xdb->slots + i;
778         if (slot->startpage >= xdb->slotnpages)
779             break;
780         slot->startpage = xdb->slotnpages;
781         if (slot->pagecnt)
782             abort();
783     }
784
785     /* move tail element to the new end */
786     slot = xdb->slots + nslots + spp;
787     *slot = xdb->slots[nslots];
788     slot->slotno = nslots + spp;
789     xdb->slots[slot->prev].next = slot->slotno;
790     xdb->nslots += spp;
791
792     /* add new free slots to the firstfree chain */
793     memset(xdb->slots + nslots, 0, sizeof(*slot) * spp);
794     for (i = 0; i < spp - 1; i++) {
795         xdb->slots[nslots + i].slotno = nslots + i;
796         xdb->slots[nslots + i].next = i + 1;
797     }
798     xdb->slots[nslots + i].slotno = nslots + i;
799     xdb->firstfree = nslots;
800     return RPMRC_OK;
801 }
802
803 static int createblob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag)
804 {
805     struct xdb_slot *slot;
806     unsigned int id;
807
808     if (subtag > 255)
809         return RPMRC_FAIL;
810     if (!xdb->firstfree) {
811         if (addslotpage(xdb))
812             return RPMRC_FAIL;
813     }
814     id = xdb->firstfree;
815     slot = xdb->slots + xdb->firstfree;
816     xdb->firstfree = slot->next;
817
818     slot->mapped = 0;
819     slot->blobtag = blobtag;
820     slot->subtag = subtag;
821     slot->startpage = xdb->slotnpages;
822     slot->pagecnt = 0;
823     rpmxdbUpdateSlot(xdb, slot);
824     /* enqueue */
825     slot->prev = 0;
826     slot->next = xdb->slots[0].next;
827     xdb->slots[slot->next].prev = id;
828     xdb->slots[0].next = id;
829 #if 0
830     printf("createblob #%d %d/%d\n", id, blobtag, subtag);
831 #endif
832     if (slot->slotno != id)
833         abort();
834     if (slot->mapped)
835         abort();
836     *idp = id;
837     return RPMRC_OK;
838 }
839
840 int rpmxdbLookupBlob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag, int flags)
841 {
842     struct xdb_slot *slot;
843     unsigned int i, nslots;
844     if (rpmxdbLockReadHeader(xdb, flags ? 1 : 0))
845         return RPMRC_FAIL;
846     nslots = xdb->nslots;
847     slot = 0;
848     for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
849         slot = xdb->slots + i;
850         if (slot->blobtag == blobtag && slot->subtag == subtag)
851             break;
852     }
853     if (i == nslots)
854         i = 0;
855     if (i && (flags & O_TRUNC) != 0) {
856         if (rpmxdbResizeBlob(xdb, i, 0)) {
857             rpmxdbUnlock(xdb, flags ? 1 : 0);
858             return RPMRC_FAIL;
859         }
860     }
861     if (!i && (flags & O_CREAT) != 0) {
862         if (createblob(xdb, &i, blobtag, subtag)) {
863             rpmxdbUnlock(xdb, flags ? 1 : 0);
864             return RPMRC_FAIL;
865         }
866     }
867     *idp = i;
868     rpmxdbUnlock(xdb, flags ? 1 : 0);
869     return i ? RPMRC_OK : RPMRC_NOTFOUND;
870 }
871
872 int rpmxdbDelBlob(rpmxdb xdb, unsigned int id)
873 {
874     struct xdb_slot *slot;
875     if (!id)
876         return RPMRC_FAIL;
877     if (rpmxdbLockReadHeader(xdb, 1))
878         return RPMRC_FAIL;
879     if (id >= xdb->nslots) {
880         rpmxdbUnlock(xdb, 1);
881         return RPMRC_FAIL;
882     }
883     slot = xdb->slots + id;
884     if (!slot->startpage) {
885         rpmxdbUnlock(xdb, 1);
886         return RPMRC_OK;
887     }
888     if (slot->mapped) {
889         unmapslot(xdb, slot);
890         slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
891     }
892     /* remove from old chain */
893     xdb->slots[slot->prev].next = slot->next;
894     xdb->slots[slot->next].prev = slot->prev;
895     xdb->usedblobpages -= slot->pagecnt;
896
897     if (xdb->usedblobpages * 2 < xdb->slots[xdb->nslots].startpage && (slot->startpage + slot->pagecnt) * 2 < xdb->slots[xdb->nslots].startpage) {
898         /* freed in first half of pages, move last two blobs if we can */
899         moveblobstofront(xdb, xdb->slots + slot->prev);
900     }
901
902     /* zero slot */
903     memset(slot, 0, sizeof(*slot));
904     slot->slotno = id;
905     rpmxdbUpdateSlot(xdb, slot);
906
907     /* enqueue into free chain */
908     slot->next = xdb->firstfree;
909     xdb->firstfree = slot->slotno;
910
911     /* check if we should truncate the file */
912     slot = xdb->slots + xdb->slots[xdb->nslots].prev;
913     if (slot->startpage + slot->pagecnt < xdb->slots[xdb->nslots].startpage / 4 * 3) {
914         unsigned int newend = slot->startpage + slot->pagecnt;
915         if (!ftruncate(xdb->fd, newend * xdb->pagesize))
916             xdb->slots[xdb->nslots].startpage = newend;
917     }
918
919     rpmxdbUnlock(xdb, 1);
920     return RPMRC_OK;
921 }
922
923 int rpmxdbResizeBlob(rpmxdb xdb, unsigned int id, size_t newsize)
924 {
925     struct xdb_slot *slot;
926     unsigned int oldpagecnt, newpagecnt;
927     if (!id)
928         return RPMRC_FAIL;
929     if (rpmxdbLockReadHeader(xdb, 1))
930         return RPMRC_FAIL;
931     if (id >= xdb->nslots) {
932         rpmxdbUnlock(xdb, 1);
933         return RPMRC_FAIL;
934     }
935     slot = xdb->slots + id;
936     if (!slot->startpage) {
937         rpmxdbUnlock(xdb, 1);
938         return RPMRC_FAIL;
939     }
940     oldpagecnt = slot->pagecnt;
941     newpagecnt = (newsize + xdb->pagesize - 1) / xdb->pagesize;
942     if (oldpagecnt && newpagecnt && newpagecnt <= oldpagecnt) {
943         /* reducing size. zero to end of page */
944         unsigned int pg = newsize & (xdb->pagesize - 1);
945         if (pg) {
946             if (slot->mapped) {
947                 memset(slot->mapped + pg, 0, xdb->pagesize - pg);
948             } else {
949                 char *empty = calloc(1, xdb->pagesize - pg);
950                 if (!empty) {
951                     rpmxdbUnlock(xdb, 1);
952                     return RPMRC_FAIL;
953                 }
954                 if (pwrite(xdb->fd, empty, xdb->pagesize - pg, (slot->startpage + newpagecnt - 1) * xdb->pagesize + pg ) != xdb->pagesize - pg) {
955                     free(empty);
956                     rpmxdbUnlock(xdb, 1);
957                     return RPMRC_FAIL;
958                 }
959                 free(empty);
960             }
961         }
962     }
963     if (newpagecnt == oldpagecnt) {
964         /* no size change */
965         rpmxdbUnlock(xdb, 1);
966         return RPMRC_OK;
967     }
968     if (!newpagecnt) {
969         /* special case: zero size blob, no longer mapped */
970         if (slot->mapped)
971             unmapslot(xdb, slot);
972         slot->pagecnt = 0;
973         slot->startpage = xdb->slotnpages;
974         /* remove from old chain */
975         xdb->slots[slot->prev].next = slot->next;
976         xdb->slots[slot->next].prev = slot->prev;
977         /* enqueue into head */
978         slot->prev = 0;
979         slot->next = xdb->slots[0].next;
980         xdb->slots[slot->next].prev = slot->slotno;
981         xdb->slots[0].next = slot->slotno;
982         rpmxdbUpdateSlot(xdb, slot);
983         xdb->usedblobpages -= oldpagecnt;
984         if (slot->mapcallback)
985             slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
986     } else if (newpagecnt <= xdb->slots[slot->next].startpage - slot->startpage) {
987         /* can do it inplace */
988         if (newpagecnt > oldpagecnt) {
989             /* zero new pages */
990             if (rpmxdbWriteEmptyPages(xdb, slot->startpage + oldpagecnt, newpagecnt - oldpagecnt)) {
991                 rpmxdbUnlock(xdb, 1);
992                 return RPMRC_FAIL;
993             }
994         }
995         if (slot->mapcallback) {
996             if (remapslot(xdb, slot, newpagecnt)) {
997                 rpmxdbUnlock(xdb, 1);
998                 return RPMRC_FAIL;
999             }
1000         } else {
1001             if (slot->mapped)
1002                 unmapslot(xdb, slot);
1003             slot->pagecnt = newpagecnt;
1004         }
1005         rpmxdbUpdateSlot(xdb, slot);
1006         xdb->usedblobpages -= oldpagecnt;
1007         xdb->usedblobpages += newpagecnt;
1008         if (slot->mapcallback)
1009             slot->mapcallback(xdb, slot->mapcallbackdata, slot->mapped, slot->pagecnt * xdb->pagesize);
1010     } else {
1011         /* need to relocate to a new page area */
1012         if (moveblob(xdb, slot, newpagecnt)) {
1013             rpmxdbUnlock(xdb, 1);
1014             return RPMRC_FAIL;
1015         }
1016     }
1017     rpmxdbUnlock(xdb, 1);
1018     return RPMRC_OK;
1019 }
1020
1021 int rpmxdbMapBlob(rpmxdb xdb, unsigned int id, int flags, void (*mapcallback)(rpmxdb xdb, void *data, void *newaddr, size_t newsize), void *mapcallbackdata)
1022 {
1023     struct xdb_slot *slot;
1024     if (!id || !mapcallback)
1025         return RPMRC_FAIL;
1026     if ((flags & (O_RDONLY|O_RDWR)) == O_RDWR && xdb->rdonly)
1027         return RPMRC_FAIL;
1028     if (rpmxdbLockReadHeader(xdb, 0))
1029         return RPMRC_FAIL;
1030     if (id >= xdb->nslots) {
1031         rpmxdbUnlock(xdb, 0);
1032         return RPMRC_FAIL;
1033     }
1034     slot = xdb->slots + id;
1035     if (!slot->startpage || slot->mapped) {
1036         rpmxdbUnlock(xdb, 0);
1037         return RPMRC_FAIL;
1038     }
1039     slot->mapflags = (flags & (O_RDONLY|O_RDWR)) == O_RDWR ? PROT_READ | PROT_WRITE : PROT_READ;
1040     if (slot->pagecnt) {
1041         if (mapslot(xdb, slot)) {
1042             slot->mapflags = 0;
1043             rpmxdbUnlock(xdb, 0);
1044             return RPMRC_FAIL;
1045         }
1046     }
1047     slot->mapcallback = mapcallback;
1048     slot->mapcallbackdata = mapcallbackdata;
1049     mapcallback(xdb, mapcallbackdata, slot->mapped, slot->mapped ? slot->pagecnt * xdb->pagesize : 0);
1050     rpmxdbUnlock(xdb, 0);
1051     return RPMRC_OK;
1052 }
1053
1054 int rpmxdbUnmapBlob(rpmxdb xdb, unsigned int id)
1055 {
1056     struct xdb_slot *slot;
1057     if (!id)
1058         return RPMRC_OK;
1059     if (rpmxdbLockReadHeader(xdb, 0))
1060         return RPMRC_FAIL;
1061     if (id >= xdb->nslots) {
1062         rpmxdbUnlock(xdb, 0);
1063         return RPMRC_FAIL;
1064     }
1065     slot = xdb->slots + id;
1066     if (slot->mapped) {
1067         unmapslot(xdb, slot);
1068         slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
1069     }
1070     slot->mapcallback = 0;
1071     slot->mapcallbackdata = 0;
1072     slot->mapflags = 0;
1073     rpmxdbUnlock(xdb, 0);
1074     return RPMRC_OK;
1075 }
1076
1077 int rpmxdbRenameBlob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag)
1078 {
1079     struct xdb_slot *slot;
1080     unsigned int otherid;
1081     unsigned int id = *idp;
1082     int rc;
1083
1084     if (!id || subtag > 255)
1085         return RPMRC_FAIL;
1086     if (rpmxdbLockReadHeader(xdb, 1))
1087         return RPMRC_FAIL;
1088     if (id >= xdb->nslots) {
1089         rpmxdbUnlock(xdb, 1);
1090         return RPMRC_FAIL;
1091     }
1092     slot = xdb->slots + id;
1093 #if 0
1094     printf("rpmxdbRenameBlob #%d %d/%d -> %d/%d\n", id, slot->blobtag, slot->subtag, blobtag, subtag);
1095 #endif
1096     if (!slot->startpage) {
1097         rpmxdbUnlock(xdb, 1);
1098         return RPMRC_FAIL;
1099     }
1100     if (slot->blobtag == blobtag && slot->subtag == subtag) {
1101         rpmxdbUnlock(xdb, 1);
1102         return RPMRC_OK;
1103     }
1104     rc = rpmxdbLookupBlob(xdb, &otherid, blobtag, subtag, 0);
1105     if (rc == RPMRC_NOTFOUND)
1106         otherid = 0;
1107     else if (rc) {
1108         rpmxdbUnlock(xdb, 1);
1109         return RPMRC_FAIL;
1110     }
1111     if (otherid) {
1112 #if 0
1113         printf("(replacing #%d)\n", otherid);
1114 #endif
1115         if (rpmxdbDelBlob(xdb, otherid)) {
1116             rpmxdbUnlock(xdb, 1);
1117             return RPMRC_FAIL;
1118         }
1119         /* get otherid back from free chain */
1120         if (xdb->firstfree != otherid)
1121             return RPMRC_FAIL;
1122         xdb->firstfree = xdb->slots[otherid].next;
1123
1124         slot->blobtag = blobtag;
1125         slot->subtag = subtag;
1126         xdb->slots[otherid] = *slot;
1127         /* fixup ids */
1128         xdb->slots[otherid].slotno = otherid;
1129         xdb->slots[slot->prev].next = otherid;
1130         xdb->slots[slot->next].prev = otherid;
1131         /* write */
1132         rpmxdbUpdateSlot(xdb, xdb->slots + otherid);
1133         memset(slot, 0, sizeof(*slot));
1134         slot->slotno = id;
1135         rpmxdbUpdateSlot(xdb, slot);
1136         slot->next = xdb->firstfree;
1137         xdb->firstfree = slot->slotno;
1138         *idp = otherid;
1139     } else {
1140         slot = xdb->slots + id;
1141         slot->blobtag = blobtag;
1142         slot->subtag = subtag;
1143         rpmxdbUpdateSlot(xdb, slot);
1144     }
1145     rpmxdbUnlock(xdb, 1);
1146     return RPMRC_OK;
1147 }
1148
1149 void rpmxdbSetFsync(rpmxdb xdb, int dofsync)
1150 {
1151     xdb->dofsync = dofsync;
1152 }
1153
1154 int rpmxdbIsRdonly(rpmxdb xdb)
1155 {
1156     return xdb->rdonly;
1157 }
1158
1159 int rpmxdbSetUserGeneration(rpmxdb xdb, unsigned int usergeneration)
1160 {
1161     if (rpmxdbLockReadHeader(xdb, 1))
1162         return RPMRC_FAIL;
1163     /* sync before the update */
1164     if (xdb->dofsync && fsync(xdb->fd)) {
1165         rpmxdbUnlock(xdb, 1);
1166         return RPMRC_FAIL;
1167     }
1168     xdb->usergeneration = usergeneration;
1169     xdb->generation++;
1170     rpmxdbWriteHeader(xdb);
1171     rpmxdbUnlock(xdb, 1);
1172     return RPMRC_OK;
1173 }
1174
1175 int rpmxdbGetUserGeneration(rpmxdb xdb, unsigned int *usergenerationp)
1176 {
1177     if (rpmxdbLockReadHeader(xdb, 0))
1178         return RPMRC_FAIL;
1179     *usergenerationp = xdb->usergeneration;
1180     rpmxdbUnlock(xdb, 0);
1181     return RPMRC_OK;
1182 }
1183
1184 int rpmxdbStats(rpmxdb xdb)
1185 {
1186     struct xdb_slot *slot;
1187     unsigned int i, nslots;
1188
1189     if (rpmxdbLockReadHeader(xdb, 0))
1190         return RPMRC_FAIL;
1191     nslots = xdb->nslots;
1192     printf("--- XDB Stats\n");
1193     printf("Filename: %s\n", xdb->filename);
1194     printf("Generation: %d\n", xdb->generation);
1195     printf("Slot pages: %d\n", xdb->slotnpages);
1196     printf("Blob pages: %d\n", xdb->usedblobpages);
1197     printf("Free pages: %d\n", xdb->slots[nslots].startpage - xdb->usedblobpages - xdb->slotnpages);
1198     printf("Pagesize: %d / %d\n", xdb->pagesize, xdb->systempagesize);
1199     for (i = 1, slot = xdb->slots + i; i < nslots; i++, slot++) {
1200         if (!slot->startpage)
1201             continue;
1202         printf("%2d: tag %d/%d, startpage %d, pagecnt %d%s\n", i, slot->blobtag, slot->subtag, slot->startpage, slot->pagecnt, slot->mapcallbackdata ? ", mapped" : "");
1203     }
1204 #if 0
1205     printf("Again in offset order:\n");
1206     for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
1207         slot = xdb->slots + i;
1208         printf("%2d: tag %d/%d, startpage %d, pagecnt %d%s\n", i, slot->blobtag, slot->subtag, slot->startpage, slot->pagecnt, slot->mapcallbackdata ? ", mapped" : "");
1209     }
1210 #endif
1211 #if 0
1212     printf("Free chain:\n");
1213     for (i = xdb->firstfree; i; i = slot->next) {
1214         slot = xdb->slots + i;
1215         printf("%2d [%2d]: tag %d/%d, startpage %d, pagecnt %d%s\n", i, slot->slotno, slot->blobtag, slot->subtag, slot->startpage, slot->pagecnt, slot->mapcallbackdata ? ", mapped" : "");
1216     }
1217 #endif
1218     rpmxdbUnlock(xdb, 0);
1219     return RPMRC_OK;
1220 }
1221