2

I have a BufReader on a File. I use read_line to read from the file. This is done at a fixed rate, say every five seconds. It is possible that in these five seconds the file is deleted and recreated with the same name. Unfortunately, the file and reader objects I have still retain references to the old unlinked file, and become a zombie.

MVCE:

use std::fs::File;
use std::io::{BufRead, BufReader};
use std::{thread, time};

fn main() -> std::io::Result<()> {
    let mut x = String::new();

    let f = File::open("foo.txt")?;
    let mut reader = BufReader::new(&f);
    let metadata = f.metadata()?;

    let res = reader.read_line(&mut x);
    println!("First: {:?}\n{:?}\n{:?}", metadata, x, res);

    let duration = time::Duration::from_millis(5000);
    thread::sleep(duration);
    // delete the file in this time

    let metadata = f.metadata()?;
    let res = reader.read_line(&mut x);
    println!("Second: {:?}\n{:?}\n{:?}", metadata, x, res);

    Ok(())
}

The output I get is:

First: Metadata { file_type: FileType(FileType { mode: 33204 }), is_dir: false, is_file: true, permissions: Permissions(FilePermissions { mode: 33204 }), modified: Ok(SystemTime { tv_sec: 1610027798, tv_nsec: 326270377 }), accessed: Ok(SystemTime { tv_sec: 1610027788, tv_nsec: 510393797 }), created: Ok(SystemTime { tv_sec: 1610027786, tv_nsec: 622417600 }) }
"1\n"
Ok(2)
Second: Metadata { file_type: FileType(FileType { mode: 33204 }), is_dir: false, is_file: true, permissions: Permissions(FilePermissions { mode: 33204 }), modified: Ok(SystemTime { tv_sec: 1610027798, tv_nsec: 326270377 }), accessed: Ok(SystemTime { tv_sec: 1610027801, tv_nsec: 298233116 }), created: Ok(SystemTime { tv_sec: 1610027786, tv_nsec: 622417600 }) }
"1\n"
Ok(0)

As you may see, both timestamps are identical, and there is no indication that the underlying File object has been unlinked from the main filesytem. The corresponding Python question uses os.fstat but I cannot figure out how to use the corresponding Rust alternative (which I think, is this).

My general algorithm every five seconds is:

  1. Use fstat to find number of active links of currently open File object.
  2. If the number of links is zero, reopen the File object.
  3. If the number of links is still zero, continue to step 1 after five seconds.
  4. Read_line from this file.

I'd appreciate help in figuring out how to do step 1, or if there's a different method to do this altogether.

Gaurang Tandon
  • 6,504
  • 11
  • 47
  • 84

1 Answers1

3

The standard library does have methods for obtaining the number of links. They're just hidden behind OS specific extensions.

There's three MetadataExt traits, each in std::os::linux::fs, std::os::unix::fs, and std::os::windows::fs. Each with their own respective st_nlink(), nlinks(), and number_of_links(). Note the Windows' one is experimental.

Alternatively, you could also use e.g. the notify crate to watch for file events.


Unix:

use std::os::unix::fs::MetadataExt;

use std::fs::File;
use std::thread;
use std::time::Duration;

fn main() {
    let f = File::open("foo.txt").unwrap();

    println!("{:?}", f.metadata().unwrap().nlinks());
    // Prints `1`

    // Now delete `foo.txt`
    thread::sleep(Duration::from_secs(5));

    println!("{:?}", f.metadata().unwrap().nlinks());
    // Prints `0`
}

Windows:

#![feature(windows_by_handle)]

use std::os::windows::fs::MetadataExt;

use std::fs::File;
use std::thread;
use std::time::Duration;

fn main() {
    let f = File::open("foo.txt").unwrap();

    println!("{:?}", f.metadata().unwrap().number_of_links());
    // Prints `Some(1)`

    // Now delete `foo.txt`
    thread::sleep(Duration::from_secs(5));

    println!("{:?}", f.metadata().unwrap().number_of_links());
    // Prints `Some(0)`
}
vallentin
  • 23,478
  • 6
  • 59
  • 81