Điều gì xảy ra với một xử lý tệp đang mở trên Linux nếu tệp trỏ bị di chuyển hoặc bị xóa


107

Điều gì xảy ra với một xử lý tệp đang mở trên Linux nếu tệp trỏ trong khi đó nhận được:

  • Đã chuyển đi -> Xử lý tệp có còn hợp lệ không?
  • Đã xóa -> Điều này có dẫn đến EBADF, chỉ ra một tệp xử lý không hợp lệ?
  • Được thay thế bởi một tệp mới -> Xử lý tệp có trỏ đến tệp mới này không?
  • Được thay thế bằng một liên kết cứng đến một tệp mới -> Tệp của tôi có xử lý "theo" liên kết này không?
  • Được thay thế bằng một liên kết mềm thành một tệp mới -> Trình xử lý tệp của tôi hiện có trúng tệp liên kết mềm này không?

Tại sao tôi lại đặt những câu hỏi như vậy: Tôi đang sử dụng phần cứng cắm nóng (chẳng hạn như thiết bị USB, v.v.). Có thể xảy ra trường hợp thiết bị (và cả / dev / tệp của nó) bị người dùng hoặc một Gremlin khác gắn lại.

Cách tốt nhất để giải quyết vấn đề này là gì?

Câu trả lời:


159

Nếu tệp được di chuyển (trong cùng một hệ thống tệp) hoặc đổi tên, thì trình xử lý tệp vẫn mở và vẫn có thể được sử dụng để đọc và ghi tệp.

Nếu tệp bị xóa, xử lý tệp vẫn mở và vẫn có thể được sử dụng (Đây không phải là điều mà một số người mong đợi). Tệp sẽ không thực sự bị xóa cho đến khi chốt cuối cùng được đóng lại.

Nếu tệp được thay thế bằng tệp mới, nó phụ thuộc chính xác như thế nào. Nếu nội dung của tệp bị ghi đè, trình xử lý tệp sẽ vẫn hợp lệ và truy cập nội dung mới. Nếu tệp hiện có được hủy liên kết và tệp mới được tạo có cùng tên hoặc, nếu tệp mới được di chuyển vào tệp hiện có bằng cách sử dụng rename(), thì nó cũng giống như xóa (xem ở trên) - nghĩa là, trình xử lý tệp sẽ tiếp tục tham chiếu các gốc phiên bản của tập tin.

Nói chung, một khi tệp được mở, tệp sẽ mở và không ai thay đổi cấu trúc thư mục có thể thay đổi điều đó - họ có thể di chuyển, đổi tên tệp hoặc đặt thứ gì khác vào vị trí của nó, nó chỉ đơn giản là vẫn mở.

Trong Unix không có lệnh xóa, duy nhất unlink(), điều này có ý nghĩa vì nó không nhất thiết phải xóa tệp - chỉ xóa liên kết khỏi thư mục.


Mặt khác, nếu thiết bị bên dưới biến mất (ví dụ: rút USB) thì trình xử lý tệp sẽ không còn hợp lệ nữa và có khả năng gây ra lỗi IO / trên bất kỳ hoạt động nào. Bạn vẫn phải đóng nó lại. Điều này sẽ đúng ngay cả khi thiết bị đã được cắm lại, vì không hợp lý để giữ một tệp mở trong trường hợp này.


Tôi cho rằng điểm thứ hai của bạn cũng áp dụng như nhau nếu một thư mục chứa tệp bị xóa. Là vậy sao?
Drew Noakes

2
Tôi quan tâm đến một điều: nếu bạn sử dụng lệnh cp để ghi đè lên một tệp, đó là trường hợp đầu tiên hay trường hợp thứ hai?
xuhdev

1
" Tập tin sẽ không thực sự bị xóa cho đến khi chốt cuối cùng được đóng lại. " cảm ơn
Geremia

8

Các xử lý tệp trỏ tới một nút chứ không phải đến một đường dẫn, vì vậy hầu hết các trường hợp của bạn vẫn hoạt động như bạn giả định, vì xử lý vẫn trỏ đến tệp.

Cụ thể, với kịch bản xóa - chức năng được gọi là "hủy liên kết" vì một lý do nào đó, nó phá hủy "liên kết" giữa tên tệp (răng giả) và tệp. Khi bạn mở một tệp, sau đó hủy liên kết tệp đó, tệp thực sự vẫn tồn tại cho đến khi số lượng tham chiếu của nó bằng 0, đó là khi bạn đóng chốt điều khiển.

Chỉnh sửa: Trong trường hợp phần cứng, bạn đã mở một chốt điều khiển đến một nút thiết bị cụ thể, nếu bạn rút phích cắm của thiết bị, hạt nhân sẽ không thể truy cập vào nó, ngay cả khi thiết bị quay trở lại. Bạn sẽ phải đóng thiết bị và mở lại.


5

Tôi không chắc về các thao tác khác, nhưng đối với thao tác xóa: Việc xóa đơn giản không diễn ra (về mặt vật lý, tức là trong hệ thống tệp) cho đến khi chốt mở cuối cùng của tệp được đóng lại. Do đó, không thể xóa tệp khỏi ứng dụng của bạn.

Một số ứng dụng (không nghĩ đến) dựa vào hành vi này, bằng cách tạo, mở và xóa ngay lập tức các tệp, tệp này sau đó tồn tại chính xác miễn là ứng dụng - cho phép các ứng dụng khác biết về vòng đời của ứng dụng đầu tiên mà không cần nhìn vào bản đồ quy trình và như vậy.

Có thể các cân nhắc tương tự cũng áp dụng cho những thứ khác.


4

Nếu bạn muốn kiểm tra xem trình xử lý tệp (bộ mô tả tệp) có ổn không, bạn có thể gọi hàm này.

/**
 * 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
Điểm if(!fcntl(fd, F_GETFL)) {kiểm tra là gì? Tôi đoán bạn đang tìm kiếm EBADFở đó. (Bạn cũng có thể quên khởi tạo errnothành 0).
woky

Đây không phải là công việc cho tôi. Tôi đã cố gắng sử dụng cách tiếp cận này với open(O_WRONLY|O_APPEND)- st_nlink luôn ở lại> = 1 trong khi bộ mô tả của tôi được mở.
Embedearr

2

Thông tin trong bộ nhớ của tệp đã xóa (tất cả các ví dụ bạn đưa ra là các phiên bản của tệp đã xóa) cũng như các inodes trên đĩa vẫn tồn tại cho đến khi tệp bị đóng.

Phần cứng được hotplugged là một vấn đề hoàn toàn khác nhau, và bạn không nên mong đợi chương trình của bạn để sống lâu nếu inode trên đĩa hoặc siêu dữ liệu đã thay đổi chút nào .


2

Thí nghiệm sau đây cho thấy câu trả lời của MarkR là đúng.

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;
}

dữ liệu:

1234
1234
1234
1234
1234

Sử dụng gcc code.cđể sản xuất a.out. Chạy đi ./a.out. Khi bạn thấy kết quả sau:

line: 1234

Sử dụng rm datađể xóa data. Nhưng ./a.outsẽ tiếp tục chạy mà không có lỗi và tạo ra toàn bộ kết quả sau:

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

Tôi đã thực hiện thử nghiệm trên Ubuntu 16.04.3.


1

Trong thư mục / proc /, bạn sẽ tìm thấy danh sách mọi quy trình hiện đang hoạt động, chỉ cần tìm PID của bạn và tất cả dữ liệu liên quan đều có ở đó. Một thông tin liên quan là thư mục fd /, bạn sẽ tìm thấy tất cả các trình xử lý tệp hiện đang được mở bởi quá trình này.

Cuối cùng, bạn sẽ tìm thấy một liên kết tượng trưng đến thiết bị của mình (theo / dev / hoặc thậm chí / proc / bus / usb /), nếu thiết bị bị treo, liên kết sẽ chết và không thể làm mới tay cầm này, quá trình phải đóng và mở lại (ngay cả khi kết nối lại)

Mã này có thể đọc trạng thái hiện tại của liên kết PID của bạn

#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;
}

Mã cuối cùng này rất đơn giản, bạn có thể chơi với chức năng 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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.