]> git.baikalelectronics.ru Git - kernel.git/commit
fs/file.c: __fget() and dup2() atomicity rules
authorEric Dumazet <edumazet@google.com>
Mon, 29 Jun 2015 15:10:30 +0000 (17:10 +0200)
committerAl Viro <viro@zeniv.linux.org.uk>
Wed, 1 Jul 2015 06:31:08 +0000 (02:31 -0400)
commit446c5376e5a156a2665a0de36f9a298f6fdad914
tree97666a4d6fe7e925f3fd676f52bd339855a229a5
parent108bfa262eb48d9488af63986c0fab35babb4ff7
fs/file.c: __fget() and dup2() atomicity rules

__fget() does lockless fetch of pointer from the descriptor
table, attempts to grab a reference and treats "it was already
zero" as "it's already gone from the table, we just hadn't
seen the store, let's fail".  Unfortunately, that breaks the
atomicity of dup2() - __fget() might see the old pointer,
notice that it's been already dropped and treat that as
"it's closed".  What we should be getting is either the
old file or new one, depending whether we come before or after
dup2().

Dmitry had following test failing sometimes :

int fd;
void *Thread(void *x) {
  char buf;
  int n = read(fd, &buf, 1);
  if (n != 1)
    exit(printf("read failed: n=%d errno=%d\n", n, errno));
  return 0;
}

int main()
{
  fd = open("/dev/urandom", O_RDONLY);
  int fd2 = open("/dev/urandom", O_RDONLY);
  if (fd == -1 || fd2 == -1)
    exit(printf("open failed\n"));
  pthread_t th;
  pthread_create(&th, 0, Thread, 0);
  if (dup2(fd2, fd) == -1)
    exit(printf("dup2 failed\n"));
  pthread_join(th, 0);
  if (close(fd) == -1)
    exit(printf("close failed\n"));
  if (close(fd2) == -1)
    exit(printf("close failed\n"));
  printf("DONE\n");
  return 0;
}

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/file.c