diff --git a/dirent/tst-seekdir.c b/dirent/tst-seekdir.c
index dcdd699b09..187eda7584 100644
--- a/dirent/tst-seekdir.c
+++ b/dirent/tst-seekdir.c
@@ -41,6 +41,14 @@ do_test (void)
       if (i == 400)
 	break;
     }
+  if (i < 3)
+    {
+      /* Non-lfs opendir skips entries that can not be represented (for
+	 instance if d_off is not an offset but rather an internal filesystem
+	 representation.  For this case there is no point in continue the
+	 testcase.  */
+      return 77;
+    }
 
   printf ("going back past 4-th entry...\n");
 
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index f298878e8f..33708f910b 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -465,6 +465,7 @@ ifeq ($(subdir),dirent)
 sysdep_routines += getdirentries getdirentries64
 tests += \
   tst-getdents64 \
+  tst-opendir-nolfs \
   tst-readdir64-compat \
   # tests
 endif # $(subdir) == dirent
diff --git a/sysdeps/unix/sysv/linux/alpha/bits/dirent.h b/sysdeps/unix/sysv/linux/alpha/bits/dirent.h
index c8a0cfe93f..586d75586a 100644
--- a/sysdeps/unix/sysv/linux/alpha/bits/dirent.h
+++ b/sysdeps/unix/sysv/linux/alpha/bits/dirent.h
@@ -54,4 +54,7 @@ struct dirent64
 /* Inform libc code that these two types are effectively identical.  */
 #define _DIRENT_MATCHES_DIRENT64	1
 
+/* alpha 'long int' is enough to handle off64_t.  */
+#define _DIRENT_OFFSET_TRANSLATION	0
+
 #endif /* bits/dirent.h */
diff --git a/sysdeps/unix/sysv/linux/bits/dirent.h b/sysdeps/unix/sysv/linux/bits/dirent.h
index ab34d986ff..bb02dcb70a 100644
--- a/sysdeps/unix/sysv/linux/bits/dirent.h
+++ b/sysdeps/unix/sysv/linux/bits/dirent.h
@@ -57,3 +57,7 @@ struct dirent64
 #else
 # define _DIRENT_MATCHES_DIRENT64	0
 #endif
+
+/* The telldir function returns long int, which may not be large enough to
+   store off64_t values.  In this case, translation is required.  */
+#define _DIRENT_OFFSET_TRANSLATION (LONG_WIDTH < 64)
diff --git a/sysdeps/unix/sysv/linux/closedir.c b/sysdeps/unix/sysv/linux/closedir.c
index f1c2608642..9585a6ca3a 100644
--- a/sysdeps/unix/sysv/linux/closedir.c
+++ b/sysdeps/unix/sysv/linux/closedir.c
@@ -47,6 +47,10 @@ __closedir (DIR *dirp)
   __libc_lock_fini (dirp->lock);
 #endif
 
+#if _DIRENT_OFFSET_TRANSLATION
+  dirstream_loc_clear (&dirp->locs);
+#endif
+
   free ((void *) dirp);
 
   return __close_nocancel (fd);
diff --git a/sysdeps/unix/sysv/linux/dirstream.h b/sysdeps/unix/sysv/linux/dirstream.h
index adcf8234f1..8f58a1c3a6 100644
--- a/sysdeps/unix/sysv/linux/dirstream.h
+++ b/sysdeps/unix/sysv/linux/dirstream.h
@@ -22,6 +22,7 @@
 #include <sys/types.h>
 
 #include <libc-lock.h>
+#include <telldir.h>
 
 /* Directory stream type.
 
@@ -38,13 +39,16 @@ struct __dirstream
     size_t size;		/* Total valid data in the block.  */
     size_t offset;		/* Current offset into the block.  */
 
-    off_t filepos;		/* Position of next entry to read.  */
+    off64_t filepos;		/* Position of next entry to read.  */
 
     int errcode;		/* Delayed error code.  */
 
 #if !defined __OFF_T_MATCHES_OFF64_T || !defined __INO_T_MATCHES_INO64_T
     struct dirent tdp;
 #endif
+#if _DIRENT_OFFSET_TRANSLATION
+    struct dirstream_loc_t locs; /* off64_t to long int map for telldir.  */
+#endif
 
     /* Directory block.  We must make sure that this block starts
        at an address that is aligned adequately enough to store
diff --git a/sysdeps/unix/sysv/linux/opendir.c b/sysdeps/unix/sysv/linux/opendir.c
index 4336196a4d..3e2caabb9d 100644
--- a/sysdeps/unix/sysv/linux/opendir.c
+++ b/sysdeps/unix/sysv/linux/opendir.c
@@ -129,6 +129,9 @@ __alloc_dir (int fd, bool close_fd, int flags,
   dirp->offset = 0;
   dirp->filepos = 0;
   dirp->errcode = 0;
+#if _DIRENT_OFFSET_TRANSLATION
+  dirstream_loc_init (&dirp->locs);
+#endif
 
   return dirp;
 }
diff --git a/sysdeps/unix/sysv/linux/readdir.c b/sysdeps/unix/sysv/linux/readdir.c
index cd0ccaf33a..7a7f484c36 100644
--- a/sysdeps/unix/sysv/linux/readdir.c
+++ b/sysdeps/unix/sysv/linux/readdir.c
@@ -36,6 +36,15 @@ dirstream_entry (struct __dirstream *ds, const struct dirent64 *dp64)
   if (dp64->d_reclen - offsetof (struct dirent64, d_name) > NAME_MAX)
     return false;
 
+  /* telldir can not return an error, so preallocate the map if the entry can
+     not be packed directly.  */
+  if (telldir_need_dirstream (dp64->d_off))
+    {
+      dirstream_loc_add (&ds->locs, dp64->d_off);
+      if (dirstream_loc_has_failed (&ds->locs))
+	return false;
+    }
+
   ds->filepos = dp64->d_off;
 
   ds->tdp.d_off = dp64->d_off;
@@ -76,7 +85,7 @@ __readdir_unlocked (DIR *dirp)
 
  	  /* Reset the offset into the buffer.  */
 	  dirp->offset = 0;
- 	}
+	}
 
       struct dirent64 *dp64 = (struct dirent64 *) &dirp->data[dirp->offset];
       dirp->offset += dp64->d_reclen;
diff --git a/sysdeps/unix/sysv/linux/rewinddir.c b/sysdeps/unix/sysv/linux/rewinddir.c
index c0fb7aa765..1b158a584f 100644
--- a/sysdeps/unix/sysv/linux/rewinddir.c
+++ b/sysdeps/unix/sysv/linux/rewinddir.c
@@ -33,6 +33,11 @@ __rewinddir (DIR *dirp)
   dirp->offset = 0;
   dirp->size = 0;
   dirp->errcode = 0;
+
+#ifndef __LP64__
+  dirstream_loc_clear (&dirp->locs);
+#endif
+
 #if IS_IN (libc)
   __libc_lock_unlock (dirp->lock);
 #endif
diff --git a/sysdeps/unix/sysv/linux/seekdir.c b/sysdeps/unix/sysv/linux/seekdir.c
index 939ccc4447..30cce691a4 100644
--- a/sysdeps/unix/sysv/linux/seekdir.c
+++ b/sysdeps/unix/sysv/linux/seekdir.c
@@ -22,14 +22,39 @@
 #include <dirstream.h>
 
 /* Seek to position POS in DIRP.  */
-/* XXX should be __seekdir ? */
 void
 seekdir (DIR *dirp, long int pos)
 {
+  off64_t filepos;
+
   __libc_lock_lock (dirp->lock);
-  (void) __lseek (dirp->fd, pos, SEEK_SET);
-  dirp->size = 0;
-  dirp->offset = 0;
-  dirp->filepos = pos;
+
+#if _DIRENT_OFFSET_TRANSLATION
+  union dirstream_packed dsp = { .l = pos };
+  if (dsp.p.is_packed == 1)
+    filepos = dsp.p.info;
+  else
+    {
+      size_t index = dsp.p.info;
+
+      if (index >= dirstream_loc_size (&dirp->locs))
+	{
+	  __libc_lock_unlock (dirp->lock);
+	  return;
+	}
+      filepos = *dirstream_loc_at (&dirp->locs, index);
+    }
+#else
+  filepos = pos;
+#endif
+
+  if (dirp->filepos != filepos)
+    {
+      __lseek64 (dirp->fd, filepos, SEEK_SET);
+      dirp->filepos = filepos;
+      dirp->offset = 0;
+      dirp->size = 0;
+    }
+
   __libc_lock_unlock (dirp->lock);
 }
diff --git a/sysdeps/unix/sysv/linux/telldir.c b/sysdeps/unix/sysv/linux/telldir.c
index 1e5c129e9f..c3ef14f3da 100644
--- a/sysdeps/unix/sysv/linux/telldir.c
+++ b/sysdeps/unix/sysv/linux/telldir.c
@@ -15,9 +15,11 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <assert.h>
 #include <dirent.h>
 
 #include <dirstream.h>
+#include <telldir.h>
 
 /* Return the current position of DIRP.  */
 long int
@@ -26,7 +28,40 @@ telldir (DIR *dirp)
   long int ret;
 
   __libc_lock_lock (dirp->lock);
+
+#if _DIRENT_OFFSET_TRANSLATION
+  /* If the directory position fits in the packet structure, returns it.
+     Otherwise, check if the position is already been recorded in the
+     dynamic array.  If not, add the new record.  */
+
+  union dirstream_packed dsp;
+
+  if (!telldir_need_dirstream (dirp->filepos))
+    {
+      dsp.p.is_packed = 1;
+      dsp.p.info = dirp->filepos;
+    }
+  else
+    {
+      dsp.l = -1;
+
+      size_t i;
+      for (i = 0; i < dirstream_loc_size (&dirp->locs); i++)
+	if (*dirstream_loc_at (&dirp->locs, i) == dirp->filepos)
+	  break;
+      /* It should be pre-allocated on readdir.  */
+      assert (i != dirstream_loc_size (&dirp->locs));
+
+      dsp.p.is_packed = 0;
+      /* This assignment might overflow, however most likely ENOME would
+	 happen long before.  */
+      dsp.p.info = i;
+    }
+
+  ret = dsp.l;
+#else
   ret = dirp->filepos;
+#endif
   __libc_lock_unlock (dirp->lock);
 
   return ret;
diff --git a/sysdeps/unix/sysv/linux/telldir.h b/sysdeps/unix/sysv/linux/telldir.h
new file mode 100644
index 0000000000..758bcb0eb3
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/telldir.h
@@ -0,0 +1,65 @@
+/* Linux internal telldir definitions.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _TELLDIR_H
+#define _TELLDIR_H 1
+
+#if _DIRENT_OFFSET_TRANSLATION
+/* On platforms where 'long int' is smaller than 'off64_t' this is how the
+   returned value is encoded and returned by 'telldir'.  If the directory
+   offset can be enconded in 31 bits it is returned in the 'info' member
+   with 'is_packed' set to 1.
+
+   Otherwise, the 'info' member describes an index in a dynamic array at
+   'DIR' structure.  */
+
+union dirstream_packed
+{
+  long int l;
+  struct
+  {
+    unsigned long int is_packed:1;
+    unsigned long int info:31;
+  } p;
+};
+
+_Static_assert (sizeof (long int) == sizeof (union dirstream_packed),
+		"sizeof (long int) != sizeof (union dirstream_packed)");
+
+/* telldir maintains a list of offsets that describe the obtained diretory
+   position if it can fit this information in the returned 'dirstream_packed'
+   struct.  */
+
+# define DYNARRAY_STRUCT  dirstream_loc_t
+# define DYNARRAY_ELEMENT off64_t
+# define DYNARRAY_PREFIX  dirstream_loc_
+# include <malloc/dynarray-skeleton.c>
+
+static __always_inline bool
+telldir_need_dirstream (__off64_t d_off)
+{
+  return d_off >= 1UL << 31;
+}
+#else
+
+_Static_assert (sizeof (long int) == sizeof (off64_t),
+		"sizeof (long int) != sizeof (off64_t)");
+
+#endif /* __LP64__  */
+
+#endif /* _TELLDIR_H  */
diff --git a/sysdeps/unix/sysv/linux/tst-opendir-nolfs.c b/sysdeps/unix/sysv/linux/tst-opendir-nolfs.c
new file mode 100644
index 0000000000..52e18171a7
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-opendir-nolfs.c
@@ -0,0 +1,146 @@
+/* Check multiple telldir and seekdir.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+
+/* Some filesystems returns an arbitrary value for d_off direnty entry (ext4
+   for instance, where the value is an internal hash key).  The idea of create
+   a large number of file is to try trigger a overflow d_off value in a entry
+   to check if telldir/seekdir does work corretly in such case.  */
+static const char *dirname;
+/* The 2 extra files are '.' and '..'.  */
+static const size_t nfiles = (1<<14) + 2;
+
+static inline bool
+in_ino_t_range (ino64_t v)
+{
+  ino_t s = v;
+  return s == v;
+}
+
+static inline bool
+in_off_t_range (off64_t v)
+{
+  off_t s = v;
+  return s == v;
+}
+
+static void
+do_prepare (int argc, char *argv[])
+{
+  dirname = support_create_temp_directory ("tst-opendir-nolfs-");
+
+  for (size_t i = 0; i < nfiles - 2; i++)
+    {
+      int fd = create_temp_file_in_dir ("tempfile.", dirname, NULL);
+      TEST_VERIFY_EXIT (fd > 0);
+      close (fd);
+    }
+}
+#define PREPARE do_prepare
+
+static int
+do_test (void)
+{
+  DIR *dirp = opendir (dirname);
+  TEST_VERIFY_EXIT (dirp != NULL);
+
+  long int *tdirp = xmalloc (nfiles * sizeof (long int));
+  struct dirent **ddirp = xmalloc (nfiles * sizeof (struct dirent *));
+
+  /* For non-LFS, the entry is skipped if it can not be converted.  */
+  int count = 0;
+  for (; count < nfiles; count++)
+    {
+      tdirp[count] = telldir (dirp);
+      struct dirent *dp = readdir (dirp);
+      if (dp == NULL)
+	break;
+      ddirp[count] = xmalloc (dp->d_reclen);
+      memcpy (ddirp[count], dp, dp->d_reclen);
+    }
+
+  closedir (dirp);
+
+  /* Check against the getdents64 syscall.  */
+  int fd = xopen (dirname, O_RDONLY | O_DIRECTORY, 0);
+  int i = 0;
+  while (true)
+    {
+      struct
+      {
+	char buffer[1024];
+	struct dirent64 pad;
+      } data;
+
+      ssize_t ret = getdents64 (fd, &data.buffer, sizeof (data.buffer));
+      if (ret < 0)
+	FAIL_EXIT1 ("getdents64: %m");
+      if (ret == 0)
+	break;
+
+      char *current = data.buffer;
+      char *end = data.buffer + ret;
+      while (current != end)
+	{
+	  struct dirent64 entry;
+          memcpy (&entry, current, sizeof (entry));
+          /* Truncate overlong strings.  */
+          entry.d_name[sizeof (entry.d_name) - 1] = '\0';
+          TEST_VERIFY (strlen (entry.d_name) < sizeof (entry.d_name) - 1);
+
+	  if (in_ino_t_range (entry.d_ino) && in_off_t_range (entry.d_off))
+	    {
+	      TEST_COMPARE_STRING (entry.d_name, ddirp[i]->d_name);
+	      TEST_COMPARE (entry.d_ino, ddirp[i]->d_ino);
+	      TEST_COMPARE (entry.d_off, ddirp[i]->d_off);
+	      TEST_COMPARE (entry.d_type, ddirp[i]->d_type);
+
+	      /* Offset zero is reserved for the first entry.  */
+	      TEST_VERIFY (entry.d_off != 0);
+
+	      TEST_VERIFY_EXIT (entry.d_reclen <= end - current);
+	      i++;
+	    }
+
+	  current += entry.d_reclen;
+	}
+    }
+
+  /* direntries_read has been called more than once.  */
+  TEST_COMPARE (count, i);
+
+  free (tdirp);
+  for (int i = 0; i < count; i++)
+    free (ddirp[i]);
+  free (ddirp);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
