Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Files.newOutputStream(path, CREATE_NEW, DELETE_ON_CLOSE) does not write to file

Tags:

java

nio

I have the following code:

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;

import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;

public class Test {
    public static void main(String[] args) throws IOException {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get("test"), CREATE_NEW, DELETE_ON_CLOSE)) {
            outputStream.write(123);
            outputStream.flush();
            System.out.println("done");
        }
    }
}

I put a break point on the call to System.out.println and inspected my working directory. There was no file called test. Why isn't the output stream writing to a file?

like image 349
Max Avatar asked Mar 12 '23 07:03

Max


1 Answers

The reason is that on Linux you can delete a file from a directory even the file is open (appropriate permissions silently assumed here). Under Windows it's not possible.

From the source of sun.nio.fs.UnixChannelFactory

// unlink file immediately if delete on close. The spec is clear that
// an implementation cannot guarantee to unlink the correct file when
// replaced by an attacker after it is opened.
if (flags.deleteOnClose) { ...

If you amend your code as

for (int i = 0; i < 10; i++) {
    outputStream.write(123);
    outputStream.flush();
    System.out.println("flush...");
    Thread.sleep(10_000);
}

You are able to see that the file is open but already deleted

# assumed that the code write to Paths.get("/tmp/test")
lsof | grep "/tmp/test"
...  /tmp/test (deleted)

edit If you only want to ensure that a temporary file is removed when your application quits, have a look at the snippet below.

import java.io.File;
import java.io.OutputStream;
import java.nio.file.Files;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("/tmp/test");
        file.deleteOnExit();
        System.out.println("tempFile = " + tempFile);
        try (OutputStream outputStream = Files.newOutputStream(file.toPath(),
                CREATE_NEW)) {
            outputStream.write(123);
            outputStream.flush();
            System.out.println("done");
        }
        System.out.printf("%s exists: %s%n", file, file.exists());
    }
}

The file /tmp/test will be removed at the moment the application finished.

output (the file still exists at this time)

/tmp/test exists: true

if you now check on the console

$ ls /tmp/test
ls: cannot access '/tmp/test': No such file or directory

In case you don't even care about the file name you might consider to use a randomly generated one.

File tempFile = File.createTempFile("foo_", ".tmp", new File("/tmp"));

edit Another solution could be.

  1. create the file (prefer to use a random temporary file name)
  2. open an InputStream
  3. open the OutputStream with DELETE_ON_CLOSE

Doing it in that order it would work as you expect.

Find below a working snippet.

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
import static java.nio.file.StandardOpenOption.READ;

public class DeleteOnClose {

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("/tmp/test");
        System.out.println("before create: " + Files.exists(path));
        Files.createFile(path);
        System.out.println("after create: " + Files.exists(path));
        try (InputStream in = Files.newInputStream(path, READ);
                OutputStream out = Files.newOutputStream(path, APPEND, 
                        DELETE_ON_CLOSE)) {
            out.write("Hello file!".getBytes(UTF_8));
            out.flush();

            for (int c = in.read(); c >= 0; c = in.read()) {
                System.out.print((char) c);
            }
            System.out.println();
        }
        System.out.println("after close: " + Files.exists(path));
    }
}

output

before create: false
after create: true
Hello file!
after close: false
like image 60
SubOptimal Avatar answered Apr 14 '23 14:04

SubOptimal