指摘されたファイルが移動または削除された場合、Linuxで開いているファイルハンドルはどうなりますか


107

指摘されたファイルがその間に取得されると、Linuxで開いているファイルハンドルはどうなりますか。

  • 移動->ファイルハンドルは有効なままですか?
  • 削除->これにより、無効なファイルハンドルを示すEBADFが発生しますか?
  • 新しいファイルに置き換えられました->ファイルはこの新しいファイルをポイントしていますか?
  • 新しいファイルへのハードリンクに置き換えられました->ファイルはこのリンクを「フォロー」しますか?
  • 新しいファイルへのソフトリンクに置き換えられました->ファイルハンドルはこのソフトリンクファイルにヒットしますか?

なぜ私はそのような質問をしているのですか?私はホットプラグされたハードウェア(USBデバイスなど)を使用しています。デバイス(および/ dev / file)がユーザーまたは別のGremlinによって再接続される可能性があります。

これに対処するためのベストプラクティスは何ですか?

回答:


159

ファイルが移動された場合(同じファイルシステム内)、または名前が変更された場合、ファイルハンドルは開いたままになり、ファイルの読み取りと書き込みに引き続き使用できます。

ファイルが削除されても、ファイルハンドルは開いたままになり、引き続き使用できます(これは一部の人が期待することではありません)。最後のハンドルが閉じられるまで、ファイルは実際には削除されません。

ファイルが新しいファイルで置き換えられる場合、それは正確にどのように依存するかです。ファイルの内容が上書きされた場合でも、ファイルハンドルは有効であり、新しい内容にアクセスします。既存のファイルがリンク解除され、同じ名前で新しいファイルが作成された場合、または新しいファイルがを使用して既存のファイルに移動された場合、rename()削除と同じです(上記を参照)-つまり、ファイルハンドルは引き続き参照しますのファイルのバージョン。

一般に、いったんファイルが開かれると、ファイルは開かれ、ディレクトリ構造を変更する人は誰もそれを変更できません。ファイルを移動したり、名前を変更したり、他の場所に配置したりできますが、ファイルは開いたままです。

Unixでは削除はなく、のみですunlink()。これは、必ずしもファイルを削除するわけではないため、ディレクトリからリンクを削除するだけです。


一方、基になるデバイスが消えた場合(USBの取り外しなど)、ファイルハンドルは無効になり、操作中にIO /エラーが発生する可能性があります。ただし、それを閉じる必要があります。この場合、ファイルを開いたままにしておくのは賢明ではないため、デバイスが再び接続されている場合でも、これは当てはまります。


ファイルを含むディレクトリが削除された場合にも、2つ目の点が当てはまると思います。そうですか?
Drew Noakes 2014年

2
私は1つのことに興味があります。cpコマンドを使用してファイルを上書きする場合、それは最初のケースですか、それとも2番目のケースですか?
xuhdev 14

1
最後のハンドルが閉じられるまで、ファイルは実際には削除されません。」興味深い。感謝
ジェレミア2018年

8

ファイルハンドルはパスではなくiノードを指しているため、ハンドルは依然としてファイルを指しているため、ほとんどのシナリオは想定どおりに機能します。

具体的には、削除シナリオでは、この関数は理由により「リンク解除」と呼ばれ、ファイル名(歯列)とファイル間の「リンク」を破棄します。ファイルを開いてリンクを解除すると、参照カウントが0になるまでファイルは実際に存在します(ハンドルを閉じるとき)。

編集:ハードウェアの場合、特定のデバイスノードへのハンドルを開いています。デバイスを取り外した場合、デバイスが戻ってきても、カーネルはそのノードへのすべてのアクセスに失敗します。デバイスを閉じて、再度開く必要があります。


5

他の操作についてはわかりませんが、削除に関しては、ファイルへの最後の開いているハンドルが閉じられるまで、削除は(物理的に、つまりファイルシステムで)行われません。したがって、アプリケーションの下からファイルを削除することはできません。

一部のアプリ(頭に浮かばない)は、ファイルを作成、開いてすぐに削除することでこの動作に依存し、アプリケーションと同じ長さで存続するため、他のアプリケーションは最初のアプリのライフサイクルを意識する必要がありません。プロセスマップなどを見てください。

同様の考慮事項が他のものに適用される可能性があります。


4

ファイルハンドラー(ファイル記述子)に問題がないかどうかを確認する場合は、この関数を呼び出すことができます。

/**
 * version : 1.1
 *    date : 2015-02-05
 *    func : check if the fileDescriptor is fine.
 */

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

/**
 * On success, zero is returned.  On error, -1  is  returned,  and  errno  is  set
 *      appropriately.
 */
int check_fd_fine(int fd) {
    struct stat _stat;
    int ret = -1;
    if(!fcntl(fd, F_GETFL)) {
        if(!fstat(fd, &_stat)) {
            if(_stat.st_nlink >= 1)
                ret = 0;
            else
                printf("File was deleted!\n");
        }
    }
    if(errno != 0)
        perror("check_fd_fine");
    return ret;
}

int main() {
    int fd = -1;
    fd = open("/dev/ttyUSB1", O_RDONLY);
    if(fd < 0) {
        perror("open file fail");
        return -1;
    }
    // close or remove file(remove usb device)
//  close(fd);
    sleep(5);
    if(!check_fd_fine(fd)) {
        printf("fd okay!\n");
    } else {
        printf("fd bad!\n");
    }
    close(fd);
    return 0;
}

1
if(!fcntl(fd, F_GETFL)) {チェックのポイントは何ですか?あなたはEBADFそこを探していると思います。(また、おそらくerrno0 に初期化するのを忘れていました)。
2016年

これは私にはうまくいきません。私はこのアプローチを使用してみましたopen(O_WRONLY|O_APPEND)-私の記述子が開いている間、st_nlinkは常に> = 1のままです。
imbearr

2

削除されたファイルのメモリ内情報(すべての例は削除されたファイルのインスタンスです)とディスク上のiノードは、ファイルが閉じられるまで存在し続けます。

ハードウェアのホットプラグは完全に異なる問題であり、ディスク上のiノードまたはメタデータがまったく変更された場合でも、プログラムが長く存続することを期待するべきではありません。


2

次の実験は、MarkRの答えが正しいことを示しています。

code.c:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <stdio.h>

void perror_and_exit() {
  perror(NULL);
  exit(1);
}

int main(int argc, char *argv[]) {
  int fd;
  if ((fd = open("data", O_RDONLY)) == -1) {
    perror_and_exit();
  }
  char buf[5];
  for (int i = 0; i < 5; i++) {
    bzero(buf, 5);
    if (read(fd, buf, 5) != 5) {
      perror_and_exit();
    }
    printf("line: %s", buf);
    sleep(20);
  }
  if (close(fd) != 0) {
    perror_and_exit();
  }
  return 0;
}

データ:

1234
1234
1234
1234
1234

gcc code.cを生成するために使用しa.outます。を実行します./a.out。次の出力が表示された場合:

line: 1234

を使用rm dataして削除しdataます。ただし./a.out、エラーなしで実行され続け、次の全体出力が生成されます。

line: 1234
line: 1234
line: 1234
line: 1234
line: 1234

Ubuntu 16.04.3で実験を行いました。


1

/ proc /ディレクトリの下に、現在アクティブなすべてのプロセスのリストが表示されます。PIDを見つけるだけで、関連するすべてのデータがそこにあります。興味深い情報はfd /フォルダーです。このプロセスによって現在開かれているすべてのファイルハンドラーが見つかります。

最終的には、デバイスへのシンボリックリンク(/ dev /または/ proc / bus / usb /の下にあります)が見つかります。デバイスがハングした場合、リンクは無効になり、このハンドルを更新できなくなります。プロセスは閉じて、再度開きます(再接続した場合でも)

このコードは、PIDのリンクの現在のステータスを読み取ることができます

#include <unistd.h>
#include <stdio.h>
#include <dirent.h>

int main() {
    // the directory we are going to open
    DIR           *d;

    // max length of strings
    int maxpathlength=256;

    // the buffer for the full path
    char path[maxpathlength];

    // /proc/PID/fs contains the list of the open file descriptors among the respective filenames
    sprintf(path,"/proc/%i/fd/",getpid() );

    printf("List of %s:\n",path);

    struct dirent *dir;
    d = opendir(path);
    if (d) {
        //loop for each file inside d
        while ((dir = readdir(d)) != NULL) {

            //let's check if it is a symbolic link
            if (dir->d_type == DT_LNK) {

                const int maxlength = 256;

                //string returned by readlink()
                char hardfile[maxlength];

                //string length returned by readlink()
                int len;

                //tempath will contain the current filename among the fullpath
                char tempath[maxlength];

                sprintf(tempath,"%s%s",path,dir->d_name);
                if ((len=readlink(tempath,hardfile,maxlength-1))!=-1) {
                    hardfile[len]='\0';
                        printf("%s -> %s\n", dir->d_name,hardfile);

                } else
                    printf("error when executing readlink() on %s\n",tempath);

            }
        }

        closedir(d);
    }
    return 0;
}

この最後のコードは単純で、linkat関数で遊ぶことができます。

int
open_dir(char * path)
{
  int fd;

  path = strdup(path);
  *strrchr(path, '/') = '\0';
  fd = open(path, O_RDONLY | O_DIRECTORY);
  free(path);

  return fd;
}

int
main(int argc, char * argv[])
{
  int odir, ndir;
  char * ofile, * nfile;
  int status;

  if (argc != 3)
    return 1;

  odir = open_dir(argv[1]);
  ofile = strrchr(argv[1], '/') + 1;

  ndir = open_dir(argv[2]);
  nfile = strrchr(argv[2], '/') + 1;

  status = linkat(odir, ofile, ndir, nfile, AT_SYMLINK_FOLLOW);
if (status) {
  perror("linkat failed");
}


  return 0;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.