Skip to content

ftruncate of unlinked file returns -1 with errno ENOENT #40262

@qbolec

Description

@qbolec

Windows Version

Microsoft Windows [Version 10.0.26200.8037]

WSL Version

2.6.3.0

Are you using WSL 1 or WSL 2?

  • WSL 2
  • WSL 1

Kernel Version

6.6.87.2-1

Distro Version

podman-machine-default and Ubuntu, too

Other Software

I can reproduce it on both podman-machine-default and wsl -d Ubuntu

Repro Steps

#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

namespace {

void print_errno(const char *label) {
  std::cout << label << ": errno=" << errno << " (" << std::strerror(errno) << ")\n";
}

void print_stat(const std::string &path) {
  struct stat st {};
  if (stat(path.c_str(), &st) == 0) {
    std::cout << "stat(" << path << "): mode=" << std::oct << (st.st_mode & 0777) << std::dec
              << " size=" << st.st_size << " uid=" << st.st_uid << " gid=" << st.st_gid << "\n";
  } else {
    print_errno(("stat(" + path + ")").c_str());
  }
}

void read_back(int fd, const char *label) {
  if (lseek(fd, 0, SEEK_SET) == -1) {
    print_errno("lseek(SEEK_SET)");
    return;
  }

  char buf[64] = {};
  errno = 0;
  ssize_t n = read(fd, buf, sizeof(buf) - 1);
  std::cout << label << ": read rc=" << n << "\n";
  if (n < 0) {
    print_errno(label);
    return;
  }
  std::cout << label << ": data=('" << std::string(buf, static_cast<size_t>(n)) << "')\n";
}

}

int main(int argc, char **argv) {
  std::string path = argc > 1 ? argv[1] : "/workspace/mysql-bin/ftruncate-repro.tmp";

  std::cout << "Using path: " << path << "\n";
  print_stat(path);

  int fd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0666);
  if (fd < 0) {
    print_errno("open");
    return 1;
  }
  std::cout << "open: fd=" << fd << "\n";
  print_stat(path);

  const char payload[] = "hello";
  ssize_t written = write(fd, payload, sizeof(payload) - 1);
  if (written < 0) {
    print_errno("write");
    close(fd);
    return 2;
  }
  std::cout << "write: wrote=" << written << "\n";
  print_stat(path);

  int rc = 0;
  errno = 0;
  rc = unlink(path.c_str());
  std::cout << "unlink(path): rc=" << rc << "\n";
  if (rc != 0) {
    print_errno("unlink(path)");
  }
  print_stat(path);

  errno = 0;
  rc = ftruncate(fd, 0);
  std::cout << "ftruncate(fd, 0): rc=" << rc << "\n";
  if (rc != 0) {
    print_errno("ftruncate(fd, 0)");
  }
  print_stat(path);
  errno = 0;
  written = write(fd, "Z", 1);
  std::cout << "write after ftruncate(fd, 0): wrote=" << written << "\n";
  if (written < 0) {
    print_errno("write after ftruncate(fd, 0)");
  }
  read_back(fd, "read after ftruncate(fd, 0)");

  errno = 0;
  rc = ftruncate(fd, 12345);
  std::cout << "ftruncate(fd, 12345): rc=" << rc << "\n";
  if (rc != 0) {
    print_errno("ftruncate(fd, 12345)");
  }
  print_stat(path);
  errno = 0;
  if (lseek(fd, 0, SEEK_END) == -1) {
    print_errno("lseek(SEEK_END)");
  }
  written = write(fd, "Y", 1);
  std::cout << "write after ftruncate(fd, 12345): wrote=" << written << "\n";
  if (written < 0) {
    print_errno("write after ftruncate(fd, 12345)");
  }
  read_back(fd, "read after ftruncate(fd, 12345)");

  if (close(fd) != 0) {
    print_errno("close");
    return 3;
  }
  std::cout << "close: ok\n";
  print_stat(path);
  return 0;
}

compile it, then run within the wsl like this: ./ftruncate_repro ./ftruncate-unlinked-repro.tmp

Expected Behavior

I would expect that all the operations (seek, write, read, and ftruncate) to still work on a descriptor even after the file was unlinked.

Actual Behavior

While write and read continue to work even after unlink, the ftruncate, doesn't:

qbolec@DESKTOP-UCOA13U:/mnt/c/ade/mysql/worker-1/mysql-bin$ ./ftruncate_repro ./ftruncate-unlinked-repro.tmp
Using path: ./ftruncate-unlinked-repro.tmp
stat(./ftruncate-unlinked-repro.tmp): errno=2 (No such file or directory)
open: fd=3
stat(./ftruncate-unlinked-repro.tmp): mode=777 size=0 uid=1000 gid=1000
write: wrote=5
stat(./ftruncate-unlinked-repro.tmp): mode=777 size=5 uid=1000 gid=1000
unlink(path): rc=0
stat(./ftruncate-unlinked-repro.tmp): errno=2 (No such file or directory)
ftruncate(fd, 0): rc=-1
ftruncate(fd, 0): errno=2 (No such file or directory)
stat(./ftruncate-unlinked-repro.tmp): errno=2 (No such file or directory)
write after ftruncate(fd, 0): wrote=1
read after ftruncate(fd, 0): read rc=6
read after ftruncate(fd, 0): data=('helloZ')
ftruncate(fd, 12345): rc=-1
ftruncate(fd, 12345): errno=2 (No such file or directory)
stat(./ftruncate-unlinked-repro.tmp): errno=2 (No such file or directory)
write after ftruncate(fd, 12345): wrote=1
read after ftruncate(fd, 12345): read rc=7
read after ftruncate(fd, 12345): data=('helloZY')
close: ok
stat(./ftruncate-unlinked-repro.tmp): errno=2 (No such file or directory)

Diagnostic Logs

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions