4238d2d219a5582f41fff3af99a7ae2fe8acb05c
[platform/upstream/libsolv.git] / ext / pool_fileconflicts.c
1 /*
2  * Copyright (c) 2009-2013, Novell Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 #include <stdio.h>
9 #include <sys/stat.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13
14 #include "pool.h"
15 #include "repo.h"
16 #include "hash.h"
17 #include "repo_rpmdb.h"
18 #include "pool_fileconflicts.h"
19
20 struct cbdata {
21   Pool *pool;
22   int create;
23   int aliases;
24
25   Queue lookat;         /* conflict candidates */
26   Queue lookat_dir;     /* not yet conflicting directories */
27
28   Hashtable cflmap;
29   Hashval cflmapn;
30   unsigned int cflmapused;
31
32   Hashtable dirmap;
33   Hashval dirmapn;
34   unsigned int dirmapused;
35   int dirconflicts;
36
37   Map idxmap;
38
39   unsigned int lastdiridx;      /* last diridx we have seen */
40   unsigned int lastdirhash;     /* strhash of last dir we have seen */
41
42   Id idx;       /* index of package we're looking at */
43   Id hx;        /* used in findfileconflicts2_cb, limit to files matching hx */
44
45   Id dirid;     /* used in findfileconflicts2_cb, limit to dirs matching dirid */
46   Id dirhash;   /* used in findfileconflicts2_cb, limit to dirs matching dirhash */
47
48   Queue files;
49   unsigned char *filesspace;
50   unsigned int filesspacen;
51
52   Hashtable normap;
53   Hashval normapn;
54   unsigned int normapused;
55   Queue norq;
56
57   Hashtable statmap;
58   Hashval statmapn;
59   unsigned int statmapused;
60
61   int usestat;
62   int statsmade;
63
64   const char *rootdir;
65   int rootdirl;
66
67   char *canonspace;
68   int canonspacen;
69 };
70
71 #define FILESSPACE_BLOCK 255
72
73 static Hashtable
74 growhash(Hashtable map, Hashval *mapnp)
75 {
76   Hashval mapn = *mapnp;
77   Hashval newn = (mapn + 1) * 2 - 1;
78   Hashval i, h, hh;
79   Hashtable m;
80   Id hx, qx;
81
82   m = solv_calloc(newn + 1, 2 * sizeof(Id));
83   for (i = 0; i <= mapn; i++)
84     {
85       hx = map[2 * i];
86       if (!hx)
87         continue;
88       h = hx & newn;
89       hh = HASHCHAIN_START;
90       for (;;)
91         {
92           qx = m[2 * h];
93           if (!qx)
94             break;
95           h = HASHCHAIN_NEXT(h, hh, newn);
96         }
97       m[2 * h] = hx;
98       m[2 * h + 1] = map[2 * i + 1];
99     }
100   solv_free(map);
101   *mapnp = newn;
102   return m;
103 }
104
105 static void
106 finddirs_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
107 {
108   struct cbdata *cbdata = cbdatav;
109   Hashval h, hh;
110   Id hx, qx;
111   Id oidx, idx = cbdata->idx;
112
113   hx = strhash(fn);
114   if (!hx)
115     hx = strlen(fn) + 1;
116   h = hx & cbdata->dirmapn;
117   hh = HASHCHAIN_START;
118   for (;;)
119     {
120       qx = cbdata->dirmap[2 * h];
121       if (!qx)
122         break;
123       if (qx == hx)
124         break;
125       h = HASHCHAIN_NEXT(h, hh, cbdata->dirmapn);
126     }
127   if (!qx)
128     {
129       /* a miss */
130       if (!cbdata->create)
131         return;
132       cbdata->dirmap[2 * h] = hx;
133       cbdata->dirmap[2 * h + 1] = idx;
134       if (++cbdata->dirmapused * 2 > cbdata->dirmapn)
135         cbdata->dirmap = growhash(cbdata->dirmap, &cbdata->dirmapn);
136       return;
137     }
138   oidx = cbdata->dirmap[2 * h + 1];
139   if (oidx == idx)
140     return;
141   /* found a conflict, this dir may be used in multiple packages */
142   if (oidx != -1)
143     {
144       MAPSET(&cbdata->idxmap, oidx);
145       cbdata->dirmap[2 * h + 1] = -1;
146       cbdata->dirconflicts++;
147     }
148   MAPSET(&cbdata->idxmap, idx);
149 }
150
151 static inline int
152 isindirmap(struct cbdata *cbdata, Id hx)
153 {
154   Hashval h, hh;
155   Id qx;
156
157   h = hx & cbdata->dirmapn;
158   hh = HASHCHAIN_START;
159   for (;;)
160     {
161       qx = cbdata->dirmap[2 * h];
162       if (!qx)
163         return 0;
164       if (qx == hx)
165         return cbdata->dirmap[2 * h + 1] == -1 ? 1 : 0;
166       h = HASHCHAIN_NEXT(h, hh, cbdata->dirmapn);
167     }
168 }
169
170 static void
171 findfileconflicts_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
172 {
173   struct cbdata *cbdata = cbdatav;
174   int isdir = S_ISDIR(info->mode);
175   const char *dp;
176   Id idx, oidx;
177   Id hx, qx;
178   Hashval h, hh, dhx;
179
180   idx = cbdata->idx;
181
182   if (!info->dirlen)
183     return;
184   dp = fn + info->dirlen;
185   if (info->diridx != cbdata->lastdiridx)
186     {
187       cbdata->lastdiridx = info->diridx;
188       cbdata->lastdirhash = strnhash(fn, dp - fn);
189     }
190   dhx = cbdata->lastdirhash;
191   /* this mirrors the "if (!hx) hx = strlen(fn) + 1" in finddirs_cb */
192   if (!isindirmap(cbdata, dhx ? dhx : dp - fn + 1))
193     return;
194   hx = strhash_cont(dp, dhx);
195   if (!hx)
196     hx = strlen(fn) + 1;
197
198   h = hx & cbdata->cflmapn;
199   hh = HASHCHAIN_START;
200   for (;;)
201     {
202       qx = cbdata->cflmap[2 * h];
203       if (!qx)
204         break;
205       if (qx == hx)
206         break;
207       h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
208     }
209   if (!qx)
210     {
211       /* a miss */
212       if (!cbdata->create)
213         return;
214       cbdata->cflmap[2 * h] = hx;
215       cbdata->cflmap[2 * h + 1] = (isdir ? ~idx : idx);
216       if (++cbdata->cflmapused * 2 > cbdata->cflmapn)
217         cbdata->cflmap = growhash(cbdata->cflmap, &cbdata->cflmapn);
218       return;
219     }
220   oidx = cbdata->cflmap[2 * h + 1];
221   if (oidx < 0)
222     {
223       int i;
224       if (isdir)
225         {
226           /* both are directories. delay the conflict, keep oidx in slot */
227           queue_push2(&cbdata->lookat_dir, hx, idx);
228           return;
229         }
230       oidx = ~oidx;
231       /* now have file, had directories before. */
232       cbdata->cflmap[2 * h + 1] = oidx; /* make it a file */
233       /* dump all delayed directory hits for hx */
234       for (i = 0; i < cbdata->lookat_dir.count; i += 2)
235         if (cbdata->lookat_dir.elements[i] == hx)
236           {
237             queue_push2(&cbdata->lookat, hx, cbdata->lookat_dir.elements[i + 1]);
238             queue_push2(&cbdata->lookat, 0, 0);
239           }
240     }
241   else if (oidx == idx)
242     return;     /* no conflicts with ourself, please */
243   queue_push2(&cbdata->lookat, hx, oidx);
244   queue_push2(&cbdata->lookat, 0, 0);
245   queue_push2(&cbdata->lookat, hx, idx);
246   queue_push2(&cbdata->lookat, 0, 0);
247 }
248
249 /* same as findfileconflicts_cb, but
250  * - hashes with just the basename
251  * - sets idx in a map instead of pushing to lookat
252  * - sets the hash element to -1 if there may be a conflict
253  */
254 static void
255 findfileconflicts_basename_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
256 {
257   struct cbdata *cbdata = cbdatav;
258   int isdir = S_ISDIR(info->mode);
259   const char *dp;
260   Id idx, oidx;
261   Id hx, qx;
262   Hashval h, hh;
263
264   idx = cbdata->idx;
265
266   if (!info->dirlen)
267     return;
268   dp = fn + info->dirlen;
269   hx = strhash(dp);
270   if (!hx)
271     hx = strlen(fn) + 1;
272
273   h = hx & cbdata->cflmapn;
274   hh = HASHCHAIN_START;
275   for (;;)
276     {
277       qx = cbdata->cflmap[2 * h];
278       if (!qx)
279         break;
280       if (qx == hx)
281         break;
282       h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
283     }
284   if (!qx)
285     {
286       /* a miss */
287       if (!cbdata->create)
288         return;
289       cbdata->cflmap[2 * h] = hx;
290       cbdata->cflmap[2 * h + 1] = (isdir ? -idx - 2 : idx);
291       if (++cbdata->cflmapused * 2 > cbdata->cflmapn)
292         cbdata->cflmap = growhash(cbdata->cflmap, &cbdata->cflmapn);
293       return;
294     }
295   oidx = cbdata->cflmap[2 * h + 1];
296   if (oidx < -1)
297     {
298       int i;
299       if (isdir)
300         {
301           /* both are directories. delay the conflict, keep oidx in slot */
302           queue_push2(&cbdata->lookat_dir, hx, idx);
303           return;
304         }
305       oidx = -idx - 2;
306       /* now have file, had directories before. */
307       cbdata->cflmap[2 * h + 1] = oidx; /* make it a file */
308       /* dump all delayed directory hits for hx */
309       for (i = 0; i < cbdata->lookat_dir.count; i += 2)
310         if (cbdata->lookat_dir.elements[i] == hx)
311           MAPSET(&cbdata->idxmap, cbdata->lookat_dir.elements[i + 1]);
312     }
313   else if (oidx == idx)
314     return;     /* no conflicts with ourself, please */
315   if (oidx >= 0)
316     MAPSET(&cbdata->idxmap, oidx);
317   MAPSET(&cbdata->idxmap, idx);
318   if (oidx != -1)
319     cbdata->cflmap[2 * h + 1] = -1;
320 }
321
322 static inline Id
323 addfilesspace(struct cbdata *cbdata, int len)
324 {
325   unsigned int off = cbdata->filesspacen;
326   cbdata->filesspace = solv_extend(cbdata->filesspace, cbdata->filesspacen, len, 1, FILESSPACE_BLOCK);
327   cbdata->filesspacen += len;
328   return off;
329 }
330
331 static Id
332 unifywithstat(struct cbdata *cbdata, Id diroff, int dirl)
333 {
334   struct stat stb;
335   int i;
336   Hashval h, hh;
337   Id hx, qx;
338   Id nspaceoff;
339   unsigned char statdata[16 + sizeof(stb.st_dev) + sizeof(stb.st_ino)];
340
341   if (dirl > 1 && cbdata->filesspace[diroff + dirl - 1] == '/')
342     cbdata->filesspace[diroff + dirl - 1] = 0;
343   cbdata->statsmade++;
344   i = stat((char *)cbdata->filesspace + diroff, &stb);
345   if (dirl > 1 && cbdata->filesspace[diroff + dirl - 1] == 0)
346     cbdata->filesspace[diroff + dirl - 1] = '/';
347   if (i)
348     return diroff;
349   memset(statdata, 0, 16);
350   memcpy(statdata + 8, &stb.st_dev, sizeof(stb.st_dev));
351   memcpy(statdata, &stb.st_ino, sizeof(stb.st_ino));
352   hx = 0;
353   for (i = 15; i >= 0; i--)
354     hx = (unsigned int)hx * 13 + statdata[i];
355   h = hx & cbdata->statmapn;
356   hh = HASHCHAIN_START;
357   for (;;)
358     {
359       qx = cbdata->statmap[2 * h];
360       if (!qx)
361         break;
362       if (qx == hx)
363         {
364           Id off = cbdata->statmap[2 * h + 1];
365           char *dp = (char *)cbdata->filesspace + cbdata->norq.elements[off];
366           if (!memcmp(dp, statdata, 16))
367             return cbdata->norq.elements[off + 1];
368         }
369       h = HASHCHAIN_NEXT(h, hh, cbdata->statmapn);
370     }
371   /* new stat result. work. */
372   nspaceoff = addfilesspace(cbdata, 16);
373   memcpy(cbdata->filesspace + nspaceoff, statdata, 16);
374   queue_push2(&cbdata->norq, nspaceoff, nspaceoff);
375   cbdata->statmap[2 * h] = hx;
376   cbdata->statmap[2 * h + 1] = cbdata->norq.count - 2;
377   if (++cbdata->statmapused * 2 > cbdata->statmapn)
378     cbdata->statmap = growhash(cbdata->statmap, &cbdata->statmapn);
379   return nspaceoff;
380 }
381
382 /* forward declaration */
383 static Id normalizedir(struct cbdata *cbdata, const char *dir, int dirl, Id hx, int create);
384
385 static Id
386 unifywithcanon(struct cbdata *cbdata, Id diroff, int dirl)
387 {
388   Id dirnameid;
389   int i, l, ll, lo;
390   struct stat stb;
391
392 #if 0
393   printf("UNIFY %.*s\n", dirl, (char *)cbdata->filesspace + diroff);
394 #endif
395   if (!dirl || cbdata->filesspace[diroff] != '/')
396     return diroff;
397   /* strip / at end*/
398   while (dirl && cbdata->filesspace[diroff + dirl - 1] == '/')
399     dirl--;
400   if (!dirl)
401     return diroff;
402
403   /* find dirname */
404   for (i = dirl - 1; i > 0; i--)
405     if (cbdata->filesspace[diroff + i] == '/')
406       break;
407   i++;                          /* include trailing / */
408
409   /* normalize dirname */
410   dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + diroff, i, strnhash((char *)cbdata->filesspace + diroff, i), 1);
411   if (dirnameid == -1)
412     return diroff;              /* hit "in progress" marker, some cyclic link */
413
414   /* sanity check result */
415   if (cbdata->filesspace[dirnameid] != '/')
416     return diroff;              /* hmm */
417   l = strlen((char *)cbdata->filesspace + dirnameid);
418   if (l && cbdata->filesspace[dirnameid + l - 1] != '/')
419     return diroff;              /* hmm */
420
421   /* special handling for "." and ".." basename */
422   if (cbdata->filesspace[diroff + i] == '.')
423     {
424       if (dirl - i == 1)
425         return dirnameid;
426       if (dirl - i == 2 && cbdata->filesspace[diroff + i + 1] == '.')
427         {
428           if (l <= 2)
429             return dirnameid;   /* we hit our root */
430           for (i = l - 2; i > 0; i--)
431             if (cbdata->filesspace[dirnameid + i] == '/')
432               break;
433           i++;  /* include trailing / */
434           dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + dirnameid, i, strnhash((char *)cbdata->filesspace + dirnameid, i), 1);
435           return dirnameid == -1 ? diroff : dirnameid;
436         }
437     }
438
439   /* append basename to normalized dirname */
440   if (cbdata->rootdirl + l + dirl - i + 1 > cbdata->canonspacen)
441     {
442       cbdata->canonspacen = cbdata->rootdirl + l + dirl - i + 20;
443       cbdata->canonspace = solv_realloc(cbdata->canonspace, cbdata->canonspacen);
444       strcpy(cbdata->canonspace, cbdata->rootdir);
445     }
446   strcpy(cbdata->canonspace + cbdata->rootdirl, (char *)cbdata->filesspace + dirnameid);
447   strncpy(cbdata->canonspace + cbdata->rootdirl + l, (char *)cbdata->filesspace + diroff + i, dirl - i);
448   cbdata->canonspace[cbdata->rootdirl + l + dirl - i] = 0;
449
450 #if 0
451   printf("stat()ing %s\n", cbdata->canonspace);
452 #endif
453   cbdata->statsmade++;
454   if (lstat(cbdata->canonspace, &stb) != 0 || !S_ISLNK(stb.st_mode))
455     {
456       /* not a symlink or stat failed, have new canon entry */
457       diroff = addfilesspace(cbdata, l + dirl - i + 2);
458       strcpy((char *)cbdata->filesspace + diroff, cbdata->canonspace + cbdata->rootdirl);
459       l += dirl - i;
460       /* add trailing / */
461       if (cbdata->filesspace[diroff + l - 1] != '/')
462         {
463           cbdata->filesspace[diroff + l++] = '/';
464           cbdata->filesspace[diroff + l] = 0;
465         }
466       /* call normalizedir on new entry for unification purposes */
467       dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + diroff, l, strnhash((char *)cbdata->filesspace + diroff, l), 1);
468       return dirnameid == -1 ? diroff : dirnameid;
469     }
470   /* oh no, a symlink! follow */
471   lo = cbdata->rootdirl + l + dirl - i + 1;
472   if (lo + stb.st_size + 2 > cbdata->canonspacen)
473     {
474       cbdata->canonspacen = lo + stb.st_size + 20;
475       cbdata->canonspace = solv_realloc(cbdata->canonspace, cbdata->canonspacen);
476     }
477   ll = readlink(cbdata->canonspace, cbdata->canonspace + lo, stb.st_size);
478   if (ll < 0 || ll > stb.st_size)
479     return diroff;              /* hmm */
480   if (ll == 0)
481     return dirnameid;           /* empty means current dir */
482   if (cbdata->canonspace[lo + ll - 1] != '/')
483     cbdata->canonspace[lo + ll++] = '/';        /* add trailing / */
484   cbdata->canonspace[lo + ll] = 0;              /* zero terminate */
485   if (cbdata->canonspace[lo] != '/')
486     {
487       /* relative link, concatenate to dirname */
488       memmove(cbdata->canonspace + cbdata->rootdirl + l, cbdata->canonspace + lo, ll + 1);
489       lo = cbdata->rootdirl;
490       ll += l;
491     }
492   dirnameid = normalizedir(cbdata, cbdata->canonspace + lo, ll, strnhash(cbdata->canonspace + lo, ll), 1);
493   return dirnameid == -1 ? diroff : dirnameid;
494 }
495
496 /*
497  * map a directory (containing a trailing /) into a number.
498  * for unifywithstat this is the offset to the 16 byte stat result.
499  * for unifywithcanon this is the offset to the normailzed dir.
500  */
501 static Id
502 normalizedir(struct cbdata *cbdata, const char *dir, int dirl, Id hx, int create)
503 {
504   Hashval h, hh;
505   Id qx;
506   Id nspaceoff;
507   int mycnt;
508
509   if (!hx)
510     hx = dirl + 1;
511   h = hx & cbdata->normapn;
512   hh = HASHCHAIN_START;
513   for (;;)
514     {
515       qx = cbdata->normap[2 * h];
516       if (!qx)
517         break;
518       if (qx == hx)
519         {
520           Id off = cbdata->normap[2 * h + 1];
521           char *dp = (char *)cbdata->filesspace + cbdata->norq.elements[off];
522           if (!strncmp(dp, dir, dirl) && dp[dirl] == 0)
523             return cbdata->norq.elements[off + 1];
524         }
525       h = HASHCHAIN_NEXT(h, hh, cbdata->normapn);
526     }
527   if (!create)
528     return 0;
529   /* new dir. work. */
530   if (dir >= (const char *)cbdata->filesspace && dir < (const char *)cbdata->filesspace + cbdata->filesspacen)
531     {
532       /* can happen when called from unifywithcanon */
533       Id off = dir - (const char *)cbdata->filesspace;
534       nspaceoff = addfilesspace(cbdata, dirl + 1);
535       dir = (const char *)cbdata->filesspace + off;
536     }
537   else
538     nspaceoff = addfilesspace(cbdata, dirl + 1);
539   if (dirl)
540     memcpy(cbdata->filesspace + nspaceoff, dir, dirl);
541   cbdata->filesspace[nspaceoff + dirl] = 0;
542   mycnt = cbdata->norq.count;
543   queue_push2(&cbdata->norq, nspaceoff, -1);    /* -1: in progress */
544   cbdata->normap[2 * h] = hx;
545   cbdata->normap[2 * h + 1] = mycnt;
546   if (++cbdata->normapused * 2 > cbdata->normapn)
547     cbdata->normap = growhash(cbdata->normap, &cbdata->normapn);
548   /* unify */
549   if (cbdata->usestat)
550     nspaceoff = unifywithstat(cbdata, nspaceoff, dirl);
551   else
552     nspaceoff = unifywithcanon(cbdata, nspaceoff, dirl);
553   cbdata->norq.elements[mycnt + 1] = nspaceoff; /* patch in result */
554 #if 0
555   if (!cbdata->usestat)
556     printf("%s normalized to %d: %s\n", cbdata->filesspace + cbdata->norq.elements[mycnt], nspaceoff, cbdata->filesspace + nspaceoff);
557 #endif
558   return nspaceoff;
559 }
560
561 static void
562 findfileconflicts_alias_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
563 {
564   int isdir = S_ISDIR(info->mode);
565   struct cbdata *cbdata = cbdatav;
566   const char *dp;
567   Id idx, dirid;
568   Id hx, qx;
569   Hashval h, hh;
570
571   idx = cbdata->idx;
572
573   if (!info->dirlen)
574     return;
575   dp = fn + info->dirlen;
576   if (info->diridx != cbdata->lastdiridx)
577     {
578       cbdata->lastdiridx = info->diridx;
579       cbdata->lastdirhash = 0;
580     }
581   dp = fn + info->dirlen;
582   hx = strhash(dp);
583   if (!hx)
584     hx = strlen(fn) + 1;
585
586   h = hx & cbdata->cflmapn;
587   hh = HASHCHAIN_START;
588   for (;;)
589     {
590       qx = cbdata->cflmap[2 * h];
591       if (!qx)
592         break;
593       if (qx == hx)
594         break;
595       h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
596     }
597   if (!qx || cbdata->cflmap[2 * h + 1] != -1)
598     return;
599   if (!cbdata->lastdirhash)
600     cbdata->lastdirhash = strnhash(fn, dp - fn);
601   dirid = normalizedir(cbdata, fn, dp - fn, cbdata->lastdirhash, 1);
602   queue_push2(&cbdata->lookat, hx, idx);
603   queue_push2(&cbdata->lookat, cbdata->lastdirhash, isdir ? -dirid : dirid);
604 }
605
606 static void
607 findfileconflicts2_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
608 {
609   struct cbdata *cbdata = cbdatav;
610   Hashval hx;
611   const char *dp;
612   char md5padded[34];
613   Id off;
614
615   if (!info->dirlen)
616     return;
617   dp = fn + info->dirlen;
618   if (info->diridx != cbdata->lastdiridx)
619     {
620       cbdata->lastdiridx = info->diridx;
621       cbdata->lastdirhash = strnhash(fn, dp - fn);
622     }
623   if (cbdata->aliases)
624     {
625       if (cbdata->lastdirhash != cbdata->dirhash)
626         return;
627       hx = strhash(dp);
628     }
629   else
630     {
631       hx = cbdata->lastdirhash;
632       hx = strhash_cont(dp, hx);
633     }
634   if (!hx)
635     hx = strlen(fn) + 1;
636   if ((Id)hx != cbdata->hx)
637     return;
638   if (cbdata->dirid && cbdata->dirid != normalizedir(cbdata, fn, dp - fn, cbdata->dirhash, 0))
639     return;
640   strncpy(md5padded, info->digest, 32);
641   md5padded[32] = 0;
642   md5padded[33] = info->color;
643   /* printf("%d, hx %x -> %s   %d %s\n", cbdata->idx, hx, fn, info->mode, info->digest); */
644   off = addfilesspace(cbdata, strlen(fn) + (34 + 1));
645   memcpy(cbdata->filesspace + off, (unsigned char *)md5padded, 34);
646   strcpy((char *)cbdata->filesspace + off + 34, fn);
647   queue_push(&cbdata->files, off);
648 }
649
650 static int
651 lookat_idx_cmp(const void *ap, const void *bp, void *dp)
652 {
653   const Id *a = ap, *b = bp;
654   unsigned int ahx, bhx;
655   if (a[1] - b[1] != 0)         /* idx */
656     return a[1] - b[1];
657   if (a[3] - b[3] != 0)         /* dirid */
658     return a[3] - b[3];
659   ahx = (unsigned int)a[0];     /* can be < 0 */
660   bhx = (unsigned int)b[0];
661   if (ahx != bhx)
662     return ahx < bhx ? -1 : 1;
663   ahx = (unsigned int)a[2];     /* dhx */
664   bhx = (unsigned int)b[2];
665   if (ahx != bhx)
666     return ahx < bhx ? -1 : 1;
667   return 0;
668 }
669
670 static int
671 lookat_hx_cmp(const void *ap, const void *bp, void *dp)
672 {
673   const Id *a = ap, *b = bp;
674   unsigned int ahx, bhx;
675   Id adirid, bdirid;
676   ahx = (unsigned int)a[0];     /* can be < 0 */
677   bhx = (unsigned int)b[0];
678   if (ahx != bhx)
679     return ahx < bhx ? -1 : 1;
680   adirid = a[3] < 0 ? -a[3] : a[3];
681   bdirid = b[3] < 0 ? -b[3] : b[3];
682   if (adirid - bdirid != 0)     /* dirid */
683     return adirid - bdirid;
684   if (a[3] != b[3])
685     return a[3] > 0 ? -1 : 1;   /* bring positive dirids to front */
686   if (a[1] - b[1] != 0)         /* idx */
687     return a[1] - b[1];
688   ahx = (unsigned int)a[2];     /* dhx */
689   bhx = (unsigned int)b[2];
690   if (ahx != bhx)
691     return ahx < bhx ? -1 : 1;
692   return 0;
693 }
694
695 static int
696 conflicts_cmp(const void *ap, const void *bp, void *dp)
697 {
698   Pool *pool = dp;
699   const Id *a = ap;
700   const Id *b = bp;
701   if (a[0] != b[0])     /* filename1 */
702     return strcmp(pool_id2str(pool, a[0]), pool_id2str(pool, b[0]));
703   if (a[3] != b[3])     /* filename2 */
704     return strcmp(pool_id2str(pool, a[3]), pool_id2str(pool, b[3]));
705   if (a[1] != b[1])     /* pkgid1 */
706     return a[1] - b[1];
707   if (a[4] != b[4])     /* pkgid2 */
708     return a[4] - b[4];
709   return 0;
710 }
711
712 static void
713 iterate_solvable_dirs(Pool *pool, Id p, void (*cb)(void *, const char *, struct filelistinfo *), void *cbdata)
714 {
715   Repodata *lastdata = 0;
716   Id lastdirid = -1;
717   Dataiterator di;
718
719   dataiterator_init(&di, pool, 0, p, SOLVABLE_FILELIST, 0, SEARCH_COMPLETE_FILELIST);
720   while (dataiterator_step(&di))
721     {
722       if (di.data == lastdata && di.kv.id == lastdirid)
723         continue;
724       lastdata = di.data;
725       lastdirid = di.kv.id;
726       cb(cbdata, repodata_dir2str(di.data, di.kv.id, ""), 0);
727     }
728   dataiterator_free(&di);
729 }
730
731 /* before calling the expensive findfileconflicts_cb we check if any of
732  * the files match. This only makes sense when cbdata->create is off.
733  */
734 static int
735 precheck_solvable_files(struct cbdata *cbdata, Pool *pool, Id p)
736 {
737   Dataiterator di;
738   Id hx, qx;
739   Hashval h, hh;
740   int found = 0;
741   int aliases = cbdata->aliases;
742   unsigned int lastdirid = -1;
743   Hashval lastdirhash = 0;
744   int lastdirlen = 0;
745   int checkthisdir = 0;
746   Repodata *lastrepodata = 0;
747
748   dataiterator_init(&di, pool, 0, p, SOLVABLE_FILELIST, 0, SEARCH_COMPLETE_FILELIST);
749   while (dataiterator_step(&di))
750     {
751       if (aliases)
752         {
753           /* hash just the basename */
754           hx = strhash(di.kv.str);
755           if (!hx)
756             hx = strlen(di.kv.str) + 1;
757         }
758       else
759         {
760           /* hash the full path */
761           if (di.data != lastrepodata || di.kv.id != lastdirid)
762             {
763               const char *dir;
764               lastrepodata = di.data;
765               lastdirid = di.kv.id;
766               dir = repodata_dir2str(lastrepodata, lastdirid, "");
767               lastdirlen = strlen(dir);
768               lastdirhash = strhash(dir);
769               checkthisdir =  isindirmap(cbdata, lastdirhash ? lastdirhash : lastdirlen + 1);
770             }
771           if (!checkthisdir)
772             continue;
773           hx = strhash_cont(di.kv.str, lastdirhash);
774           if (!hx)
775             hx = lastdirlen + strlen(di.kv.str) + 1;
776         }
777       h = hx & cbdata->cflmapn;
778       hh = HASHCHAIN_START;
779       for (;;)
780         {
781           qx = cbdata->cflmap[2 * h];
782           if (!qx)
783             break;
784           if (qx == hx)
785             {
786               found = 1;
787               break;
788             }
789           h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
790         }
791       if (found)
792         break;
793     }
794   dataiterator_free(&di);
795   return found;
796 }
797
798
799 int
800 pool_findfileconflicts(Pool *pool, Queue *pkgs, int cutoff, Queue *conflicts, int flags, void *(*handle_cb)(Pool *, Id, void *) , void *handle_cbdata)
801 {
802   int i, j, cflmapn, idxmapset;
803   struct cbdata cbdata;
804   unsigned int now, start;
805   void *handle;
806   Repo *installed = pool->installed;
807   Id p;
808   int obsoleteusescolors = pool_get_flag(pool, POOL_FLAG_OBSOLETEUSESCOLORS);
809   int hdrfetches;
810
811   queue_empty(conflicts);
812   if (!pkgs->count)
813     return 0;
814
815   now = start = solv_timems(0);
816   POOL_DEBUG(SOLV_DEBUG_STATS, "searching for file conflicts\n");
817   POOL_DEBUG(SOLV_DEBUG_STATS, "packages: %d, cutoff %d\n", pkgs->count, cutoff);
818
819   memset(&cbdata, 0, sizeof(cbdata));
820   cbdata.aliases = flags & FINDFILECONFLICTS_CHECK_DIRALIASING;
821   cbdata.pool = pool;
822   if (cbdata.aliases && (flags & FINDFILECONFLICTS_USE_ROOTDIR) != 0)
823     {
824       cbdata.rootdir = pool_get_rootdir(pool);
825       if (cbdata.rootdir && !strcmp(cbdata.rootdir, "/"))
826         cbdata.rootdir = 0;
827       if (cbdata.rootdir)
828         cbdata.rootdirl = strlen(cbdata.rootdir);
829       if (!cbdata.rootdir)
830         cbdata.usestat = 1;
831     }
832   queue_init(&cbdata.lookat);
833   queue_init(&cbdata.lookat_dir);
834   map_init(&cbdata.idxmap, pkgs->count);
835
836   if (cutoff <= 0)
837     cutoff = pkgs->count;
838
839   /* avarage file list size: 200 files per package */
840   /* avarage dir count: 20 dirs per package */
841
842   /* first pass: scan dirs */
843   if (!cbdata.aliases)
844     {
845       hdrfetches = 0;
846       cflmapn = (cutoff + 3) * 64;
847       while ((cflmapn & (cflmapn - 1)) != 0)
848         cflmapn = cflmapn & (cflmapn - 1);
849       cbdata.dirmap = solv_calloc(cflmapn, 2 * sizeof(Id));
850       cbdata.dirmapn = cflmapn - 1;     /* make it a mask */
851       cbdata.create = 1;
852       idxmapset = 0;
853       for (i = 0; i < pkgs->count; i++)
854         {
855           if (i == cutoff)
856             cbdata.create = 0;
857           cbdata.idx = i;
858           p = pkgs->elements[i];
859           if ((flags & FINDFILECONFLICTS_USE_SOLVABLEFILELIST) != 0 && installed)
860             {
861               if (p >= installed->start && p < installed->end && pool->solvables[p].repo == installed)
862                 {
863                   iterate_solvable_dirs(pool, p, finddirs_cb, &cbdata);
864                   if (MAPTST(&cbdata.idxmap, i))
865                     idxmapset++;
866                   continue;
867                 }
868             }
869           handle = (*handle_cb)(pool, p, handle_cbdata);
870           if (!handle)
871             continue;
872           hdrfetches++;
873           rpm_iterate_filelist(handle, RPM_ITERATE_FILELIST_ONLYDIRS, finddirs_cb, &cbdata);
874           if (MAPTST(&cbdata.idxmap, i))
875             idxmapset++;
876         }
877       POOL_DEBUG(SOLV_DEBUG_STATS, "dirmap size: %d, used %d\n", cbdata.dirmapn + 1, cbdata.dirmapused);
878       POOL_DEBUG(SOLV_DEBUG_STATS, "dirmap memory usage: %d K\n", (cbdata.dirmapn + 1) * 2 * (int)sizeof(Id) / 1024);
879       POOL_DEBUG(SOLV_DEBUG_STATS, "header fetches: %d\n", hdrfetches);
880       POOL_DEBUG(SOLV_DEBUG_STATS, "dirmap creation took %d ms\n", solv_timems(now));
881       POOL_DEBUG(SOLV_DEBUG_STATS, "dir conflicts found: %d, idxmap %d of %d\n", cbdata.dirconflicts, idxmapset, pkgs->count);
882     }
883
884   /* second pass: scan files */
885   now = solv_timems(0);
886   cflmapn = (cutoff + 3) * 128;
887   while ((cflmapn & (cflmapn - 1)) != 0)
888     cflmapn = cflmapn & (cflmapn - 1);
889   cbdata.cflmap = solv_calloc(cflmapn, 2 * sizeof(Id));
890   cbdata.cflmapn = cflmapn - 1; /* make it a mask */
891   cbdata.create = 1;
892   hdrfetches = 0;
893   for (i = 0; i < pkgs->count; i++)
894     {
895       if (i == cutoff)
896         cbdata.create = 0;
897       if (!cbdata.aliases && !MAPTST(&cbdata.idxmap, i))
898         continue;
899       cbdata.idx = i;
900       p = pkgs->elements[i];
901       if (!cbdata.create && (flags & FINDFILECONFLICTS_USE_SOLVABLEFILELIST) != 0 && installed)
902         {
903           if (p >= installed->start && p < installed->end && pool->solvables[p].repo == installed)
904             if (!precheck_solvable_files(&cbdata, pool, p))
905               continue;
906         }
907       /* can't use FINDFILECONFLICTS_USE_SOLVABLEFILELIST because we have to know if
908        * the file is a directory or not */
909       handle = (*handle_cb)(pool, p, handle_cbdata);
910       if (!handle)
911         continue;
912       hdrfetches++;
913       cbdata.lastdiridx = -1;
914       rpm_iterate_filelist(handle, RPM_ITERATE_FILELIST_NOGHOSTS, cbdata.aliases ? findfileconflicts_basename_cb : findfileconflicts_cb, &cbdata);
915     }
916
917   POOL_DEBUG(SOLV_DEBUG_STATS, "filemap size: %d, used %d\n", cbdata.cflmapn + 1, cbdata.cflmapused);
918   POOL_DEBUG(SOLV_DEBUG_STATS, "filemap memory usage: %d K\n", (cbdata.cflmapn + 1) * 2 * (int)sizeof(Id) / 1024);
919   POOL_DEBUG(SOLV_DEBUG_STATS, "header fetches: %d\n", hdrfetches);
920   POOL_DEBUG(SOLV_DEBUG_STATS, "filemap creation took %d ms\n", solv_timems(now));
921   POOL_DEBUG(SOLV_DEBUG_STATS, "lookat_dir size: %d\n", cbdata.lookat_dir.count);
922   queue_free(&cbdata.lookat_dir);
923
924   /* we need another pass for aliases */
925   if (cbdata.aliases)
926     {
927       now = solv_timems(0);
928       /* make sure the first offset is not zero */
929       addfilesspace(&cbdata, 1);
930       cflmapn = (cutoff + 3) * 16;
931       while ((cflmapn & (cflmapn - 1)) != 0)
932         cflmapn = cflmapn & (cflmapn - 1);
933       cbdata.normap = solv_calloc(cflmapn, 2 * sizeof(Id));
934       cbdata.normapn = cflmapn - 1;     /* make it a mask */
935       if (cbdata.usestat)
936         {
937           cbdata.statmap = solv_calloc(cflmapn, 2 * sizeof(Id));
938           cbdata.statmapn = cflmapn - 1;        /* make it a mask */
939         }
940       cbdata.create = 0;
941       hdrfetches = 0;
942       for (i = 0; i < pkgs->count; i++)
943         {
944           if (!MAPTST(&cbdata.idxmap, i))
945             continue;
946           p = pkgs->elements[i];
947           cbdata.idx = i;
948           /* can't use FINDFILECONFLICTS_USE_SOLVABLEFILELIST because we have to know if
949            * the file is a directory or not */
950           handle = (*handle_cb)(pool, p, handle_cbdata);
951           if (!handle)
952             continue;
953           hdrfetches++;
954           cbdata.lastdiridx = -1;
955           rpm_iterate_filelist(handle, RPM_ITERATE_FILELIST_NOGHOSTS, findfileconflicts_alias_cb, &cbdata);
956         }
957       POOL_DEBUG(SOLV_DEBUG_STATS, "normap size: %d, used %d\n", cbdata.normapn + 1, cbdata.normapused);
958       POOL_DEBUG(SOLV_DEBUG_STATS, "normap memory usage: %d K\n", (cbdata.normapn + 1) * 2 * (int)sizeof(Id) / 1024);
959       POOL_DEBUG(SOLV_DEBUG_STATS, "header fetches: %d\n", hdrfetches);
960       POOL_DEBUG(SOLV_DEBUG_STATS, "stats made: %d\n", cbdata.statsmade);
961       if (cbdata.usestat)
962         {
963           POOL_DEBUG(SOLV_DEBUG_STATS, "statmap size: %d, used %d\n", cbdata.statmapn + 1, cbdata.statmapused);
964           POOL_DEBUG(SOLV_DEBUG_STATS, "statmap memory usage: %d K\n", (cbdata.statmapn + 1) * 2 * (int)sizeof(Id) / 1024);
965         }
966       cbdata.statmap = solv_free(cbdata.statmap);
967       cbdata.statmapn = 0;
968       cbdata.canonspace = solv_free(cbdata.canonspace);
969       cbdata.canonspacen = 0;
970       POOL_DEBUG(SOLV_DEBUG_STATS, "alias processing took %d ms\n", solv_timems(now));
971     }
972
973   cbdata.dirmap = solv_free(cbdata.dirmap);
974   cbdata.dirmapn = 0;
975   cbdata.dirmapused = 0;
976   cbdata.cflmap = solv_free(cbdata.cflmap);
977   cbdata.cflmapn = 0;
978   cbdata.cflmapused = 0;
979
980   map_free(&cbdata.idxmap);
981
982   /* sort and unify/prune */
983   now = solv_timems(0);
984   POOL_DEBUG(SOLV_DEBUG_STATS, "raw candidates: %d, pruning\n", cbdata.lookat.count / 4);
985   solv_sort(cbdata.lookat.elements, cbdata.lookat.count / 4, sizeof(Id) * 4, &lookat_hx_cmp, pool);
986   for (i = j = 0; i < cbdata.lookat.count; )
987     {
988       int first = 1;
989       Id hx = cbdata.lookat.elements[i];
990       Id idx = cbdata.lookat.elements[i + 1];
991       Id dhx = cbdata.lookat.elements[i + 2];
992       Id dirid = cbdata.lookat.elements[i + 3];
993       i += 4;
994       for (; i < cbdata.lookat.count && hx == cbdata.lookat.elements[i] && (dirid == cbdata.lookat.elements[i + 3] || dirid == -cbdata.lookat.elements[i + 3]); i += 4)
995         {
996           if (idx == cbdata.lookat.elements[i + 1] && dhx == cbdata.lookat.elements[i + 2])
997             continue;   /* ignore duplicates */
998           if (first)
999             {
1000               if (dirid < 0)
1001                 continue;       /* all have a neg dirid */
1002               cbdata.lookat.elements[j++] = hx;
1003               cbdata.lookat.elements[j++] = idx;
1004               cbdata.lookat.elements[j++] = dhx;
1005               cbdata.lookat.elements[j++] = dirid;
1006               first = 0;
1007             }
1008           idx = cbdata.lookat.elements[i + 1];
1009           dhx = cbdata.lookat.elements[i + 2];
1010           cbdata.lookat.elements[j++] = hx;
1011           cbdata.lookat.elements[j++] = idx;
1012           cbdata.lookat.elements[j++] = dhx;
1013           cbdata.lookat.elements[j++] = dirid;
1014         }
1015     }
1016   queue_truncate(&cbdata.lookat, j);
1017   POOL_DEBUG(SOLV_DEBUG_STATS, "candidates now: %d\n", cbdata.lookat.count / 4);
1018   POOL_DEBUG(SOLV_DEBUG_STATS, "pruning took %d ms\n", solv_timems(now));
1019
1020   /* third pass: collect file info for all files that match a hx */
1021   now = solv_timems(0);
1022   solv_sort(cbdata.lookat.elements, cbdata.lookat.count / 4, sizeof(Id) * 4, &lookat_idx_cmp, pool);
1023   queue_init(&cbdata.files);
1024   hdrfetches = 0;
1025   for (i = 0; i < cbdata.lookat.count; i += 4)
1026     {
1027       Id idx = cbdata.lookat.elements[i + 1];
1028       int iterflags = RPM_ITERATE_FILELIST_WITHMD5 | RPM_ITERATE_FILELIST_NOGHOSTS;
1029       if (obsoleteusescolors)
1030         iterflags |= RPM_ITERATE_FILELIST_WITHCOL;
1031       p = pkgs->elements[idx];
1032       handle = (*handle_cb)(pool, p, handle_cbdata);
1033       if (handle)
1034         hdrfetches++;
1035       for (;; i += 4)
1036         {
1037           int fstart = cbdata.files.count;
1038           queue_push(&cbdata.files, idx);
1039           queue_push(&cbdata.files, 0);
1040           cbdata.idx = idx;
1041           cbdata.hx = cbdata.lookat.elements[i];
1042           cbdata.dirhash = cbdata.lookat.elements[i + 2];
1043           cbdata.dirid = cbdata.lookat.elements[i + 3];
1044           cbdata.lastdiridx = -1;
1045           if (handle)
1046             rpm_iterate_filelist(handle, iterflags, findfileconflicts2_cb, &cbdata);
1047           cbdata.files.elements[fstart + 1] = cbdata.files.count;
1048           cbdata.lookat.elements[i + 1] = fstart;
1049           if (i + 4 >= cbdata.lookat.count || cbdata.lookat.elements[i + 4 + 1] != idx)
1050             break;
1051         }
1052     }
1053   POOL_DEBUG(SOLV_DEBUG_STATS, "header fetches: %d\n", hdrfetches);
1054   POOL_DEBUG(SOLV_DEBUG_STATS, "file info fetching took %d ms\n", solv_timems(now));
1055
1056   cbdata.normap = solv_free(cbdata.normap);
1057   cbdata.normapn = 0;
1058
1059   /* forth pass: for each hx we have, compare all matching files against all other matching files */
1060   now = solv_timems(0);
1061   solv_sort(cbdata.lookat.elements, cbdata.lookat.count / 4, sizeof(Id) * 4, &lookat_hx_cmp, pool);
1062   for (i = 0; i < cbdata.lookat.count - 4; i += 4)
1063     {
1064       Id hx = cbdata.lookat.elements[i];
1065       Id pstart = cbdata.lookat.elements[i + 1];
1066       Id dirid = cbdata.lookat.elements[i + 3];
1067       Id pidx = cbdata.files.elements[pstart];
1068       Id pend = cbdata.files.elements[pstart + 1];
1069       if (cbdata.lookat.elements[i + 4] != hx)
1070         continue;       /* no package left with that hx */
1071       for (j = i + 4; j < cbdata.lookat.count && cbdata.lookat.elements[j] == hx && cbdata.lookat.elements[j + 3] == dirid; j += 4)
1072         {
1073           Id qstart = cbdata.lookat.elements[j + 1];
1074           Id qidx = cbdata.files.elements[qstart];
1075           Id qend = cbdata.files.elements[qstart + 1];
1076           int ii, jj;
1077           if (pidx >= cutoff && qidx >= cutoff)
1078             continue;   /* no conflicts between packages with idx >= cutoff */
1079           for (ii = pstart + 2; ii < pend; ii++)
1080             for (jj = qstart + 2; jj < qend; jj++)
1081               {
1082                 char *fsi = (char *)cbdata.filesspace + cbdata.files.elements[ii];
1083                 char *fsj = (char *)cbdata.filesspace + cbdata.files.elements[jj];
1084                 if (cbdata.aliases)
1085                   {
1086                     /* compare just the basenames, the dirs match because of the dirid */
1087                     char *bsi = strrchr(fsi + 34, '/');
1088                     char *bsj = strrchr(fsj + 34, '/');
1089                     if (!bsi || !bsj)
1090                       continue;
1091                     if (strcmp(bsi, bsj))
1092                       continue; /* different base names */
1093                   }
1094                 else
1095                   {
1096                     if (strcmp(fsi + 34, fsj + 34))
1097                       continue; /* different file names */
1098                   }
1099                 if (!strcmp(fsi, fsj))
1100                   continue;     /* file digests match, no conflict */
1101                 if (obsoleteusescolors && fsi[33] && fsj[33] && (fsi[33] & fsj[33]) == 0)
1102                   continue;     /* colors do not conflict */
1103                 queue_push(conflicts, pool_str2id(pool, fsi + 34, 1));
1104                 queue_push(conflicts, pkgs->elements[pidx]);
1105                 queue_push(conflicts, pool_str2id(pool, fsi, 1));
1106                 queue_push(conflicts, pool_str2id(pool, fsj + 34, 1));
1107                 queue_push(conflicts, pkgs->elements[qidx]);
1108                 queue_push(conflicts, pool_str2id(pool, fsj, 1));
1109               }
1110         }
1111     }
1112   POOL_DEBUG(SOLV_DEBUG_STATS, "filespace size: %d K\n", cbdata.filesspacen / 1024);
1113   POOL_DEBUG(SOLV_DEBUG_STATS, "candidate check took %d ms\n", solv_timems(now));
1114   cbdata.filesspace = solv_free(cbdata.filesspace);
1115   cbdata.filesspacen = 0;
1116   queue_free(&cbdata.lookat);
1117   queue_free(&cbdata.files);
1118   if (conflicts->count > 6)
1119     solv_sort(conflicts->elements, conflicts->count / 6, 6 * sizeof(Id), conflicts_cmp, pool);
1120   POOL_DEBUG(SOLV_DEBUG_STATS, "found %d file conflicts\n", conflicts->count / 6);
1121   POOL_DEBUG(SOLV_DEBUG_STATS, "file conflict detection took %d ms\n", solv_timems(start));
1122
1123   return conflicts->count / 6;
1124 }
1125