+ return off;
+}
+
+static Id
+unifywithstat(struct cbdata *cbdata, Id diroff, int dirl)
+{
+ struct stat stb;
+ int i;
+ Hashval h, hh;
+ Id hx, qx;
+ Id nspaceoff;
+ unsigned char statdata[16 + sizeof(stb.st_dev) + sizeof(stb.st_ino)];
+
+ if (dirl > 1 && cbdata->filesspace[diroff + dirl - 1] == '/')
+ cbdata->filesspace[diroff + dirl - 1] = 0;
+ cbdata->statsmade++;
+ i = stat((char *)cbdata->filesspace + diroff, &stb);
+ if (dirl > 1 && cbdata->filesspace[diroff + dirl - 1] == 0)
+ cbdata->filesspace[diroff + dirl - 1] = '/';
+ if (i)
+ return diroff;
+ memset(statdata, 0, 16);
+ memcpy(statdata + 8, &stb.st_dev, sizeof(stb.st_dev));
+ memcpy(statdata, &stb.st_ino, sizeof(stb.st_ino));
+ hx = 0;
+ for (i = 15; i >= 0; i--)
+ hx = (unsigned int)hx * 13 + statdata[i];
+ h = hx & cbdata->statmapn;
+ hh = HASHCHAIN_START;
+ for (;;)
+ {
+ qx = cbdata->statmap[2 * h];
+ if (!qx)
+ break;
+ if (qx == hx)
+ {
+ Id off = cbdata->statmap[2 * h + 1];
+ char *dp = (char *)cbdata->filesspace + cbdata->norq.elements[off];
+ if (!memcmp(dp, statdata, 16))
+ return cbdata->norq.elements[off + 1];
+ }
+ h = HASHCHAIN_NEXT(h, hh, cbdata->statmapn);
+ }
+ /* new stat result. work. */
+ nspaceoff = addfilesspace(cbdata, 16);
+ memcpy(cbdata->filesspace + nspaceoff, statdata, 16);
+ queue_push2(&cbdata->norq, nspaceoff, nspaceoff);
+ cbdata->statmap[2 * h] = hx;
+ cbdata->statmap[2 * h + 1] = cbdata->norq.count - 2;
+ if (++cbdata->statmapused * 2 > cbdata->statmapn)
+ cbdata->statmap = growhash(cbdata->statmap, &cbdata->statmapn);
+ return nspaceoff;
+}
+
+/* forward declaration */
+static Id normalizedir(struct cbdata *cbdata, const char *dir, int dirl, Id hx, int create);
+
+static Id
+unifywithcanon(struct cbdata *cbdata, Id diroff, int dirl)
+{
+ Id dirnameid;
+ int i, l, ll, lo;
+ struct stat stb;
+
+#if 0
+ printf("UNIFY %.*s\n", dirl, (char *)cbdata->filesspace + diroff);
+#endif
+ if (!dirl || cbdata->filesspace[diroff] != '/')
+ return diroff;
+ /* strip / at end*/
+ while (dirl && cbdata->filesspace[diroff + dirl - 1] == '/')
+ dirl--;
+ if (!dirl)
+ return diroff;
+
+ /* find dirname */
+ for (i = dirl - 1; i > 0; i--)
+ if (cbdata->filesspace[diroff + i] == '/')
+ break;
+ i++; /* include trailing / */
+
+ /* normalize dirname */
+ dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + diroff, i, strnhash((char *)cbdata->filesspace + diroff, i), 1);
+ if (dirnameid == -1)
+ return diroff; /* hit "in progress" marker, some cyclic link */
+
+ /* sanity check result */
+ if (cbdata->filesspace[dirnameid] != '/')
+ return diroff; /* hmm */
+ l = strlen((char *)cbdata->filesspace + dirnameid);
+ if (l && cbdata->filesspace[dirnameid + l - 1] != '/')
+ return diroff; /* hmm */
+
+ /* special handling for "." and ".." basename */
+ if (cbdata->filesspace[diroff + i] == '.')
+ {
+ if (dirl - i == 1)
+ return dirnameid;
+ if (dirl - i == 2 && cbdata->filesspace[diroff + i + 1] == '.')
+ {
+ if (l <= 2)
+ return dirnameid; /* we hit our root */
+ for (i = l - 2; i > 0; i--)
+ if (cbdata->filesspace[dirnameid + i] == '/')
+ break;
+ i++; /* include trailing / */
+ dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + dirnameid, i, strnhash((char *)cbdata->filesspace + dirnameid, i), 1);
+ return dirnameid == -1 ? diroff : dirnameid;
+ }
+ }
+
+ /* append basename to normalized dirname */
+ if (cbdata->rootdirl + l + dirl - i + 1 > cbdata->canonspacen)
+ {
+ cbdata->canonspacen = cbdata->rootdirl + l + dirl - i + 20;
+ cbdata->canonspace = solv_realloc(cbdata->canonspace, cbdata->canonspacen);
+ strcpy(cbdata->canonspace, cbdata->rootdir);
+ }
+ strcpy(cbdata->canonspace + cbdata->rootdirl, (char *)cbdata->filesspace + dirnameid);
+ strncpy(cbdata->canonspace + cbdata->rootdirl + l, (char *)cbdata->filesspace + diroff + i, dirl - i);
+ cbdata->canonspace[cbdata->rootdirl + l + dirl - i] = 0;
+
+#if 0
+ printf("stat()ing %s\n", cbdata->canonspace);
+#endif
+ cbdata->statsmade++;
+ if (lstat(cbdata->canonspace, &stb) != 0 || !S_ISLNK(stb.st_mode))
+ {
+ /* not a symlink or stat failed, have new canon entry */
+ diroff = addfilesspace(cbdata, l + dirl - i + 2);
+ strcpy((char *)cbdata->filesspace + diroff, cbdata->canonspace + cbdata->rootdirl);
+ l += dirl - i;
+ /* add trailing / */
+ if (cbdata->filesspace[diroff + l - 1] != '/')
+ {
+ cbdata->filesspace[diroff + l++] = '/';
+ cbdata->filesspace[diroff + l] = 0;
+ }
+ /* call normalizedir on new entry for unification purposes */
+ dirnameid = normalizedir(cbdata, (char *)cbdata->filesspace + diroff, l, strnhash((char *)cbdata->filesspace + diroff, l), 1);
+ return dirnameid == -1 ? diroff : dirnameid;
+ }
+ /* oh no, a symlink! follow */
+ lo = cbdata->rootdirl + l + dirl - i + 1;
+ if (lo + stb.st_size + 2 > cbdata->canonspacen)
+ {
+ cbdata->canonspacen = lo + stb.st_size + 20;
+ cbdata->canonspace = solv_realloc(cbdata->canonspace, cbdata->canonspacen);
+ }
+ ll = readlink(cbdata->canonspace, cbdata->canonspace + lo, stb.st_size);
+ if (ll < 0 || ll > stb.st_size)
+ return diroff; /* hmm */
+ if (ll == 0)
+ return dirnameid; /* empty means current dir */
+ if (cbdata->canonspace[lo + ll - 1] != '/')
+ cbdata->canonspace[lo + ll++] = '/'; /* add trailing / */
+ cbdata->canonspace[lo + ll] = 0; /* zero terminate */
+ if (cbdata->canonspace[lo] != '/')
+ {
+ /* relative link, concatenate to dirname */
+ memmove(cbdata->canonspace + cbdata->rootdirl + l, cbdata->canonspace + lo, ll + 1);
+ lo = cbdata->rootdirl;
+ ll += l;
+ }
+ dirnameid = normalizedir(cbdata, cbdata->canonspace + lo, ll, strnhash(cbdata->canonspace + lo, ll), 1);
+ return dirnameid == -1 ? diroff : dirnameid;
+}
+
+/*
+ * map a directory (containing a trailing /) into a number.
+ * for unifywithstat this is the offset to the 16 byte stat result.
+ * for unifywithcanon this is the offset to the normailzed dir.
+ */
+static Id
+normalizedir(struct cbdata *cbdata, const char *dir, int dirl, Id hx, int create)
+{
+ Hashval h, hh;
+ Id qx;
+ Id nspaceoff;
+ int mycnt;
+
+ if (!hx)
+ hx = dirl + 1;
+ h = hx & cbdata->normapn;
+ hh = HASHCHAIN_START;
+ for (;;)
+ {
+ qx = cbdata->normap[2 * h];
+ if (!qx)
+ break;
+ if (qx == hx)
+ {
+ Id off = cbdata->normap[2 * h + 1];
+ char *dp = (char *)cbdata->filesspace + cbdata->norq.elements[off];
+ if (!strncmp(dp, dir, dirl) && dp[dirl] == 0)
+ return cbdata->norq.elements[off + 1];
+ }
+ h = HASHCHAIN_NEXT(h, hh, cbdata->normapn);
+ }
+ if (!create)
+ return 0;
+ /* new dir. work. */
+ if (dir >= (const char *)cbdata->filesspace && dir < (const char *)cbdata->filesspace + cbdata->filesspacen)
+ {
+ /* can happen when called from unifywithcanon */
+ Id off = dir - (const char *)cbdata->filesspace;
+ nspaceoff = addfilesspace(cbdata, dirl + 1);
+ dir = (const char *)cbdata->filesspace + off;
+ }
+ else
+ nspaceoff = addfilesspace(cbdata, dirl + 1);
+ if (dirl)
+ memcpy(cbdata->filesspace + nspaceoff, dir, dirl);
+ cbdata->filesspace[nspaceoff + dirl] = 0;
+ mycnt = cbdata->norq.count;
+ queue_push2(&cbdata->norq, nspaceoff, -1); /* -1: in progress */
+ cbdata->normap[2 * h] = hx;
+ cbdata->normap[2 * h + 1] = mycnt;
+ if (++cbdata->normapused * 2 > cbdata->normapn)
+ cbdata->normap = growhash(cbdata->normap, &cbdata->normapn);
+ /* unify */
+ if (cbdata->usestat)
+ nspaceoff = unifywithstat(cbdata, nspaceoff, dirl);
+ else
+ nspaceoff = unifywithcanon(cbdata, nspaceoff, dirl);
+ cbdata->norq.elements[mycnt + 1] = nspaceoff; /* patch in result */
+#if 0
+ if (!cbdata->usestat)
+ printf("%s normalized to %d: %s\n", cbdata->filesspace + cbdata->norq.elements[mycnt], nspaceoff, cbdata->filesspace + nspaceoff);
+#endif
+ return nspaceoff;
+}
+
+static void
+findfileconflicts_alias_cb(void *cbdatav, const char *fn, struct filelistinfo *info)
+{
+ int isdir = S_ISDIR(info->mode);
+ struct cbdata *cbdata = cbdatav;
+ const char *dp;
+ Id idx, dirid;
+ Id hx, qx;
+ Hashval h, hh;
+
+ idx = cbdata->idx;
+
+ if (!info->dirlen)
+ return;
+ dp = fn + info->dirlen;
+ if (info->diridx != cbdata->lastdiridx)
+ {
+ cbdata->lastdiridx = info->diridx;
+ cbdata->lastdirhash = 0;
+ }
+ dp = fn + info->dirlen;
+ hx = strhash(dp);
+ if (!hx)
+ hx = strlen(fn) + 1;
+
+ h = hx & cbdata->cflmapn;
+ hh = HASHCHAIN_START;
+ for (;;)
+ {
+ qx = cbdata->cflmap[2 * h];
+ if (!qx)
+ break;
+ if (qx == hx)
+ break;
+ h = HASHCHAIN_NEXT(h, hh, cbdata->cflmapn);
+ }
+ if (!qx || cbdata->cflmap[2 * h + 1] != -1)
+ return;
+ if (!cbdata->lastdirhash)
+ cbdata->lastdirhash = strnhash(fn, dp - fn);
+ dirid = normalizedir(cbdata, fn, dp - fn, cbdata->lastdirhash, 1);
+ queue_push2(&cbdata->lookat, hx, idx);
+ queue_push2(&cbdata->lookat, cbdata->lastdirhash, isdir ? -dirid : dirid);