จะเกิดอะไรขึ้นกับตัวจัดการไฟล์แบบเปิดบน Linux หากไฟล์ปลายแหลมถูกย้ายหรือถูกลบ


107

จะเกิดอะไรขึ้นกับตัวจัดการไฟล์ที่เปิดอยู่บน Linux หากไฟล์ปลายแหลมได้รับ:

  • ย้ายออกไป -> ที่จับไฟล์ยังคงใช้ได้หรือไม่
  • ลบ -> สิ่งนี้นำไปสู่ ​​EBADF ซึ่งบ่งชี้ว่าไฟล์จัดการไม่ถูกต้องหรือไม่?
  • แทนที่ด้วยไฟล์ใหม่ -> จัดการไฟล์ที่ชี้ไปที่ไฟล์ใหม่นี้หรือไม่
  • แทนที่ด้วยฮาร์ดลิงก์ไปยังไฟล์ใหม่ -> ไฟล์ของฉันจัดการ "ตาม" ลิงก์นี้หรือไม่
  • แทนที่ด้วยซอฟต์ลิงก์ไปยังไฟล์ใหม่ -> ไฟล์ของฉันจัดการกับไฟล์ซอฟต์ลิงก์นี้หรือไม่

ทำไมฉันถึงถามคำถามเช่นนี้: ฉันใช้ฮาร์ดแวร์แบบเสียบปลั๊กไฟ (เช่นอุปกรณ์ USB เป็นต้น) อาจเกิดขึ้นได้ที่อุปกรณ์ (รวมถึง / dev / file) ได้รับการแนบซ้ำโดยผู้ใช้หรือ Gremlin อื่น

แนวทางปฏิบัติที่ดีที่สุดในการจัดการกับสิ่งนี้คืออะไร?

คำตอบ:


159

หากไฟล์ถูกย้าย (ในระบบไฟล์เดียวกัน) หรือเปลี่ยนชื่อแฮนเดิลไฟล์จะยังคงเปิดอยู่และยังสามารถใช้อ่านและเขียนไฟล์ได้

หากไฟล์ถูกลบแฮนเดิลไฟล์จะยังคงเปิดอยู่และยังสามารถใช้งานได้ (นี่ไม่ใช่สิ่งที่บางคนคาดหวัง) ไฟล์จะไม่ถูกลบจริง ๆ จนกว่าแฮนเดิลสุดท้ายจะถูกปิด

หากไฟล์ถูกแทนที่ด้วยไฟล์ใหม่มันขึ้นอยู่กับวิธีการ หากเนื้อหาของไฟล์ถูกเขียนทับการจัดการไฟล์จะยังคงถูกต้องและเข้าถึงเนื้อหาใหม่ได้ หากไฟล์ที่มีอยู่ถูกยกเลิกการเชื่อมโยงและไฟล์ใหม่ที่สร้างขึ้นโดยใช้ชื่อเดียวกันหรือหากไฟล์ใหม่ถูกย้ายไปยังไฟล์ที่มีอยู่โดยใช้ไฟล์rename()นั้นจะเหมือนกับการลบ (ดูด้านบน) นั่นคือแฮนเดิลไฟล์จะอ้างอิงต่อไปเดิมรุ่นของแฟ้ม

โดยทั่วไปเมื่อเปิดไฟล์แล้วไฟล์จะเปิดขึ้นและไม่มีใครเปลี่ยนโครงสร้างไดเร็กทอรีที่สามารถเปลี่ยนแปลงได้ - พวกเขาสามารถย้ายเปลี่ยนชื่อไฟล์หรือวางอย่างอื่นแทนได้ แต่ก็ยังคงเปิดอยู่

ใน Unix ไม่มีการลบเพียงอย่างเดียวunlink()ซึ่งสมเหตุสมผลเนื่องจากไม่จำเป็นต้องลบไฟล์เพียงแค่ลบลิงก์ออกจากไดเร็กทอรี


หากในทางกลับกันอุปกรณ์พื้นฐานหายไป (เช่นถอดปลั๊ก USB) ที่จับไฟล์จะไม่ถูกต้องอีกต่อไปและมีแนวโน้มที่จะให้ IO / error ในการดำเนินการใด ๆ คุณยังคงต้องปิดมัน สิ่งนี้จะเป็นจริงแม้ว่าจะเสียบอุปกรณ์กลับเข้าไปก็ตามเนื่องจากไม่เหมาะสมที่จะเปิดไฟล์ไว้ในกรณีนี้


ฉันคิดว่าจุดที่สองของคุณใช้เท่า ๆ กันหากไดเร็กทอรีที่มีไฟล์ถูกลบ ขนาดนั้นเลยเหรอ
Drew Noakes

2
ฉันสนใจสิ่งหนึ่ง: ถ้าคุณใช้คำสั่ง cp เพื่อเขียนทับไฟล์มันเป็นกรณีแรกหรือกรณีที่สอง?
xuhdev

1
" ไฟล์จะไม่ถูกลบจริงๆจนกว่าแฮนเดิลสุดท้ายจะถูกปิด " ที่น่าสนใจ ขอบคุณ
Geremia

8

ที่จับไฟล์จะชี้ไปที่ไอโหนดไม่ใช่เส้นทางดังนั้นสถานการณ์ส่วนใหญ่ของคุณจะยังคงทำงานตามที่คุณคิดไว้เนื่องจากจุดจับยังคงชี้ไปที่ไฟล์

โดยเฉพาะอย่างยิ่งกับสถานการณ์การลบ - ฟังก์ชันนี้เรียกว่า "ยกเลิกการเชื่อมโยง" ด้วยเหตุผลใดก็ตามฟังก์ชันนี้จะทำลาย "ลิงก์" ระหว่างชื่อไฟล์ (Dentry) และไฟล์ เมื่อคุณเปิดไฟล์แล้วยกเลิกการเชื่อมโยงไฟล์นั้นจะยังคงอยู่จนกว่าจำนวนการอ้างอิงจะเป็นศูนย์ซึ่งก็คือเมื่อคุณปิดที่จับ

แก้ไข:ในกรณีของฮาร์ดแวร์คุณได้เปิดแฮนเดิลไปยังโหนดอุปกรณ์เฉพาะหากคุณถอดปลั๊กอุปกรณ์เคอร์เนลจะไม่สามารถเข้าถึงได้ทั้งหมดแม้ว่าอุปกรณ์จะกลับมาก็ตาม คุณจะต้องปิดอุปกรณ์และเปิดใหม่


5

ฉันไม่แน่ใจเกี่ยวกับการดำเนินการอื่น ๆ แต่สำหรับการลบ: การลบจะไม่เกิดขึ้น (ทางกายภาพเช่นในระบบไฟล์) จนกว่าแฮนเดิลที่เปิดสุดท้ายของไฟล์จะถูกปิด ดังนั้นจึงไม่ควรลบไฟล์ออกจากแอปพลิเคชันของคุณ

แอพบางตัว (ที่ไม่ได้คำนึงถึง) อาศัยพฤติกรรมนี้โดยการสร้างเปิดและลบไฟล์ทันทีซึ่งจะอยู่ได้นานพอ ๆ กับแอปพลิเคชัน - ทำให้แอปพลิเคชันอื่น ๆ สามารถรับรู้วงจรชีวิตของแอปแรกได้โดยไม่จำเป็นต้อง ดูแผนที่กระบวนการและอื่น ๆ

การพิจารณาที่คล้ายกันนี้อาจนำไปใช้กับสิ่งอื่น ๆ ได้


4

หากคุณต้องการตรวจสอบว่าตัวจัดการไฟล์ (file descriptor) ใช้ได้หรือไม่คุณสามารถเรียกใช้ฟังก์ชันนี้ได้

/**
 * 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นั่น (คุณอาจลืมเริ่มต้นerrnoเป็น 0)
woky

นี่ไม่ได้ผลสำหรับฉัน ฉันได้พยายามใช้แนวทางนี้กับopen(O_WRONLY|O_APPEND)- st_nlink อยู่เสมอ> = 1 ในขณะที่ตัวอธิบายของฉันเปิดอยู่
imbearr

2

ข้อมูลในหน่วยความจำของไฟล์ที่ถูกลบ (ตัวอย่างทั้งหมดที่คุณให้คืออินสแตนซ์ของไฟล์ที่ถูกลบ) รวมถึง inodes บนดิสก์จะยังคงอยู่จนกว่าไฟล์จะถูกปิด

อุปกรณ์ถูกถอดออกเป็นปัญหาที่แตกต่างกันอย่างสมบูรณ์และคุณไม่ควรคาดหวังว่าโปรแกรมของคุณจะมีชีวิตอยู่นานถ้า inodes บนดิสก์หรือเมตาดาต้าที่มีการเปลี่ยนแปลงอยู่ตลอด


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.