ccb4f17771a7f1715132e617b76750411d9ca06f
[platform/upstream/rpm.git] / lib / rpmdb.c
1 #include "system.h"
2
3 #include <sys/file.h>
4 #include <signal.h>
5 #include <sys/signal.h>
6
7 #include <rpmlib.h>
8 #include <rpmurl.h>
9 #include <rpmmacro.h>   /* XXX for rpmGetPath */
10
11 #include "dbindex.h"
12 /*@access dbiIndexSet@*/
13 /*@access dbiIndexRecord@*/
14
15 #include "falloc.h"
16 #include "fprint.h"
17 #include "misc.h"
18 #include "rpmdb.h"
19
20 extern int _noDirTokens;
21
22 const char *rpmdb_filenames[] = {
23     "packages.rpm",
24     "nameindex.rpm",
25     "fileindex.rpm",
26     "groupindex.rpm",
27     "requiredby.rpm",
28     "providesindex.rpm",
29     "conflictsindex.rpm",
30     "triggerindex.rpm",
31     NULL
32 };
33
34 /* XXX the signal handling in here is not thread safe */
35
36 /* the requiredbyIndex isn't stricly necessary. In a perfect world, we could
37    have each header keep a list of packages that need it. However, we
38    can't reserve space in the header for extra information so all of the
39    required packages would move in the database every time a package was
40    added or removed. Instead, each package (or virtual package) name
41    keeps a list of package offsets of packages that might depend on this
42    one. Version numbers still need verification, but it gets us in the
43    right area w/o a linear search through the database. */
44
45 struct rpmdb_s {
46     FD_t pkgs;
47     dbiIndex nameIndex, fileIndex, groupIndex, providesIndex;
48     dbiIndex requiredbyIndex, conflictsIndex, triggerIndex;
49 };
50
51 static sigset_t signalMask;
52
53 static void blockSignals(void)
54 {
55     sigset_t newMask;
56
57     sigfillset(&newMask);               /* block all signals */
58     sigprocmask(SIG_BLOCK, &newMask, &signalMask);
59 }
60
61 static void unblockSignals(void)
62 {
63     sigprocmask(SIG_SETMASK, &signalMask, NULL);
64 }
65
66 static int openDbFile(const char * prefix, const char * dbpath, const char * shortName, 
67          int justCheck, int mode, int perms, dbiIndex * db, DBI_TYPE type)
68 {
69     int len = (prefix ? strlen(prefix) : 0) + strlen(dbpath) + strlen(shortName) + 1;
70     char * filename = alloca(len);
71
72     *filename = '\0';
73     switch (urlIsURL(dbpath)) {
74     case URL_IS_UNKNOWN:
75         if (prefix && *prefix) strcat(filename, prefix); 
76         break;
77     default:
78         break;
79     }
80     strcat(filename, dbpath);
81     strcat(filename, shortName);
82
83     if (!justCheck || !rpmfileexists(filename)) {
84         if ((*db = dbiOpenIndex(filename, mode, perms, type)) == NULL)
85             return 1;
86     }
87
88     return 0;
89 }
90
91 static /*@only@*/ rpmdb newRpmdb(void)
92 {
93     rpmdb db = xmalloc(sizeof(*db));
94     db->pkgs = NULL;
95     db->nameIndex = NULL;
96     db->fileIndex = NULL;
97     db->groupIndex = NULL;
98     db->providesIndex = NULL;
99     db->requiredbyIndex = NULL;
100     db->conflictsIndex = NULL;
101     db->triggerIndex = NULL;
102     return db;
103 }
104
105 int openDatabase(const char * prefix, const char * dbpath, rpmdb *rpmdbp, int mode, 
106                  int perms, int flags)
107 {
108     char * filename;
109     rpmdb db;
110     int i, rc;
111     struct flock lockinfo;
112     int justcheck = flags & RPMDB_FLAG_JUSTCHECK;
113     int minimal = flags & RPMDB_FLAG_MINIMAL;
114     const char * akey;
115
116     if (mode & O_WRONLY) 
117         return 1;
118
119     if (!(perms & 0600))        /* XXX sanity */
120         perms = 0644;
121
122     /* we should accept NULL as a valid prefix */
123     if (!prefix) prefix="";
124
125     i = strlen(dbpath);
126     if (dbpath[i - 1] != '/') {
127         filename = alloca(i + 2);
128         strcpy(filename, dbpath);
129         filename[i] = '/';
130         filename[i + 1] = '\0';
131         dbpath = filename;
132     }
133     
134     filename = alloca(strlen(prefix) + strlen(dbpath) + 40);
135     *filename = '\0';
136
137     switch (urlIsURL(dbpath)) {
138     case URL_IS_UNKNOWN:
139         strcat(filename, prefix); 
140         break;
141     default:
142         break;
143     }
144     strcat(filename, dbpath);
145     (void)rpmCleanPath(filename);
146
147     rpmMessage(RPMMESS_DEBUG, _("opening database mode 0x%x in %s\n"),
148         mode, filename);
149
150     if (filename[strlen(filename)-1] != '/')
151         strcat(filename, "/");
152     strcat(filename, "packages.rpm");
153
154     db = newRpmdb();
155
156     if (!justcheck || !rpmfileexists(filename)) {
157         db->pkgs = fadOpen(filename, mode, perms);
158         if (Ferror(db->pkgs)) {
159             rpmError(RPMERR_DBOPEN, _("failed to open %s: %s\n"), filename,
160                 Fstrerror(db->pkgs));
161             return 1;
162         }
163
164         /* try and get a lock - this is released by the kernel when we close
165            the file */
166         lockinfo.l_whence = 0;
167         lockinfo.l_start = 0;
168         lockinfo.l_len = 0;
169         
170         if (mode & O_RDWR) {
171             lockinfo.l_type = F_WRLCK;
172             if (Fcntl(db->pkgs, F_SETLK, (void *) &lockinfo)) {
173                 rpmError(RPMERR_FLOCK, _("cannot get %s lock on database"), 
174                          _("exclusive"));
175                 rpmdbClose(db);
176                 return 1;
177             } 
178         } else {
179             lockinfo.l_type = F_RDLCK;
180             if (Fcntl(db->pkgs, F_SETLK, (void *) &lockinfo)) {
181                 rpmError(RPMERR_FLOCK, _("cannot get %s lock on database"), 
182                          _("shared"));
183                 rpmdbClose(db);
184                 return 1;
185             } 
186         }
187     }
188
189     rc = openDbFile(prefix, dbpath, "nameindex.rpm", justcheck, mode, perms,
190                     &db->nameIndex, DBI_HASH);
191
192     if (minimal) {
193         *rpmdbp = xmalloc(sizeof(struct rpmdb_s));
194         if (rpmdbp)
195             *rpmdbp = db;       /* structure assignment */
196         else
197             rpmdbClose(db);
198         return 0;
199     }
200
201     if (!rc)
202         rc = openDbFile(prefix, dbpath, "fileindex.rpm", justcheck, mode, perms,
203                         &db->fileIndex, DBI_HASH);
204
205     /* We used to store the fileindexes as complete paths, rather then
206        plain basenames. Let's see which version we are... */
207     /*
208      * XXX FIXME: db->fileindex can be NULL under pathological (e.g. mixed
209      * XXX db1/db2 linkage) conditions.
210      */
211     if (!justcheck && !dbiGetFirstKey(db->fileIndex, &akey)) {
212         if (strchr(akey, '/')) {
213             rpmError(RPMERR_OLDDB, _("old format database is present; "
214                         "use --rebuilddb to generate a new format database"));
215             rc |= 1;
216         }
217         xfree(akey);
218     }
219
220     if (!rc)
221         rc = openDbFile(prefix, dbpath, "providesindex.rpm", justcheck, mode, perms,
222                         &db->providesIndex, DBI_HASH);
223     if (!rc)
224         rc = openDbFile(prefix, dbpath, "requiredby.rpm", justcheck, mode, perms,
225                         &db->requiredbyIndex, DBI_HASH);
226     if (!rc)
227         rc = openDbFile(prefix, dbpath, "conflictsindex.rpm", justcheck, mode, perms,
228                         &db->conflictsIndex, DBI_HASH);
229     if (!rc)
230         rc = openDbFile(prefix, dbpath, "groupindex.rpm", justcheck, mode, perms,
231                         &db->groupIndex, DBI_HASH);
232     if (!rc)
233         rc = openDbFile(prefix, dbpath, "triggerindex.rpm", justcheck, mode, perms,
234                         &db->triggerIndex, DBI_HASH);
235
236     if (rc || justcheck || rpmdbp == NULL)
237         rpmdbClose(db);
238      else
239         *rpmdbp = db;
240
241      return rc;
242 }
243
244 static int doRpmdbOpen (const char * prefix, /*@out@*/ rpmdb * rpmdbp,
245                         int mode, int perms, int flags)
246 {
247     const char * dbpath = rpmGetPath("%{_dbpath}", NULL);
248     int rc;
249
250     if (!(dbpath && dbpath[0] != '%')) {
251         rpmMessage(RPMMESS_DEBUG, _("no dbpath has been set"));
252         rc = 1;
253     } else
254         rc = openDatabase(prefix, dbpath, rpmdbp, mode, perms, flags);
255     xfree(dbpath);
256     return rc;
257 }
258
259 int rpmdbOpenForTraversal(const char * prefix, rpmdb * rpmdbp)
260 {
261     return doRpmdbOpen(prefix, rpmdbp, O_RDONLY, 0644, RPMDB_FLAG_MINIMAL);
262 }
263
264 int rpmdbOpen (const char * prefix, rpmdb *rpmdbp, int mode, int perms)
265 {
266     return doRpmdbOpen(prefix, rpmdbp, mode, perms, 0);
267 }
268
269 int rpmdbInit (const char * prefix, int perms)
270 {
271     rpmdb db;
272     return doRpmdbOpen(prefix, &db, (O_CREAT | O_RDWR), perms, RPMDB_FLAG_JUSTCHECK);
273 }
274
275 void rpmdbClose (rpmdb db)
276 {
277     if (db->pkgs != NULL) Fclose(db->pkgs);
278     if (db->fileIndex) dbiCloseIndex(db->fileIndex);
279     if (db->groupIndex) dbiCloseIndex(db->groupIndex);
280     if (db->nameIndex) dbiCloseIndex(db->nameIndex);
281     if (db->providesIndex) dbiCloseIndex(db->providesIndex);
282     if (db->requiredbyIndex) dbiCloseIndex(db->requiredbyIndex);
283     if (db->conflictsIndex) dbiCloseIndex(db->conflictsIndex);
284     if (db->triggerIndex) dbiCloseIndex(db->triggerIndex);
285     free(db);
286 }
287
288 int rpmdbFirstRecNum(rpmdb db) {
289     return fadFirstOffset(db->pkgs);
290 }
291
292 int rpmdbNextRecNum(rpmdb db, unsigned int lastOffset) {
293     /* 0 at end */
294     return fadNextOffset(db->pkgs, lastOffset);
295 }
296
297 static Header doGetRecord(rpmdb db, unsigned int offset, int pristine)
298 {
299     Header h;
300     const char ** fileNames;
301     int fileCount = 0;
302     int i;
303
304     (void)Fseek(db->pkgs, offset, SEEK_SET);
305
306     h = headerRead(db->pkgs, HEADER_MAGIC_NO);
307
308     if (pristine || h == NULL) return h;
309
310     /* the RPM used to build much of RH 5.1 could produce packages whose
311        file lists did not have leading /'s. Now is a good time to fix
312        that */
313
314     /* If this tag isn't present, either no files are in the package or
315        we're dealing with a package that has just the compressed file name
316        list */
317     if (!headerGetEntryMinMemory(h, RPMTAG_OLDFILENAMES, NULL, 
318                            (void **) &fileNames, &fileCount)) return h;
319
320     for (i = 0; i < fileCount; i++) 
321         if (*fileNames[i] != '/') break;
322
323     if (i == fileCount) {
324         free(fileNames);
325     } else {    /* bad header -- let's clean it up */
326         const char ** newFileNames = alloca(sizeof(*newFileNames) * fileCount);
327         for (i = 0; i < fileCount; i++) {
328             char * newFileName = alloca(strlen(fileNames[i]) + 2);
329             if (*fileNames[i] != '/') {
330                 newFileName[0] = '/';
331                 newFileName[1] = '\0';
332             } else
333                 newFileName[0] = '\0';
334             strcat(newFileName, fileNames[i]);
335             newFileNames[i] = newFileName;
336         }
337
338         free(fileNames);
339
340         headerModifyEntry(h, RPMTAG_OLDFILENAMES, RPM_STRING_ARRAY_TYPE, 
341                           newFileNames, fileCount);
342     }
343
344     /* The file list was moved to a more compressed format which not
345        only saves memory (nice), but gives fingerprinting a nice, fat
346        speed boost (very nice). Go ahead and convert old headers to
347        the new style (this is a noop for new headers) */
348     compressFilelist(h);
349
350     return h;
351 }
352
353 Header rpmdbGetRecord(rpmdb db, unsigned int offset)
354 {
355     return doGetRecord(db, offset, 0);
356 }
357
358 int rpmdbFindByFile(rpmdb db, const char * filespec, dbiIndexSet * matches)
359 {
360     const char * dirName;
361     const char * baseName;
362     fingerPrint fp1, fp2;
363     dbiIndexSet allMatches = NULL;
364     int i, rc;
365     fingerPrintCache fpc;
366
367     *matches = NULL;
368     if ((baseName = strrchr(filespec, '/')) != NULL) {
369         char * t;
370         size_t len;
371
372         len = baseName - filespec + 1;
373         t = strncpy(alloca(len + 1), filespec, len);
374         t[len] = '\0';
375         dirName = t;
376         baseName++;
377     } else {
378         dirName = "";
379         baseName = filespec;
380     }
381
382     fpc = fpCacheCreate(20);
383     fp1 = fpLookup(fpc, dirName, baseName, 1);
384
385     rc = dbiSearchIndex(db->fileIndex, baseName, &allMatches);
386     if (rc) {
387         dbiFreeIndexSet(allMatches);
388         allMatches = NULL;
389         fpCacheFree(fpc);
390         return rc;
391     }
392
393     *matches = dbiCreateIndexSet();
394     i = 0;
395     while (i < dbiIndexSetCount(allMatches)) {
396         const char ** baseNames, ** dirNames;
397         int_32 * dirIndexes;
398         unsigned int recoff = dbiIndexRecordOffset(allMatches, i);
399         unsigned int prevoff;
400         Header h;
401
402         if ((h = rpmdbGetRecord(db, recoff)) == NULL) {
403             i++;
404             continue;
405         }
406
407         headerGetEntryMinMemory(h, RPMTAG_BASENAMES, NULL, 
408                                 (void **) &baseNames, NULL);
409         headerGetEntryMinMemory(h, RPMTAG_DIRINDEXES, NULL, 
410                                 (void **) &dirIndexes, NULL);
411         headerGetEntryMinMemory(h, RPMTAG_DIRNAMES, NULL, 
412                                 (void **) &dirNames, NULL);
413
414         do {
415             int num = dbiIndexRecordFileNumber(allMatches, i);
416
417             fp2 = fpLookup(fpc, dirNames[dirIndexes[num]], baseNames[num], 1);
418             if (FP_EQUAL(fp1, fp2))
419                 dbiAppendIndexRecord(*matches, dbiIndexRecordOffset(allMatches, i), dbiIndexRecordFileNumber(allMatches, i));
420
421             prevoff = recoff;
422             i++;
423             recoff = dbiIndexRecordOffset(allMatches, i);
424         } while (i < dbiIndexSetCount(allMatches) && 
425                 (i == 0 || recoff == prevoff));
426
427         free(baseNames);
428         free(dirNames);
429         headerFree(h);
430     }
431
432     if (allMatches) {
433         dbiFreeIndexSet(allMatches);
434         allMatches = NULL;
435     }
436
437     fpCacheFree(fpc);
438
439     if (dbiIndexSetCount(*matches) == 0) {
440         dbiFreeIndexSet(*matches);
441         *matches = NULL; 
442         return 1;
443     }
444
445     return 0;
446 }
447
448 int rpmdbFindByProvides(rpmdb db, const char * filespec, dbiIndexSet * matches) {
449     return dbiSearchIndex(db->providesIndex, filespec, matches);
450 }
451
452 int rpmdbFindByRequiredBy(rpmdb db, const char * filespec, dbiIndexSet * matches) {
453     return dbiSearchIndex(db->requiredbyIndex, filespec, matches);
454 }
455
456 int rpmdbFindByConflicts(rpmdb db, const char * filespec, dbiIndexSet * matches) {
457     return dbiSearchIndex(db->conflictsIndex, filespec, matches);
458 }
459
460 int rpmdbFindByTriggeredBy(rpmdb db, const char * filespec, dbiIndexSet * matches) {
461     return dbiSearchIndex(db->triggerIndex, filespec, matches);
462 }
463
464 int rpmdbFindByGroup(rpmdb db, const char * group, dbiIndexSet * matches) {
465     return dbiSearchIndex(db->groupIndex, group, matches);
466 }
467
468 int rpmdbFindPackage(rpmdb db, const char * name, dbiIndexSet * matches) {
469     return dbiSearchIndex(db->nameIndex, name, matches);
470 }
471
472 static void removeIndexEntry(dbiIndex dbi, const char * key, dbiIndexRecord rec,
473                              int tolerant, const char * idxName)
474 {
475     dbiIndexSet matches = NULL;
476     int rc;
477     
478     rc = dbiSearchIndex(dbi, key, &matches);
479     switch (rc) {
480       case 0:
481         if (dbiRemoveIndexRecord(matches, rec) && !tolerant) {
482             rpmError(RPMERR_DBCORRUPT, _("package %s not listed in %s"),
483                   key, idxName);
484         } else {
485             dbiUpdateIndex(dbi, key, matches);
486                /* errors from above will be reported from dbindex.c */
487         }
488         break;
489       case 1:
490         if (!tolerant) 
491             rpmError(RPMERR_DBCORRUPT, _("package %s not found in %s"), 
492                         key, idxName);
493         break;
494       case 2:
495         break;   /* error message already generated from dbindex.c */
496     }
497     if (matches) {
498         dbiFreeIndexSet(matches);
499         matches = NULL;
500     }
501 }
502
503 int rpmdbRemove(rpmdb db, unsigned int offset, int tolerant)
504 {
505     Header h;
506     char * name, * group;
507     int type;
508     unsigned int count;
509     dbiIndexRecord rec;
510     char ** baseNames, ** providesList, ** requiredbyList;
511     char ** conflictList, ** triggerList;
512     int i;
513
514
515     h = rpmdbGetRecord(db, offset);
516     if (h == NULL) {
517         rpmError(RPMERR_DBCORRUPT, _("cannot read header at %d for uninstall"),
518               offset);
519         return 1;
520     }
521
522     rec = dbiReturnIndexRecordInstance(offset, 0);
523
524     blockSignals();
525
526     if (!headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name, &count)) {
527         rpmError(RPMERR_DBCORRUPT, _("package has no name"));
528     } else {
529         rpmMessage(RPMMESS_DEBUG, _("removing name index\n"));
530         removeIndexEntry(db->nameIndex, name, rec, tolerant, "name index");
531     }
532
533     if (!headerGetEntry(h, RPMTAG_GROUP, &type, (void **) &group, &count)) {
534         rpmMessage(RPMMESS_DEBUG, _("package has no group\n"));
535     } else {
536         rpmMessage(RPMMESS_DEBUG, _("removing group index\n"));
537         removeIndexEntry(db->groupIndex, group, rec, tolerant, "group index");
538     }
539
540     if (headerGetEntry(h, RPMTAG_PROVIDENAME, &type, (void **) &providesList, 
541          &count)) {
542         for (i = 0; i < count; i++) {
543             rpmMessage(RPMMESS_DEBUG, _("removing provides index for %s\n"), 
544                     providesList[i]);
545             removeIndexEntry(db->providesIndex, providesList[i], rec, tolerant, 
546                              "providesfile index");
547         }
548         free(providesList);
549     }
550
551     if (headerGetEntry(h, RPMTAG_REQUIRENAME, &type, (void **) &requiredbyList, 
552          &count)) {
553         /* There could be dups in requiredByLIst, and the list is sorted.
554            Rather then sort the list, be tolerant of missing entries
555            as they should just indicate duplicated requirements. */
556
557         for (i = 0; i < count; i++) {
558             rpmMessage(RPMMESS_DEBUG, _("removing requiredby index for %s\n"), 
559                     requiredbyList[i]);
560             removeIndexEntry(db->requiredbyIndex, requiredbyList[i], rec, 
561                              1, "requiredby index");
562         }
563         free(requiredbyList);
564     }
565
566     if (headerGetEntry(h, RPMTAG_TRIGGERNAME, &type, (void **) &triggerList, 
567          &count)) {
568         /* triggerList often contains duplicates */
569         for (i = 0; i < count; i++) {
570             rpmMessage(RPMMESS_DEBUG, _("removing trigger index for %s\n"), 
571                        triggerList[i]);
572             removeIndexEntry(db->triggerIndex, triggerList[i], rec, 
573                              1, "trigger index");
574         }
575         free(triggerList);
576     }
577
578     if (headerGetEntry(h, RPMTAG_CONFLICTNAME, &type, (void **) &conflictList, 
579          &count)) {
580         for (i = 0; i < count; i++) {
581             rpmMessage(RPMMESS_DEBUG, _("removing conflict index for %s\n"), 
582                     conflictList[i]);
583             removeIndexEntry(db->conflictsIndex, conflictList[i], rec, 
584                              tolerant, "conflict index");
585         }
586         free(conflictList);
587     }
588
589     if (headerGetEntry(h, RPMTAG_BASENAMES, &type, (void **) &baseNames, 
590          &count)) {
591         for (i = 0; i < count; i++) {
592             rpmMessage(RPMMESS_DEBUG, _("removing file index for %s\n"), 
593                         baseNames[i]);
594             /* structure assignment */
595             rec = dbiReturnIndexRecordInstance(offset, i);
596             removeIndexEntry(db->fileIndex, baseNames[i], rec, tolerant, 
597                              "file index");
598         }
599         free(baseNames);
600     } else {
601         rpmMessage(RPMMESS_DEBUG, _("package has no files\n"));
602     }
603
604     fadFree(db->pkgs, offset);
605
606     dbiSyncIndex(db->nameIndex);
607     dbiSyncIndex(db->groupIndex);
608     dbiSyncIndex(db->fileIndex);
609
610     unblockSignals();
611
612     dbiFreeIndexRecordInstance(rec);
613     headerFree(h);
614
615     return 0;
616 }
617
618 static int addIndexEntry(dbiIndex idx, const char *index, unsigned int offset,
619                          unsigned int fileNumber)
620 {
621     dbiIndexSet set = NULL;
622     int rc;
623
624     rc = dbiSearchIndex(idx, index, &set);
625     switch (rc) {
626     case -1:                    /* error */
627         if (set) {
628             dbiFreeIndexSet(set);
629             set = NULL;
630         }
631         return 1;
632         /*@notreached@*/ break;
633     case 1:                     /* new item */
634         set = dbiCreateIndexSet();
635         break;
636     default:
637         break;
638     }
639
640     dbiAppendIndexRecord(set, offset, fileNumber);
641     if (dbiUpdateIndex(idx, index, set))
642         exit(EXIT_FAILURE);     /* XXX W2DO? return 1; */
643
644     if (set) {
645         dbiFreeIndexSet(set);
646         set = NULL;
647     }
648
649     return 0;
650 }
651
652 int rpmdbAdd(rpmdb db, Header dbentry)
653 {
654     unsigned int dboffset;
655     unsigned int i, j;
656     const char ** baseNames;
657     const char ** providesList;
658     const char ** requiredbyList;
659     const char ** conflictList;
660     const char ** triggerList;
661     const char * name;
662     const char * group;
663     int count = 0, providesCount = 0, requiredbyCount = 0, conflictCount = 0;
664     int triggerCount = 0;
665     int type;
666     int newSize;
667     int rc = 0;
668
669     headerGetEntry(dbentry, RPMTAG_NAME, &type, (void **) &name, &count);
670     headerGetEntry(dbentry, RPMTAG_GROUP, &type, (void **) &group, &count);
671
672     if (!group) group = "Unknown";
673
674     count = 0;
675
676     headerGetEntry(dbentry, RPMTAG_BASENAMES, &type, (void **) 
677                     &baseNames, &count);
678
679     if (_noDirTokens) {
680         const char ** newBaseNames;
681         char * data;
682         int len;
683         len = count * sizeof(*baseNames);
684         for (i = 0; i < count; i++)
685             len += strlen(baseNames[i]) + 1;
686         newBaseNames = xmalloc(len);
687         data = (char *) newBaseNames + count;
688         for (i = 0; i < count; i++) {
689             newBaseNames[i] = data;
690             data = stpcpy(data, baseNames[i]);
691             *data++ = '\0';
692         }
693         expandFilelist(dbentry);
694     }
695
696     headerGetEntry(dbentry, RPMTAG_PROVIDENAME, &type, (void **) &providesList, 
697                    &providesCount);
698     headerGetEntry(dbentry, RPMTAG_REQUIRENAME, &type, 
699                    (void **) &requiredbyList, &requiredbyCount);
700     headerGetEntry(dbentry, RPMTAG_CONFLICTNAME, &type, 
701                    (void **) &conflictList, &conflictCount);
702     headerGetEntry(dbentry, RPMTAG_TRIGGERNAME, &type, 
703                    (void **) &triggerList, &triggerCount);
704
705     blockSignals();
706
707     newSize = headerSizeof(dbentry, HEADER_MAGIC_NO);
708     dboffset = fadAlloc(db->pkgs, newSize);
709     if (!dboffset) {
710         rc = 1;
711     } else {
712         /* XXX TODO: set max. no. of bytes to write */
713         (void)Fseek(db->pkgs, dboffset, SEEK_SET);
714         fdSetContentLength(db->pkgs, newSize);
715         rc = headerWrite(db->pkgs, dbentry, HEADER_MAGIC_NO);
716         fdSetContentLength(db->pkgs, -1);
717     }
718
719     if (rc) {
720         rpmError(RPMERR_DBCORRUPT, _("cannot allocate space for database"));
721         goto exit;
722     }
723
724     /* Now update the appropriate indexes */
725     if (addIndexEntry(db->nameIndex, name, dboffset, 0))
726         rc = 1;
727     if (addIndexEntry(db->groupIndex, group, dboffset, 0))
728         rc = 1;
729
730     for (i = 0; i < triggerCount; i++) {
731         /* don't add duplicates */
732         for (j = 0; j < i; j++)
733             if (!strcmp(triggerList[i], triggerList[j])) break;
734         if (j == i)
735             rc += addIndexEntry(db->triggerIndex, triggerList[i], dboffset, 0);
736     }
737
738     for (i = 0; i < conflictCount; i++)
739         rc += addIndexEntry(db->conflictsIndex, conflictList[i], dboffset, 0);
740
741     for (i = 0; i < requiredbyCount; i++)
742         rc += addIndexEntry(db->requiredbyIndex, requiredbyList[i], 
743                             dboffset, 0);
744
745     for (i = 0; i < providesCount; i++)
746         rc += addIndexEntry(db->providesIndex, providesList[i], dboffset, 0);
747
748     for (i = 0; i < count; i++) {
749         rc += addIndexEntry(db->fileIndex, baseNames[i], dboffset, i);
750     }
751
752     dbiSyncIndex(db->nameIndex);
753     dbiSyncIndex(db->groupIndex);
754     dbiSyncIndex(db->fileIndex);
755     dbiSyncIndex(db->providesIndex);
756     dbiSyncIndex(db->requiredbyIndex);
757     dbiSyncIndex(db->triggerIndex);
758
759 exit:
760     unblockSignals();
761
762     if (requiredbyCount) free(requiredbyList);
763     if (providesCount) free(providesList);
764     if (conflictCount) free(conflictList);
765     if (triggerCount) free(triggerList);
766     if (count) free(baseNames);
767
768     return rc;
769 }
770
771 int rpmdbUpdateRecord(rpmdb db, int offset, Header newHeader)
772 {
773     Header oldHeader;
774     int oldSize, newSize;
775     int rc = 0;
776
777     oldHeader = doGetRecord(db, offset, 1);
778     if (oldHeader == NULL) {
779         rpmError(RPMERR_DBCORRUPT, _("cannot read header at %d for update"),
780                 offset);
781         return 1;
782     }
783
784     oldSize = headerSizeof(oldHeader, HEADER_MAGIC_NO);
785     headerFree(oldHeader);
786
787     if (_noDirTokens)
788         expandFilelist(newHeader);
789
790     newSize = headerSizeof(newHeader, HEADER_MAGIC_NO);
791     if (oldSize != newSize) {
792         rpmMessage(RPMMESS_DEBUG, _("header changed size!"));
793         if (rpmdbRemove(db, offset, 1))
794             return 1;
795
796         if (rpmdbAdd(db, newHeader)) 
797             return 1;
798     } else {
799         blockSignals();
800
801         /* XXX TODO: set max. no. of bytes to write */
802         (void)Fseek(db->pkgs, offset, SEEK_SET);
803
804         fdSetContentLength(db->pkgs, newSize);
805         rc = headerWrite(db->pkgs, newHeader, HEADER_MAGIC_NO);
806         fdSetContentLength(db->pkgs, -1);
807
808         unblockSignals();
809     }
810
811     return rc;
812 }
813
814 void rpmdbRemoveDatabase(const char * rootdir, const char * dbpath)
815
816     int i;
817     const char **rpmdbfnp;
818     char * filename;
819
820     i = strlen(dbpath);
821     if (dbpath[i - 1] != '/') {
822         filename = alloca(i);
823         strcpy(filename, dbpath);
824         filename[i] = '/';
825         filename[i + 1] = '\0';
826         dbpath = filename;
827     }
828     
829     filename = alloca(strlen(rootdir) + strlen(dbpath) + 40);
830
831     for (rpmdbfnp = rpmdb_filenames; *rpmdbfnp; rpmdbfnp++) {
832         sprintf(filename, "%s/%s/%s", rootdir, dbpath, *rpmdbfnp);
833         unlink(filename);
834     }
835
836     sprintf(filename, "%s/%s", rootdir, dbpath);
837     rmdir(filename);
838
839 }
840
841 int rpmdbMoveDatabase(const char * rootdir, const char * olddbpath, const char * newdbpath)
842 {
843     int i;
844     const char **rpmdbfnp;
845     char * ofilename, * nfilename;
846     int rc = 0;
847  
848     i = strlen(olddbpath);
849     if (olddbpath[i - 1] != '/') {
850         ofilename = alloca(i + 2);
851         strcpy(ofilename, olddbpath);
852         ofilename[i] = '/';
853         ofilename[i + 1] = '\0';
854         olddbpath = ofilename;
855     }
856     
857     i = strlen(newdbpath);
858     if (newdbpath[i - 1] != '/') {
859         nfilename = alloca(i + 2);
860         strcpy(nfilename, newdbpath);
861         nfilename[i] = '/';
862         nfilename[i + 1] = '\0';
863         newdbpath = nfilename;
864     }
865     
866     ofilename = alloca(strlen(rootdir) + strlen(olddbpath) + 40);
867     nfilename = alloca(strlen(rootdir) + strlen(newdbpath) + 40);
868
869     for (rpmdbfnp = rpmdb_filenames; *rpmdbfnp; rpmdbfnp++) {
870         sprintf(ofilename, "%s/%s/%s", rootdir, olddbpath, *rpmdbfnp);
871         sprintf(nfilename, "%s/%s/%s", rootdir, newdbpath, *rpmdbfnp);
872         if (Rename(ofilename, nfilename)) rc = 1;
873     }
874
875     return rc;
876 }
877
878 struct intMatch {
879     unsigned int recOffset;
880     unsigned int fileNumber;
881     int fpNum;
882 };
883
884 static int intMatchCmp(const void * one, const void * two)
885 {
886     const struct intMatch * a = one;
887     const struct intMatch * b = two;
888
889     if (a->recOffset < b->recOffset)
890         return -1;
891     else if (a->recOffset > b->recOffset)
892         return 1;
893
894     return 0;
895 }
896
897 int rpmdbFindFpList(rpmdb db, fingerPrint * fpList, dbiIndexSet * matchList, 
898                     int numItems)
899 {
900     int numIntMatches = 0;
901     int intMatchesAlloced = numItems;
902     struct intMatch * intMatches;
903     int i, j;
904     int start, end;
905     int num;
906     int_32 fc;
907     const char ** dirNames, ** baseNames;
908     const char ** fullBaseNames;
909     int_32 * dirIndexes, * fullDirIndexes;
910     fingerPrintCache fpc;
911
912     /* this may be worth batching by baseName, but probably not as
913        baseNames are quite unique as it is */
914
915     intMatches = xcalloc(intMatchesAlloced, sizeof(*intMatches));
916
917     /* Gather all matches from the database */
918     for (i = 0; i < numItems; i++) {
919         dbiIndexSet matches = NULL;
920         switch (dbiSearchIndex(db->fileIndex, fpList[i].baseName, &matches)) {
921         default:
922             break;
923         case 2:
924             if (matches) {
925                 dbiFreeIndexSet(matches);
926                 matches = NULL;
927             }
928             free(intMatches);
929             return 1;
930             /*@notreached@*/ break;
931         case 0:
932             if ((numIntMatches + dbiIndexSetCount(matches)) >= intMatchesAlloced) {
933                 intMatchesAlloced += dbiIndexSetCount(matches);
934                 intMatchesAlloced += intMatchesAlloced / 5;
935                 intMatches = xrealloc(intMatches, 
936                                      sizeof(*intMatches) * intMatchesAlloced);
937             }
938
939             for (j = 0; j < dbiIndexSetCount(matches); j++) {
940                 
941                 intMatches[numIntMatches].recOffset = dbiIndexRecordOffset(matches, j);
942                 intMatches[numIntMatches].fileNumber = dbiIndexRecordFileNumber(matches, j);
943                 intMatches[numIntMatches].fpNum = i;
944                 numIntMatches++;
945             }
946
947             break;
948         }
949         if (matches) {
950             dbiFreeIndexSet(matches);
951             matches = NULL;
952         }
953     }
954
955     qsort(intMatches, numIntMatches, sizeof(*intMatches), intMatchCmp);
956     /* intMatches is now sorted by (recnum, filenum) */
957
958     for (i = 0; i < numItems; i++)
959         matchList[i] = dbiCreateIndexSet();
960
961     fpc = fpCacheCreate(numIntMatches);
962
963     /* For each set of files matched in a package ... */
964     for (start = 0; start < numIntMatches; start = end) {
965         struct intMatch * im;
966         Header h;
967         fingerPrint * fps;
968
969         im = intMatches + start;
970
971         /* Find the end of the set of matched files in this package. */
972         for (end = start + 1; end < numIntMatches; end++) {
973             if (im->recOffset != intMatches[end].recOffset)
974                 break;
975         }
976         num = end - start;
977
978         /* Compute fingerprints for each file match in this package. */
979         h = rpmdbGetRecord(db, im->recOffset);
980         if (h == NULL) {
981             free(intMatches);
982             return 1;
983         }
984
985         headerGetEntryMinMemory(h, RPMTAG_DIRNAMES, NULL, 
986                             (void **) &dirNames, NULL);
987         headerGetEntryMinMemory(h, RPMTAG_BASENAMES, NULL, 
988                             (void **) &fullBaseNames, &fc);
989         headerGetEntryMinMemory(h, RPMTAG_DIRINDEXES, NULL, 
990                             (void **) &fullDirIndexes, NULL);
991
992         baseNames = xcalloc(num, sizeof(*baseNames));
993         dirIndexes = xcalloc(num, sizeof(*dirIndexes));
994         for (i = 0; i < num; i++) {
995             baseNames[i] = fullBaseNames[im[i].fileNumber];
996             dirIndexes[i] = fullDirIndexes[im[i].fileNumber];
997         }
998
999         fps = xcalloc(num, sizeof(*fps));
1000         fpLookupList(fpc, dirNames, baseNames, dirIndexes, num, fps);
1001
1002         free(dirNames);
1003         free(fullBaseNames);
1004         free(baseNames);
1005         free(dirIndexes);
1006
1007         /* Add db (recnum,filenum) to list for fingerprint matches. */
1008         for (i = 0; i < num; i++) {
1009             j = im[i].fpNum;
1010             if (FP_EQUAL_DIFFERENT_CACHE(fps[i], fpList[j]))
1011                 dbiAppendIndexRecord(matchList[j], im[i].recOffset, im[i].fileNumber);
1012         }
1013
1014         headerFree(h);
1015
1016         free(fps);
1017     }
1018
1019     fpCacheFree(fpc);
1020     free(intMatches);
1021
1022     return 0;
1023 }