+ /* Figure out if this directory is a mount point. */
+ dotdev = st.st_dev;
+ dotino = st.st_ino;
+ mount_point = dotdev != thisdev;
+
+ /* Search for the last directory. */
+#if HAVE_OPENAT_SUPPORT
+ dirstream = fdopendir (fd);
+ if (dirstream == NULL)
+ goto lose;
+ fd_needs_closing = false;
+#else
+ dirstream = __opendir (dotlist);
+ if (dirstream == NULL)
+ goto lose;
+ dotlist[dotlen++] = '/';
+#endif
+ for (;;)
+ {
+ /* Clear errno to distinguish EOF from error if readdir returns
+ NULL. */
+ __set_errno (0);
+ d = __readdir (dirstream);
+
+ /* When we've iterated through all directory entries without finding
+ one with a matching d_ino, rewind the stream and consider each
+ name again, but this time, using lstat. This is necessary in a
+ chroot on at least one system (glibc-2.3.6 + linux 2.6.12), where
+ .., ../.., ../../.., etc. all had the same device number, yet the
+ d_ino values for entries in / did not match those obtained
+ via lstat. */
+ if (d == NULL && errno == 0 && use_d_ino)
+ {
+ use_d_ino = false;
+ rewinddir (dirstream);
+ d = __readdir (dirstream);
+ }
+
+ if (d == NULL)
+ {
+ if (errno == 0)
+ /* EOF on dirstream, which can mean e.g., that the current
+ directory has been removed. */
+ __set_errno (ENOENT);
+ goto lose;
+ }
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+ continue;
+
+ if (use_d_ino)
+ {
+ bool match = (MATCHING_INO (d, thisino) || mount_point);
+ if (! match)
+ continue;
+ }
+
+ {
+ int entry_status;
+#if HAVE_OPENAT_SUPPORT
+ entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW);
+#else
+ /* Compute size needed for this file name, or for the file
+ name ".." in the same directory, whichever is larger.
+ Room for ".." might be needed the next time through
+ the outer loop. */
+ size_t name_alloc = _D_ALLOC_NAMLEN (d);
+ size_t filesize = dotlen + MAX (sizeof "..", name_alloc);
+
+ if (filesize < dotlen)
+ goto memory_exhausted;
+
+ if (dotsize < filesize)
+ {
+ /* My, what a deep directory tree you have, Grandma. */
+ size_t newsize = MAX (filesize, dotsize * 2);
+ size_t i;
+ if (newsize < dotsize)
+ goto memory_exhausted;
+ if (dotlist != dots)
+ free (dotlist);
+ dotlist = malloc (newsize);
+ if (dotlist == NULL)
+ goto lose;
+ dotsize = newsize;
+
+ i = 0;
+ do
+ {
+ dotlist[i++] = '.';
+ dotlist[i++] = '.';
+ dotlist[i++] = '/';
+ }
+ while (i < dotlen);
+ }
+
+ memcpy (dotlist + dotlen, d->d_name, _D_ALLOC_NAMLEN (d));
+ entry_status = __lstat (dotlist, &st);
+#endif
+ /* We don't fail here if we cannot stat() a directory entry.
+ This can happen when (network) file systems fail. If this
+ entry is in fact the one we are looking for we will find
+ out soon as we reach the end of the directory without
+ having found anything. */
+ if (entry_status == 0 && S_ISDIR (st.st_mode)
+ && st.st_dev == thisdev && st.st_ino == thisino)
+ break;
+ }
+ }
+
+ dirroom = dirp - dir;
+ namlen = _D_EXACT_NAMLEN (d);
+
+ if (dirroom <= namlen)
+ {
+ if (size != 0)
+ {
+ __set_errno (ERANGE);
+ goto lose;
+ }
+ else
+ {
+ char *tmp;
+ size_t oldsize = allocated;
+
+ allocated += MAX (allocated, namlen);
+ if (allocated < oldsize
+ || ! (tmp = realloc (dir, allocated)))
+ goto memory_exhausted;
+
+ /* Move current contents up to the end of the buffer.
+ This is guaranteed to be non-overlapping. */
+ dirp = memcpy (tmp + allocated - (oldsize - dirroom),
+ tmp + dirroom,
+ oldsize - dirroom);
+ dir = tmp;
+ }
+ }
+ dirp -= namlen;
+ memcpy (dirp, d->d_name, namlen);
+ *--dirp = '/';
+
+ thisdev = dotdev;
+ thisino = dotino;
+ }
+
+ if (dirstream && __closedir (dirstream) != 0)
+ {
+ dirstream = NULL;
+ goto lose;
+ }
+
+ if (dirp == &dir[allocated - 1])
+ *--dirp = '/';
+
+#if ! HAVE_OPENAT_SUPPORT
+ if (dotlist != dots)
+ free (dotlist);
+#endif
+
+ used = dir + allocated - dirp;
+ memmove (dir, dirp, used);
+
+ if (size == 0)
+ /* Ensure that the buffer is only as large as necessary. */
+ buf = realloc (dir, used);
+
+ if (buf == NULL)
+ /* Either buf was NULL all along, or 'realloc' failed but
+ we still have the original string. */
+ buf = dir;
+
+ return buf;
+
+ memory_exhausted:
+ __set_errno (ENOMEM);
+ lose:
+ {
+ int save = errno;
+ if (dirstream)
+ __closedir (dirstream);
+#if HAVE_OPENAT_SUPPORT
+ if (fd_needs_closing)
+ close (fd);
+#else
+ if (dotlist != dots)
+ free (dotlist);
+#endif
+ if (buf == NULL)
+ free (dir);
+ __set_errno (save);
+ }