You are not logged in.

#1 2021-02-26 17:16:05

priorit
Member
Registered: 2008-12-23
Posts: 21

telldir/seekdir not working correctly on tmpfs

Normally seeking to a the place of telldir should not have any effect.
This used to be the case. I had a friend test it on "4.15.0-135-generic #139-Ubuntu". On my current kernel (5.10.16-arch1-1) that's not true anymore. I don't know if it's a recent change (and whether it's in tmpfs or the glibc).

The bug happens when deleting files while iterating over the directory. The `readdir` function is supposed to deal with that. However, things don't work anymore when `seekdir` and `telldir` are used.

Anyone knows where I should file this regression?

Example:

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {
  char* pattern = strdup("/tmp/seekdir-XXXXXX");
  char* ok = mkdtemp(pattern);
  int fd = open(ok, O_RDONLY | O_DIRECTORY);
  int a_fd = openat(fd, "a", O_WRONLY | O_CREAT, 0644);
  close(a_fd);
  int b_fd = openat(fd, "b", O_WRONLY | O_CREAT, 0644);
  close(b_fd);
  close(fd);
  printf("Created temp-dir: %s with two files 'a' and 'b'\n", ok);

  fd = open(ok, O_RDONLY | O_DIRECTORY);
  DIR* dir = fdopendir(fd);
  // Skip over '.' and '..'.
  struct dirent* entry = readdir(dir);
  entry = readdir(dir);
  entry = readdir(dir);
  printf("Deleting %s\n", entry->d_name);
  unlinkat(fd, entry->d_name, 0);
  seekdir(dir, telldir(dir));
  entry = readdir(dir);
  if (entry == NULL) {
    printf("Got NULL instead of second file");
    return 1;
  }
  printf("Got: %s\n", entry->d_name);
  return 0;
}

Offline

#2 2021-02-28 18:01:35

rowdog
Member
From: East Texas
Registered: 2009-08-19
Posts: 118

Re: telldir/seekdir not working correctly on tmpfs

I've poked at this a bit and I don't see any regression but you do have a faulty assumption.

// Skip over '.' and '..'.
  struct dirent* entry = readdir(dir);
  entry = readdir(dir);
  entry = readdir(dir);

There's no guarantee that the directory will be sorted and it's a mistake to assume the location of the dot and dot dot entries.

seekdir(dir, telldir(dir))

is effectively a no-op. "Set the current location of the directory to the current location of the directory." As expected, this made no difference at all in the order of the directory entries. If your code ever worked properly, it was just dumb luck.

If you want sorted directory listings you can use scandir(3)
https://man.archlinux.org/man/scandir.3

Offline

#3 2021-02-28 18:46:04

priorit
Member
Registered: 2008-12-23
Posts: 21

Re: telldir/seekdir not working correctly on tmpfs

Thanks for having a look.

The code here is just for demonstration, and I should have added a check to ensure that the first two `readdir` really is "." and "..". Most of the time their are.

If 'seekdir(dir, telldir(dir))' is a no-op, then it shouldn't change the result.
On my machine, I hit the "Got NULL" line with it included, but get the second file otherwise. This is precisely the problem I have: I don't see all the files.

Here is the result with the 'seekdir(dir, telldir(dir))' line (on my machine):

ᐅ /tmp/a.out                                      
Created temp-dir: /tmp/seekdir-3Uowoi with two files 'a' and 'b'
Deleting b
Got NULL instead of second file                                                                                       

Here is the output without the 'seekdir':

ᐅ /tmp/a.out                                      
Created temp-dir: /tmp/seekdir-QsQAqa with two files 'a' and 'b'
Deleting b
Got: a

Offline

#4 2021-02-28 21:37:37

rowdog
Member
From: East Texas
Registered: 2009-08-19
Posts: 118

Re: telldir/seekdir not working correctly on tmpfs

I see what you're getting at and it does look like a regression. I'm not sure this is quite the right way to put it, but if this were C++, I'd say that it looks like the directory iterator is being invalidated by the unlinkat.

I can reproduce your results with gcc but what I find curious is that clang returns NULL with or without the seekdir(dir, telldir(dir)).

I sort of overstated that seekdir line being a no-op. It's functionally a no-op but it does execute code and, at least in the case of gcc, it seems to force a refresh of the directory iterator.

As an additional data point, my Gentoo box works as expected with the same kernel and compiler versions so I'm inclined to suspect glibc 2.03 vs 2.02. Of course, there's a huge amount of difference in configuration and compile time options so it could still be something else.

Offline

#5 2021-02-28 21:42:59

loqs
Member
Registered: 2014-03-06
Posts: 17,321

Re: telldir/seekdir not working correctly on tmpfs

  DIR* dir = fdopendir(fd);
....
  unlinkat(fd, entry->d_name, 0);

fdopendir.3 notes fdopendir uses the fd internally and it should not be reused by the calling application.  What if you dup the fd before passing it in?
Edit:
When readdir returns null what errno does it set?

Last edited by loqs (2021-02-28 21:46:19)

Offline

#6 2021-02-28 23:04:28

priorit
Member
Registered: 2008-12-23
Posts: 21

Re: telldir/seekdir not working correctly on tmpfs

loqs wrote:

fdopendir.3 notes fdopendir uses the fd internally and it should not be reused by the calling application.  What if you dup the fd before passing it in?

Good point (and good to know). It didn't seem to have an effect here.
I duplicated the descriptor before the fdopendir call and got the same result.

Edit:
When readdir returns null what errno does it set?

"Success".

For completeness sake, here is the modified version, and its output.

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

int main() {
  char* pattern = strdup("/tmp/seekdir-XXXXXX");
  char* ok = mkdtemp(pattern);
  int fd = open(ok, O_RDONLY | O_DIRECTORY);
  int a_fd = openat(fd, "a", O_WRONLY | O_CREAT, 0644);
  close(a_fd);
  int b_fd = openat(fd, "b", O_WRONLY | O_CREAT, 0644);
  close(b_fd);
  close(fd);
  printf("Created temp-dir: %s with two files 'a' and 'b'\n", ok);

  fd = open(ok, O_RDONLY | O_DIRECTORY);
  int fd2 = dup(fd);
  DIR* dir = fdopendir(fd);
  // Skip over '.' and '..'.
  struct dirent* entry = readdir(dir);
  entry = readdir(dir);
  entry = readdir(dir);
  printf("Deleting %s\n", entry->d_name);
  unlinkat(fd2, entry->d_name, 0);
  seekdir(dir, telldir(dir));
  errno = 0;
  entry = readdir(dir);
  if (entry == NULL) {
    perror(NULL);
    printf("Got NULL instead of second file\n");
    return 1;
  }
  printf("Got: %s\n", entry->d_name);
  return 0;
}
ᐅ /tmp/a.out                                      
Created temp-dir: /tmp/seekdir-WHmMW4 with two files 'a' and 'b'
Deleting b
Success
Got NULL instead of second file                                                                                       

Offline

Board footer

Powered by FluxBB