74656


IA Linuxでファイル出力するJavaプログラムを動かします。（以下デモプログラムはUbuntu 18とJava 1.8で確認。）
FileOutputStreamをnewした後に、対象ストリームに書き込みを行いますが、FileOutputStream作成後にOSコマンドで当該ファイルを削除(`rm`)（もしくはリネーム(`mv`)）しても、その後の書き込み処理でIOExceptionが発生せずに正常終了します。

  * `File#exists()`は`false`になります
  * `FileDescriptor#valid()`は`true`のままです

write時にOSとJVMの間で何が起こっているのかを知りたく、コマンドstraceとltraceの出力結果を見てみたのですが、ストリームのオープン／クローズおよび書き込み時には何らトレース出力は得られませんでした。

＜確認したコマンド＞

[code]

    strace -t java ～
    lstrace -t java ～
[/code]

おそらくJavaプログラムとしてはJVM上のストリームオブジェクトに書いているだけで、JVMとしてはflushもしくはsyncしてもOSのIOバッファに書いているだけで、ファイルシステム上のファイルの状態には影響を受けないため、Java側ではエラーが発生していないのではないかと想像しています。

＜質問＞

  1. ファイルシステム上でファイル削除しても、当該ファイルに対して作成済みストリームを用いた書き込み時に例外発生しないのはなぜでしょうか。
  2. OS側では何等かエラーが発生しているのではないかと思います。これを確認できるトレースコマンドがあれば教えてください。

＜追加質問＞

rmやmvするとunlinkされるだけでファイルシステムからdeleteされるわけではないため、unlink前に作成済みのファイルディスクリプタを用いたIOはエラーにならない旨、承知しました。

  3. 上記の通りstraceやltraceではJVMのIOに掛かるシステムコールが得られなかったのですが、これが分かるトレースコマンドはありますでしょうか
  4. unlinkされたことをIOしているプロセス側で検知すること（もしくはIOしているプロセス側へOSから通知すること）は可能でしょうか

＜デモプログラム＞

[code]

    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.Writer;
    
    class FileInputDemoStream {
        
        private static File file = null;
        private static OutputStream os = null;
        private static FileOutputStream fos = null;
    
        public void createStream() throws InterruptedException, FileNotFoundException, IOException {
            System.out.println("start");
            String fileName = "demo.out";
            file = new File(fileName);
            fos = new FileOutputStream(file, true);
            os = new BufferedOutputStream(fos);
        }
    
        public void close() throws InterruptedException, FileNotFoundException, IOException {
            os.close();
        }
        
        public void write(String str) {
            try {
                System.out.println("ファイル存在確認："+file.exists());  // falseになる
                System.out.println("ディスクリプタ："+fos.getFD().valid()); // trueのまま
                Writer writer = new OutputStreamWriter(os);
                writer.write(str+"\n"); // エラーにならない
                writer.flush(); // エラーにならない
                fos.getFD().sync(); // エラーにならない
            } catch (Exception e) {
                System.out.println(e);
            }
        }
        
    }
    
[/code]

上記プログラムの呼び出し元デモプログラム

[code]

    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.Writer;
    
    class FileInputDemo {
        
        private static FileInputDemoStream fi = new FileInputDemoStream();
        
        public static void main(String str[]) throws InterruptedException, FileNotFoundException, IOException {
            System.out.println("start");
            System.out.println("open stream");
            fi.createStream();
            for (int i=0; i < 10; i++) { 
                System.out.println(String.valueOf(i));
                fi.write("Counter: "+String.valueOf(i));
                Thread.sleep(10000);
            }
            System.out.println("cloes stream");
            fi.close();
            System.out.println("end");
        }
    }
    
[/code]


CC BY-SA 4.0
2021-03-13T14:02:30.863
74650
2021-03-13T17:57:01.440
2021-03-13T17:57:01.440
44323
44323
question
java linux
JavaのFileOutputStream作成後にファイル削除してもwriteでIOException発生しないのはなぜでしょうか


UNIX系 OS であれば (通常), ファイルを使用中でも削除可能です。`mv` で名前変更
(あるいは移動可能な別ディレクトリーへの移動)も可能です。(名前と実体(inode) は別もの, と言えます)  
例えば [remove(3)](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/remove.3.html)
で削除する場合, ファイルだと [unlink(2)](https://linuxjm.osdn.jp/html/LDP_man-
pages/man2/unlink.2.html) を呼び出します。  
以下 `unlink()` の解説

> unlink() はファイルシステム上の名前を削除する。 もしその名前がファイルへの最後のリンク (link) であり、
> どのプロセスもそのファイルをオープン (open) していなければ、 ファイルは削除される。
> ファイルが使用していたディスク上の領域は再利用が可能になる。  
>  
>  名前がファイルへの最後のリンクであっても、どこかのプロセスが そのファイルを開いているなら、ファイルの最後のファイルディスクリプター (file
> descriptor) が閉じられるまでファイルは存在し続ける。

* * *

これを利用したテクニック？も存在します。  
一時ファイルを作成し, すぐに削除。そのことで一時ファイルを他からアクセスできなくする というもの。  
(どの UNIX系の OSかは調べてないが, そもそも名前がない, という一時ファイルを用意できるのもあるそう)

* * *

(追記)  
ということで, エラーではないので, エラー系のログにはなにも記されていないでしょう


CC BY-SA 4.0
2021-03-13T14:57:17.890
74652
2021-03-13T15:03:05.723
2021-03-13T15:03:05.723
43025
43025
74650
answer


こちらが参考になるかと思います:

  * [Linux とWindows では「ファイルを消す」際の挙動が異なるんだ。気をつけろ - LukeSilvia’s diary](https://lukesilvia.hatenablog.com/entry/20081001/p1)
  * [削除したファイルをlsofで復元する | OSDN Magazine](https://mag.osdn.jp/06/11/23/0451251)

* * *

>   1. ファイルシステム上でファイル削除しても、当該ファイルに対して作成済みストリームを用いた書き込み時に例外発生しないのはなぜでしょうか。
>

後者リンクの該当部分を引用します:

>
> 簡単に説明すると、Linuxファイルシステム上にあるように見えるファイルは、実際はinodeへのリンクに過ぎない。inodeには、ファイルのあらゆるプロパティ（アクセス権や所有権など）のほか、ファイルの中味が実際に存在するディスク上のデータブロックのアドレスも記録される。`rm`コマンドでファイルを削除すると、ファイルのinodeを指すリンクは削除されるが、inodeそのものは削除されない。削除した時点で、他のプロセス（オーディオ・プレーヤーなど）でファイルがまだ開かれている場合もある。このようなプロセスがすべて終了し、すべてのリンクが削除されるまで、inodeとそれに関連付けられたデータブロックが書き込みの対象となることはない。

* * *

>   2. OS側では何等かエラーが発生しているのではないかと思います。これを確認できるトレースコマンドがあれば教えてください。
>

前述の通り、エラーは発生しません。  
こちらも後者のリンクに説明がありますが、OS上の挙動は次のコマンドで確認できるかと思います。  
(以下、全て Ubuntu 20.04 上で確認)

まず、ファイル削除後もプロセスが正常に書き込みを行っている(ので結果としてエラーが発生しない)のは、 `/proc/[process
id]/fd/[file descriptor]` を見ることで確認できます。

[code]

    #!/bin/sh
    
    pid=$(jps | grep FileInputDemo | cut -d ' ' -f 1)
    fd=$(ls -l /proc/$pid/fd/ | grep demo.out | cut -d ' ' -f 10)
    
    # ファイル行数出力
    # rmで削除しても削除してもプロセスが書いた内容が反映され続けるのが確認できる
    cat "/proc/$pid/fd/$fd" | wc -l
    
[/code]

例え`demo.out`ファイルを削除しても、ファイル行数が増えていくのが確認できます。

次に、書き込み中のファイルが削除されたことは`lsof`コマンドで確認できます。

[code]

    $ lsof | grep demo.out
    
[/code]

Javaプロセス実行中に`demo.out`を削除すると上記実行結果に `(deleted)` が付与され、削除されたことが確認できます。


CC BY-SA 4.0
2021-03-13T17:35:10.283
74656
2021-03-13T17:35:10.283
2808
74650
answer