cb55d58acd53cafa4f8df56dc76c4c532e92818a
[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 <unistd.h>
11
12 #include "pool.h"
13 #include "repo.h"
14 #include "hash.h"
15 #include "repo_rpmdb.h"
16 #include "pool_fileconflicts.h"
17
18 struct cbdata {
19   Pool *pool;
20   int create;
21   int aliases;
22
23   Queue lookat;         /* conflict candidates */
24   Queue lookat_dir;     /* not yet conflicting directories */
25
26   Hashtable cflmap;
27   Hashval cflmapn;
28   unsigned int cflmapused;
29
30   Hashtable dirmap;
31   Hashval dirmapn;
32   unsigned int dirmapused;
33   int dirconflicts;
34
35   Map idxmap;
36
37   unsigned int lastdiridx;      /* last diridx we have seen */
38   unsigned int lastdirhash;     /* strhash of last dir we have seen */
39
40   Id idx;       /* index of package we're looking at */
41   Id hx;        /* used in findfileconflicts2_cb, limit to files matching hx */
42
43   Id dirid;     /* used in findfileconflicts2_cb, limit to dirs matching dirid */
44   Id dirhash;   /* used in findfileconflicts2_cb, limit to dirs matching dirhash */
45
46   Queue files;
47   unsigned char *filesspace;
48   unsigned int filesspacen;
49
50   Hashtable normap;
51   Hashval normapn;
52   unsigned int normapused;
53   Queue norq;
54
55   Hashtable statmap;
56   Hashval statmapn;
57   unsigned int statmapused;
58
59   int usestat;
60   int statsmade;
61
62   const char *rootdir;
63   int rootdirl;
64
65   char *canonspace;
66   int canonspacen;
67 };
68
69 #define FILESSPACE_BLOCK 255
70
71 static Hashtable
72 growhash(Hashtable map, Hashval *mapnp)
73 {
74   Hashval mapn = *mapnp;
75   Hashval newn = (mapn + 1) * 2 - 1;
76   Hashval i, h, hh;
77   Hashtable m;
78   Id hx, qx;
79
80   m = solv_calloc(newn + 1, 2 * sizeof(Id));
81   for (i = 0; i <= mapn; i++)
82     {
83       hx = map[2 * i];
84       if (!hx)
85         continue;
86       h = hx & newn;
87       hh = HASHCHAIN_START;
88       for (;;)
89         {
90           qx = m[2 * h];
91           if (!qx)
92             break;
93           h = HASHCHAIN_NEXT(h, hh, newn);
94         }
95       m[2 * h] = hx;
96       m[2 * h + 1] = map[2 * i + 1];
97     }
98   solv_free(map);
99   *mapnp = newn;
100   return m;
101 }
102
103 static void
104 finddirs_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
105 {
106   struct cbdata *cbdata = cbdatav;
107   Hashval h, hh;
108   Id hx, qx;
109   Id oidx, idx = cbdata->idx;
110
111   hx = strhash(fn);
112   if (!hx)
113     hx = strlen(fn) + 1;
114   h = hx & cbdata->dirmapn;
115   hh = HASHCHAIN_START;
116   for (;;)
117     {
118       qx = cbdata->dirmap[2 * h];
119       if (!qx)
120         break;
121       if (qx == hx)
122         break;
123       h = HASHCHAIN_NEXT(h, hh, cbdata->dirmapn);
124     }
125   if (!qx)
126     {
127       /* a miss */
128       if (!cbdata->create)
129         return;
130       cbdata->dirmap[2 * h] = hx;
131       cbdata->dirmap[2 * h + 1] = idx;
132       if (++cbdata->dirmapused * 2 > cbdata->dirmapn)
133         cbdata->dirmap = growhash(cbdata->dirmap, &cbdata->dirmapn);
134       return;
135     }
136   oidx = cbdata->dirmap[2 * h + 1];
137   if (oidx == idx)
138     return;
139   /* found a conflict, this dir may be used in multiple packages */
140   if (oidx != -1)
141     {
142       MAPSET(&cbdata->idxmap, oidx);
143       cbdata->dirmap[2 * h + 1] = -1;
144       cbdata->dirconflicts++;
145     }
146   MAPSET(&cbdata->idxmap, idx);
147 }
148
149 static inline int
150 isindirmap(struct cbdata *cbdata, Id hx)
151 {
152   Hashval h, hh;
153   Id qx;
154
155   h = hx & cbdata->dirmapn;
156   hh = HASHCHAIN_START;
157   for (;;)
158     {
159       qx = cbdata->dirmap[2 * h];
160       if (!qx)
161         return 0;
162       if (qx == hx)
163         return cbdata->dirmap[2 * h + 1] == -1 ? 1 : 0;
164       h = HASHCHAIN_NEXT(h, hh, cbdata->dirmapn);
165     }
166 }
167
168 static void
169 findfileconflicts_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
170 {
171   struct cbdata *cbdata = cbdatav;
172   int isdir = S_ISDIR(info->mode);
173   const char *dp;
174   Id idx, oidx;
175   Id hx, qx;
176   Hashval h, hh, dhx;
177
178   idx = cbdata->idx;
179
180   if (!info->dirlen)
181     return;
182   dp = fn + info->dirlen;
183   if (info->diridx != cbdata->lastdiridx)
184     {
185       cbdata->lastdiridx = info->diridx;
186       cbdata->lastdirhash = strnhash(fn, dp - fn);
187     }
188   dhx = cbdata->lastdirhash;
189   /* this mirrors the "if (!hx) hx = strlen(fn) + 1" in finddirs_cb */
190   if (!isindirmap(cbdata, dhx ? dhx : dp - fn + 1))
191     return;
192   hx = strhash_cont(dp, dhx);
193   if (!hx)
194     hx = strlen(fn) + 1;
195
196   h = hx & cbdata->cflmapn;
197   hh = HASHCHAIN_START;
198   for (;;)
199     {
200       qx = cbdata->cflmap[2 * h];
201       if (!qx)
202         break;
203       if (qx == hx)
204         break;
205       h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
206     }
207   if (!qx)
208     {
209       /* a miss */
210       if (!cbdata->create)
211         return;
212       cbdata->cflmap[2 * h] = hx;
213       cbdata->cflmap[2 * h + 1] = (isdir ? ~idx : idx);
214       if (++cbdata->cflmapused * 2 > cbdata->cflmapn)
215         cbdata->cflmap = growhash(cbdata->cflmap, &cbdata->cflmapn);
216       return;
217     }
218   oidx = cbdata->cflmap[2 * h + 1];
219   if (oidx < 0)
220     {
221       int i;
222       if (isdir)
223         {
224           /* both are directories. delay the conflict, keep oidx in slot */
225           queue_push2(&cbdata->lookat_dir, hx, idx);
226           return;
227         }
228       oidx = ~oidx;
229       /* now have file, had directories before. */
230       cbdata->cflmap[2 * h + 1] = oidx; /* make it a file */
231       /* dump all delayed directory hits for hx */
232       for (i = 0; i < cbdata->lookat_dir.count; i += 2)
233         if (cbdata->lookat_dir.elements[i] == hx)
234           {
235             queue_push2(&cbdata->lookat, hx, cbdata->lookat_dir.elements[i + 1]);
236             queue_push2(&cbdata->lookat, 0, 0);
237           }
238     }
239   else if (oidx == idx)
240     return;     /* no conflicts with ourself, please */
241   queue_push2(&cbdata->lookat, hx, oidx);
242   queue_push2(&cbdata->lookat, 0, 0);
243   queue_push2(&cbdata->lookat, hx, idx);
244   queue_push2(&cbdata->lookat, 0, 0);
245 }
246
247 /* same as findfileconflicts_cb, but
248  * - hashes with just the basename
249  * - sets idx in a map instead of pushing to lookat
250  * - sets the hash element to -1 if there may be a conflict
251  */
252 static void
253 findfileconflicts_basename_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
254 {
255   struct cbdata *cbdata = cbdatav;
256   int isdir = S_ISDIR(info->mode);
257   const char *dp;
258   Id idx, oidx;
259   Id hx, qx;
260   Hashval h, hh;
261
262   idx = cbdata->idx;
263
264   if (!info->dirlen)
265     return;
266   dp = fn + info->dirlen;
267   hx = strhash(dp);
268   if (!hx)
269     hx = strlen(fn) + 1;
270
271   h = hx & cbdata->cflmapn;
272   hh = HASHCHAIN_START;
273   for (;;)
274     {
275       qx = cbdata->cflmap[2 * h];
276       if (!qx)
277         break;
278       if (qx == hx)
279         break;
280       h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
281     }
282   if (!qx)
283     {
284       /* a miss */
285       if (!cbdata->create)
286         return;
287       cbdata->cflmap[2 * h] = hx;
288       cbdata->cflmap[2 * h + 1] = (isdir ? -idx - 2 : idx);
289       if (++cbdata->cflmapused * 2 > cbdata->cflmapn)
290         cbdata->cflmap = growhash(cbdata->cflmap, &cbdata->cflmapn);
291       return;
292     }
293   oidx = cbdata->cflmap[2 * h + 1];
294   if (oidx < -1)
295     {
296       int i;
297       if (isdir)
298         {
299           /* both are directories. delay the conflict, keep oidx in slot */
300           queue_push2(&cbdata->lookat_dir, hx, idx);
301           return;
302         }
303       oidx = -idx - 2;
304       /* now have file, had directories before. */
305       cbdata->cflmap[2 * h + 1] = oidx; /* make it a file */
306       /* dump all delayed directory hits for hx */
307       for (i = 0; i < cbdata->lookat_dir.count; i += 2)
308         if (cbdata->lookat_dir.elements[i] == hx)
309           MAPSET(&cbdata->idxmap, cbdata->lookat_dir.elements[i + 1]);
310     }
311   else if (oidx == idx)
312     return;     /* no conflicts with ourself, please */
313   if (oidx >= 0)
314     MAPSET(&cbdata->idxmap, oidx);
315   MAPSET(&cbdata->idxmap, idx);
316   if (oidx != -1)
317     cbdata->cflmap[2 * h + 1] = -1;
318 }
319
320 static inline Id
321 addfilesspace(struct cbdata *cbdata, int len)
322 {
323   unsigned int off = cbdata->filesspacen;
324   cbdata->filesspace = solv_extend(cbdata->filesspace, cbdata->filesspacen, len, 1, FILESSPACE_BLOCK);
325   cbdata->filesspacen += len;
326   return off;
327 }
328
329 static Id
330 unifywithstat(struct cbdata *cbdata, Id diroff, int dirl)
331 {
332   struct stat stb;
333   int i;
334   Hashval h, hh;
335   Id hx, qx;
336   Id nspaceoff;
337   unsigned char statdata[16 + sizeof(stb.st_dev) + sizeof(stb.st_ino)];
338
339   if (dirl > 1 && cbdata->filesspace[diroff + dirl - 1] == '/')
340     cbdata->filesspace[diroff + dirl - 1] = 0;
341   cbdata->statsmade++;
342   i = stat((char *)cbdata->filesspace + diroff, &stb);
343   if (dirl > 1 && cbdata->filesspace[diroff + dirl - 1] == 0)
344     cbdata->filesspace[diroff + dirl - 1] = '/';
345   if (i)
346     return diroff;
347   memset(statdata, 0, 16);
348   memcpy(statdata + 8, &stb.st_dev, sizeof(stb.st_dev));
349   memcpy(statdata, &stb.st_ino, sizeof(stb.st_ino));
350   hx = 0;
351   for (i = 15; i >= 0; i--)
352     hx = (unsigned int)hx * 13 + statdata[i];
353   h = hx & cbdata->statmapn;
354   hh = HASHCHAIN_START;
355   for (;;)
356     {
357       qx = cbdata->statmap[2 * h];
358       if (!qx)
359         break;
360       if (qx == hx)
361         {
362           Id off = cbdata->statmap[2 * h + 1];
363           char *dp = (char *)cbdata->filesspace + cbdata->norq.elements[off];
364           if (!memcmp(dp, statdata, 16))
365             return cbdata->norq.elements[off + 1];
366         }
367       h = HASHCHAIN_NEXT(h, hh, cbdata->statmapn);
368     }
369   /* new stat result. work. */
370   nspaceoff = addfilesspace(cbdata, 16);
371   memcpy(cbdata->filesspace + nspaceoff, statdata, 16);
372   queue_push2(&cbdata->norq, nspaceoff, nspaceoff);
373   cbdata->statmap[2 * h] = hx;
374   cbdata->statmap[2 * h + 1] = cbdata->norq.count - 2;
375   if (++cbdata->statmapused * 2 > cbdata->statmapn)
376     cbdata->statmap = growhash(cbdata->statmap, &cbdata->statmapn);
377   return nspaceoff;
378 }
379
380 /* forward declaration */
381 static Id normalizedir(struct cbdata *cbdata, const char *dir, int dirl, Id hx, int create);
382
383 static Id
384 unifywithcanon(struct cbdata *cbdata, Id diroff, int dirl)
385 {
386   Id dirnameid;
387   int i, l, ll, lo;
388   struct stat stb;
389
390 #if 0
391   printf("UNIFY %.*s\n", dirl, (char *)cbdata->filesspace + diroff);
392 #endif
393   if (!dirl || cbdata->filesspace[diroff] != '/')
394     return diroff;
395   /* strip / at end*/
396   while (dirl && cbdata->filesspace[diroff + dirl - 1] == '/')
397     dirl--;
398   if (!dirl)
399     return diroff;
400
401   /* find dirname */
402   for (i = dirl - 1; i > 0; i--)
403     if (cbdata->filesspace[diroff + i] == '/')
404       break;
405   i++;                          /* include trailing / */
406
407   /* normalize dirname */
408   dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + diroff, i, strnhash((char *)cbdata->filesspace + diroff, i), 1);
409   if (dirnameid == -1)
410     return diroff;              /* hit "in progress" marker, some cyclic link */
411
412   /* sanity check result */
413   if (cbdata->filesspace[dirnameid] != '/')
414     return diroff;              /* hmm */
415   l = strlen((char *)cbdata->filesspace + dirnameid);
416   if (l && cbdata->filesspace[dirnameid + l - 1] != '/')
417     return diroff;              /* hmm */
418
419   /* special handling for "." and ".." basename */
420   if (cbdata->filesspace[diroff + i] == '.')
421     {
422       if (dirl - i == 1)
423         return dirnameid;
424       if (dirl - i == 2 && cbdata->filesspace[diroff + i + 1] == '.')
425         {
426           if (l <= 2)
427             return dirnameid;   /* we hit our root */
428           for (i = l - 2; i > 0; i--)
429             if (cbdata->filesspace[dirnameid + i] == '/')
430               break;
431           i++;  /* include trailing / */
432           dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + dirnameid, i, strnhash((char *)cbdata->filesspace + dirnameid, i), 1);
433           return dirnameid == -1 ? diroff : dirnameid;
434         }
435     }
436
437   /* append basename to normalized dirname */
438   if (cbdata->rootdirl + l + dirl - i + 1 > cbdata->canonspacen)
439     {
440       cbdata->canonspacen = cbdata->rootdirl + l + dirl - i + 20;
441       cbdata->canonspace = solv_realloc(cbdata->canonspace, cbdata->canonspacen);
442       strcpy(cbdata->canonspace, cbdata->rootdir);
443     }
444   strcpy(cbdata->canonspace + cbdata->rootdirl, (char *)cbdata->filesspace + dirnameid);
445   strncpy(cbdata->canonspace + cbdata->rootdirl + l, (char *)cbdata->filesspace + diroff + i, dirl - i);
446   cbdata->canonspace[cbdata->rootdirl + l + dirl - i] = 0;
447
448 #if 0
449   printf("stat()ing %s\n", cbdata->canonspace);
450 #endif
451   cbdata->statsmade++;
452   if (lstat(cbdata->canonspace, &stb) != 0 || !S_ISLNK(stb.st_mode))
453     {
454       /* not a symlink or stat failed, have new canon entry */
455       diroff = addfilesspace(cbdata, l + dirl - i + 2);
456       strcpy((char *)cbdata->filesspace + diroff, cbdata->canonspace + cbdata->rootdirl);
457       l += dirl - i;
458       /* add trailing / */
459       if (cbdata->filesspace[diroff + l - 1] != '/')
460         {
461           cbdata->filesspace[diroff + l++] = '/';
462           cbdata->filesspace[diroff + l] = 0;
463         }
464       /* call normalizedir on new entry for unification purposes */
465       dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + diroff, l, strnhash((char *)cbdata->filesspace + diroff, l), 1);
466       return dirnameid == -1 ? diroff : dirnameid;
467     }
468   /* oh no, a symlink! follow */
469   lo = cbdata->rootdirl + l + dirl - i + 1;
470   if (lo + stb.st_size + 2 > cbdata->canonspacen)
471     {
472       cbdata->canonspacen = lo + stb.st_size + 20;
473       cbdata->canonspace = solv_realloc(cbdata->canonspace, cbdata->canonspacen);
474     }
475   ll = readlink(cbdata->canonspace, cbdata->canonspace + lo, stb.st_size);
476   if (ll < 0 || ll > stb.st_size)
477     return diroff;              /* hmm */
478   if (ll == 0)
479     return dirnameid;           /* empty means current dir */
480   if (cbdata->canonspace[lo + ll - 1] != '/')
481     cbdata->canonspace[lo + ll++] = '/';        /* add trailing / */
482   cbdata->canonspace[lo + ll] = 0;              /* zero terminate */
483   if (cbdata->canonspace[lo] != '/')
484     {
485       /* relative link, concatenate to dirname */
486       memmove(cbdata->canonspace + cbdata->rootdirl + l, cbdata->canonspace + lo, ll + 1);
487       lo = cbdata->rootdirl;
488       ll += l;
489     }
490   dirnameid = normalizedir(cbdata, cbdata->canonspace + lo, ll, strnhash(cbdata->canonspace + lo, ll), 1);
491   return dirnameid == -1 ? diroff : dirnameid;
492 }
493
494 /*
495  * map a directory (containing a trailing /) into a number.
496  * for unifywithstat this is the offset to the 16 byte stat result.
497  * for unifywithcanon this is the offset to the normailzed dir.
498  */
499 static Id
500 normalizedir(struct cbdata *cbdata, const char *dir, int dirl, Id hx, int create)
501 {
502   Hashval h, hh;
503   Id qx;
504   Id nspaceoff;
505   int mycnt;
506
507   if (!hx)
508     hx = dirl + 1;
509   h = hx & cbdata->normapn;
510   hh = HASHCHAIN_START;
511   for (;;)
512     {
513       qx = cbdata->normap[2 * h];
514       if (!qx)
515         break;
516       if (qx == hx)
517         {
518           Id off = cbdata->normap[2 * h + 1];
519           char *dp = (char *)cbdata->filesspace + cbdata->norq.elements[off];
520           if (!strncmp(dp, dir, dirl) && dp[dirl] == 0)
521             return cbdata->norq.elements[off + 1];
522         }
523       h = HASHCHAIN_NEXT(h, hh, cbdata->normapn);
524     }
525   if (!create)
526     return 0;
527   /* new dir. work. */
528   if (dir >= (const char *)cbdata->filesspace && dir < (const char *)cbdata->filesspace + cbdata->filesspacen)
529     {
530       /* can happen when called from unifywithcanon */
531       Id off = dir - (const char *)cbdata->filesspace;
532       nspaceoff = addfilesspace(cbdata, dirl + 1);
533       dir = (const char *)cbdata->filesspace + off;
534     }
535   else
536     nspaceoff = addfilesspace(cbdata, dirl + 1);
537   if (dirl)
538     memcpy(cbdata->filesspace + nspaceoff, dir, dirl);
539   cbdata->filesspace[nspaceoff + dirl] = 0;
540   mycnt = cbdata->norq.count;
541   queue_push2(&cbdata->norq, nspaceoff, -1);    /* -1: in progress */
542   cbdata->normap[2 * h] = hx;
543   cbdata->normap[2 * h + 1] = mycnt;
544   if (++cbdata->normapused * 2 > cbdata->normapn)
545     cbdata->normap = growhash(cbdata->normap, &cbdata->normapn);
546   /* unify */
547   if (cbdata->usestat)
548     nspaceoff = unifywithstat(cbdata, nspaceoff, dirl);
549   else
550     nspaceoff = unifywithcanon(cbdata, nspaceoff, dirl);
551   cbdata->norq.elements[mycnt + 1] = nspaceoff; /* patch in result */
552 #if 0
553   if (!cbdata->usestat)
554     printf("%s normalized to %d: %s\n", cbdata->filesspace + cbdata->norq.elements[mycnt], nspaceoff, cbdata->filesspace + nspaceoff);
555 #endif
556   return nspaceoff;
557 }
558
559 static void
560 findfileconflicts_alias_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
561 {
562   int isdir = S_ISDIR(info->mode);
563   struct cbdata *cbdata = cbdatav;
564   const char *dp;
565   Id idx, dirid;
566   Id hx, qx;
567   Hashval h, hh;
568
569   idx = cbdata->idx;
570
571   if (!info->dirlen)
572     return;
573   dp = fn + info->dirlen;
574   if (info->diridx != cbdata->lastdiridx)
575     {
576       cbdata->lastdiridx = info->diridx;
577       cbdata->lastdirhash = 0;
578     }
579   dp = fn + info->dirlen;
580   hx = strhash(dp);
581   if (!hx)
582     hx = strlen(fn) + 1;
583
584   h = hx & cbdata->cflmapn;
585   hh = HASHCHAIN_START;
586   for (;;)
587     {
588       qx = cbdata->cflmap[2 * h];
589       if (!qx)
590         break;
591       if (qx == hx)
592         break;
593       h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
594     }
595   if (!qx || cbdata->cflmap[2 * h + 1] != -1)
596     return;
597   if (!cbdata->lastdirhash)
598     cbdata->lastdirhash = strnhash(fn, dp - fn);
599   dirid = normalizedir(cbdata, fn, dp - fn, cbdata->lastdirhash, 1);
600   queue_push2(&cbdata->lookat, hx, idx);
601   queue_push2(&cbdata->lookat, cbdata->lastdirhash, isdir ? -dirid : dirid);
602 }
603
604 static void
605 findfileconflicts2_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
606 {
607   struct cbdata *cbdata = cbdatav;
608   Hashval hx;
609   const char *dp;
610   char md5padded[34];
611   Id off;
612
613   if (!info->dirlen)
614     return;
615   dp = fn + info->dirlen;
616   if (info->diridx != cbdata->lastdiridx)
617     {
618       cbdata->lastdiridx = info->diridx;
619       cbdata->lastdirhash = strnhash(fn, dp - fn);
620     }
621   if (cbdata->aliases)
622     {
623       if (cbdata->lastdirhash != cbdata->dirhash)
624         return;
625       hx = strhash(dp);
626     }
627   else
628     {
629       hx = cbdata->lastdirhash;
630       hx = strhash_cont(dp, hx);
631     }
632   if (!hx)
633     hx = strlen(fn) + 1;
634   if ((Id)hx != cbdata->hx)
635     return;
636   if (cbdata->dirid && cbdata->dirid != normalizedir(cbdata, fn, dp - fn, cbdata->dirhash, 0))
637     return;
638   strncpy(md5padded, info->digest, 32);
639   md5padded[32] = 0;
640   md5padded[33] = info->color;
641   /* printf("%d, hx %x -> %s   %d %s %d\n", cbdata->idx, hx, fn, info->mode, info->digest, info->color); */
642   off = addfilesspace(cbdata, strlen(fn) + (34 + 1));
643   memcpy(cbdata->filesspace + off, (unsigned char *)md5padded, 34);
644   strcpy((char *)cbdata->filesspace + off + 34, fn);
645   queue_push(&cbdata->files, off);
646 }
647
648 static int
649 lookat_idx_cmp(const void *ap, const void *bp, void *dp)
650 {
651   const Id *a = ap, *b = bp;
652   unsigned int ahx, bhx;
653   if (a[1] - b[1] != 0)         /* idx */
654     return a[1] - b[1];
655   if (a[3] - b[3] != 0)         /* dirid */
656     return a[3] - b[3];
657   ahx = (unsigned int)a[0];     /* can be < 0 */
658   bhx = (unsigned int)b[0];
659   if (ahx != bhx)
660     return ahx < bhx ? -1 : 1;
661   ahx = (unsigned int)a[2];     /* dhx */
662   bhx = (unsigned int)b[2];
663   if (ahx != bhx)
664     return ahx < bhx ? -1 : 1;
665   return 0;
666 }
667
668 static int
669 lookat_hx_cmp(const void *ap, const void *bp, void *dp)
670 {
671   const Id *a = ap, *b = bp;
672   unsigned int ahx, bhx;
673   Id adirid, bdirid;
674   ahx = (unsigned int)a[0];     /* can be < 0 */
675   bhx = (unsigned int)b[0];
676   if (ahx != bhx)
677     return ahx < bhx ? -1 : 1;
678   adirid = a[3] < 0 ? -a[3] : a[3];
679   bdirid = b[3] < 0 ? -b[3] : b[3];
680   if (adirid - bdirid != 0)     /* dirid */
681     return adirid - bdirid;
682   if (a[3] != b[3])
683     return a[3] > 0 ? -1 : 1;   /* bring positive dirids to front */
684   if (a[1] - b[1] != 0)         /* idx */
685     return a[1] - b[1];
686   ahx = (unsigned int)a[2];     /* dhx */
687   bhx = (unsigned int)b[2];
688   if (ahx != bhx)
689     return ahx < bhx ? -1 : 1;
690   return 0;
691 }
692
693 static int
694 conflicts_cmp(const void *ap, const void *bp, void *dp)
695 {
696   Pool *pool = dp;
697   const Id *a = ap;
698   const Id *b = bp;
699   if (a[0] != b[0])     /* filename1 */
700     return strcmp(pool_id2str(pool, a[0]), pool_id2str(pool, b[0]));
701   if (a[3] != b[3])     /* filename2 */
702     return strcmp(pool_id2str(pool, a[3]), pool_id2str(pool, b[3]));
703   if (a[1] != b[1])     /* pkgid1 */
704     return a[1] - b[1];
705   if (a[4] != b[4])     /* pkgid2 */
706     return a[4] - b[4];
707   return 0;
708 }
709
710 static void
711 iterate_solvable_dirs(Pool *pool, Id p, void (*cb)(void *, const char *, struct filelistinfo *), void *cbdata)
712 {
713   Repodata *lastdata = 0;
714   Id lastdirid = -1;
715   Dataiterator di;
716
717   dataiterator_init(&di, pool, 0, p, SOLVABLE_FILELIST, 0, SEARCH_COMPLETE_FILELIST);
718   while (dataiterator_step(&di))
719     {
720       if (di.data == lastdata && di.kv.id == lastdirid)
721         continue;
722       lastdata = di.data;
723       lastdirid = di.kv.id;
724       cb(cbdata, repodata_dir2str(di.data, di.kv.id, ""), 0);
725     }
726   dataiterator_free(&di);
727 }
728
729 /* before calling the expensive findfileconflicts_cb we check if any of
730  * the files match. This only makes sense when cbdata->create is off.
731  */
732 static int
733 precheck_solvable_files(struct cbdata *cbdata, Pool *pool, Id p)
734 {
735   Dataiterator di;
736   Id hx, qx;
737   Hashval h, hh;
738   int found = 0;
739   int aliases = cbdata->aliases;
740   unsigned int lastdirid = -1;
741   Hashval lastdirhash = 0;
742   int lastdirlen = 0;
743   int checkthisdir = 0;
744   Repodata *lastrepodata = 0;
745
746   dataiterator_init(&di, pool, 0, p, SOLVABLE_FILELIST, 0, SEARCH_COMPLETE_FILELIST);
747   while (dataiterator_step(&di))
748     {
749       if (aliases)
750         {
751           /* hash just the basename */
752           hx = strhash(di.kv.str);
753           if (!hx)
754             hx = strlen(di.kv.str) + 1;
755         }
756       else
757         {
758           /* hash the full path */
759           if (di.data != lastrepodata || di.kv.id != lastdirid)
760             {
761               const char *dir;
762               lastrepodata = di.data;
763               lastdirid = di.kv.id;
764               dir = repodata_dir2str(lastrepodata, lastdirid, "");
765               lastdirlen = strlen(dir);
766               lastdirhash = strhash(dir);
767               checkthisdir =  isindirmap(cbdata, lastdirhash ? lastdirhash : lastdirlen + 1);
768             }
769           if (!checkthisdir)
770             continue;
771           hx = strhash_cont(di.kv.str, lastdirhash);
772           if (!hx)
773             hx = lastdirlen + strlen(di.kv.str) + 1;
774         }
775       h = hx & cbdata->cflmapn;
776       hh = HASHCHAIN_START;
777       for (;;)
778         {
779           qx = cbdata->cflmap[2 * h];
780           if (!qx)
781             break;
782           if (qx == hx)
783             {
784               found = 1;
785               break;
786             }
787           h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
788         }
789       if (found)
790         break;
791     }
792   dataiterator_free(&di);
793   return found;
794 }
795
796
797 int
798 pool_findfileconflicts(Pool *pool, Queue *pkgs, int cutoff, Queue *conflicts, int flags, void *(*handle_cb)(Pool *, Id, void *) , void *handle_cbdata)
799 {
800   int i, j, cflmapn, idxmapset;
801   struct cbdata cbdata;
802   unsigned int now, start;
803   void *handle;
804   Repo *installed = pool->installed;
805   Id p;
806   int usefilecolors;
807   int hdrfetches;
808
809   queue_empty(conflicts);
810   if (!pkgs->count)
811     return 0;
812
813   now = start = solv_timems(0);
814   /* Hmm, should we have a different flag for this? */
815   usefilecolors = pool_get_flag(pool, POOL_FLAG_IMPLICITOBSOLETEUSESCOLORS);
816   POOL_DEBUG(SOLV_DEBUG_STATS, "searching for file conflicts\n");
817   POOL_DEBUG(SOLV_DEBUG_STATS, "packages: %d, cutoff %d, usefilecolors %d\n", pkgs->count, cutoff, usefilecolors);
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 (usefilecolors)
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 (usefilecolors && 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