c - 文件锁定 + Fscanf/Lseek

标签 c locking file-descriptor lseek

我有一个名为“input.txt” 的文件,其中包含一些值。我正在编写程序,它将在该文件中找到最小值,并将该最小值替换为作为命令行参数给出的数字 - 如果该命令行参数大于最小值。这些值代表室温,因此可以使用该事实来找到最小值。此外,文件的那部分(新数字替换最小值的地方)需要被锁定。

示例:

$ ./prog 23

FILE: 21 25 19 22 24

FILE: 21 25 23 22 24

$ ./prog 29

FILE: 21 25 23 22 24

FILE: 29 25 23 22 24

代码:

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

/* Function that handles errors */
void fatal_error(const char *message){

    perror(message);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv){

    if(argc != 2)
        fatal_error("Bad arguments.\n");

    /* Fetching command line argument */
    int temp = atoi(argv[1]);

    /* Opening file and checking for errors */
    FILE *file = fopen("input.txt", "r+");
    if(!file)
        fatal_error("Unable to open file.\n");

    /* Finding minimum in the file */
    int min = 200;
    int value;
    while(fscanf(file, "%d", &value) != EOF)
        if(value < min)
            min = value;

    /* Exiting if nothing needs to change */
    if(temp <= min)
        return 0;

    /* Creating file descriptor from stream and checking for errors */
    int fdOpen = fileno(file); 
    if(fdOpen == -1)
        fatal_error("Unable to open file descriptor.\n");

    /* Moving offset to the beginning of the file */
    off_t of = lseek(fdOpen,0,SEEK_SET);
    printf("Ofset pre petlje: %jd\n", (intmax_t)of);
    while(1){

        /* I'm reading file all over again */
        if(fscanf(file, "%d", &value) == EOF)
            fatal_error("Reached end of file.\n");

        /* If I reached minimum */
        if(value ==  min){

            /* I lock that part of the file - temperatures are two digit numbers*/
            struct flock lock;
            lock.l_type = F_WRLCK;
            lock.l_whence = SEEK_CUR;
            lock.l_start = -2;
            lock.l_len = 2;

            /* I create lock */
            if(fcntl(fdOpen,F_SETLK, &lock) == -1){

                if(errno == EACCES || errno == EAGAIN)
                    fatal_error("File is locked.\n");
            }

            /* Moving two positions back from current position */
            off_t offset;
            if((offset = lseek(fdOpen, -2, SEEK_CUR)) == -1)
                fatal_error("lseek error.\n");

            /* Inserting read command line value into file */
            fprintf(file, "%d", temp);

            /* Unlocking */
            lock.l_type = F_UNLCK;
            if(fcntl(fdOpen, F_SETLK, &lock) == -1)
                fatal_error("Unable to destroy lock.\n");

            /* Closing file descriptor, and breaking loop */
            close(fdOpen);
            break;
        }
    }
    return 0;
}

然而,这不起作用。文件不会改变。问题是——我知道这是正确的做法。我编写了基本上相同的程序,例如将单词 “aa” 的每次出现更改为 “bb”。基本是同一个概念。

我试过:

  • 我尝试在 fscanf() 之前和之后获取偏移量 while() 循环。在第一个 fscanf 偏移设置为 0 之前 - 开始 的文件。在第一个 fscanf 偏移量设置到文件末尾之后, 每次迭代后偏移量保留在文件末尾。
  • 我已经尝试在这些相同的 printf()ftell() 中使用 ftell() 给我正确的偏移量。然而,while 循环中的 ftell() 仍然 给出文件结尾。我也尝试过使用 fseek() 而不是 lseek()(即使我知道 fseek() 在它的代码中使用了 lseek() 执行)。结果还是一样。
  • 我试过使用 char* 缓冲区而不是值。基本上使用 fscanf(file,"%s",buffer),转换该值以检查是否读取 值(value)是最小的,之后我用过 fprintf(file,"%s",buffer) 来写。但结果相同。
  • 我试过锁定整个文件(我想 - 也许有 问题),但是,结果相同。

代码:

这是我提到的第二个程序,它使用相同的概念。此代码有效,但我也尝试在此处打印偏移量,并且偏移量也在文件末尾。

int main(int argc, char **argv){

    if(argc != 4)
        fatal_error("You must enter exactly 4 arguments.\n");

    FILE *f = fopen(argv[1], "r+");
    if(!f)
        fatal_error("Unable to open file for reading and writing.\n");

    int fd = fileno(f);
    if(fd == -1)
        fatal_error("Unable to fetch file descriptor for file.\n");

    char word[MAX_LEN + 1];
    int word_len = strlen(argv[2]);
    while(fscanf(f,"%s",word) != EOF){

        printf("%jd\n", lseek(fd,0,SEEK_CUR));
        if(!strcmp(argv[2],word)){

            struct flock lock;
            lock.l_type = F_WRLCK;
            lock.l_whence = SEEK_CUR;
            lock.l_start = -word_len;
            lock.l_len = word_len;

            if(fcntl(fd, F_SETLKW, &lock) == -1)
                fatal_error("File locking failed.\n");

            if(lseek(fd, -word_len, SEEK_CUR) == -1)
                fatal_error("Lseek error.\n");
            fprintf(f, "%s", argv[3]);

            lock.l_type = F_UNLCK;
            if(fcntl(fd, F_SETLK, &lock) == -1)
                fatal_error("Failed to release lock.\n");
        }
    }
}

如您所见,这是完全相同的概念。第二个程序有效,第一个无效。

我现在很迷茫。如果我从文件流创建文件描述符,然后在该文件描述符上使用 lseek() 来更改偏移量,流的偏移量是否也会更改?如果您使用 fscanf() 从流中读取内容,offset_t 的变化是否与您从文件中读取的一样多?如果我将 fscanf() 与格式说明符 %d%s 一起使用,更改偏移量有什么不同吗?

最佳答案

您希望替换值的方式在(源文本和替换文本)在您的情况下具有相同长度的情况下有效 lenght(aa)==length(bb)。主要是您应该小心使用 FILE*int fd 描述符,并始终在退出前关闭文件。

fclose(f) 之前调用close(fd) 将导致缓冲数据不被写入。

另一个问题 - 相对于 SEEK_CUR 锁定文件区域不会锁定要修改的文件部分

这里你有一些修改后的代码:

int main(int argc, char **argv){

    if(argc != 4)
        fatal_error("You must enter exactly 3 arguments.\n");

    if(strlen(argv[2])!=strlen(argv[3]))
        fatal_error("src&dst words must be the length.\n");

    FILE *f = fopen(argv[1], "r+");
    if(!f)
        fatal_error("Unable to open file for reading and writing.\n");

    int fd = fileno(f);
    if(fd == -1)
        fatal_error("Unable to fetch file descriptor for file.\n");

    char word[MAX_LEN + 1];
    int word_len = strlen(argv[2]);
    while(fscanf(f,"%s",word) != EOF){

        printf("%jd\n", ftell(f));
        if(!strcmp(argv[2],word)){

            struct flock lock;
            lock.l_type = F_WRLCK;
            lock.l_whence = SEEK_SET;
            lock.l_start = ftell(f)-word_len;
            lock.l_len = word_len;

            if(fcntl(fd, F_SETLKW, &lock) == -1)
                fatal_error("File locking failed.\n");

            fseek(f,-word_len,SEEK_CUR); //FILE* based seek
            fprintf(f, "%s", argv[3]);
            fflush(f); //sync output

            lock.l_type = F_UNLCK;
            if(fcntl(fd, F_SETLK, &lock) == -1)
                fatal_error("Failed to release lock.\n");
        }
    }
    fclose(f); // close the file
}

Update1:FILE接口(interface)有自己的缓冲,与int fd不同步。 所以主要问题是使用l​​seek,而应该使用fseek

Update2:循环寻找最小值的代码

int main(int argc, char **argv){

    if(argc != 3)
        fatal_error("You must enter exactly 2 arguments.\n");

    if(strlen(argv[2]) != 2)
        fatal_error("replace num must have 2 digits.\n");

    FILE *f = fopen(argv[1], "r+");
    if(!f)
        fatal_error("Unable to open file for reading and writing.\n");

    int fd = fileno(f);
    if(fd == -1)
        fatal_error("Unable to fetch file descriptor for file.\n");

    // search for minimum
    int word_len = strlen(argv[2]);
    int value, minValue;
    long minOffs=-1;
    while(fscanf(f,"%d",&value) == 1){ //compare number of parsed items
        printf("test value %d\n", value);
        if (minValue > value) {
            minValue = value;
            minOffs = ftell(f) - word_len;
        }
    }

    // replace if found
    if (minOffs >= 0) {
        printf("replacing v=%d at %ld\n", minValue, minOffs);
        struct flock lock;
        memset(&lock, 0, sizeof(lock));
        lock.l_type = F_WRLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = minOffs;
        lock.l_len = word_len;

        fseek(f,minOffs,SEEK_SET);
        if(fcntl(fd, F_SETLK, &lock) == -1)
            fatal_error("File locking failed.\n");

        fprintf(f, "%s", argv[2]);
        fflush(f); //sync output

        lock.l_type = F_UNLCK;
        if(fcntl(fd, F_SETLK, &lock) == -1)
            fatal_error("Failed to release lock.\n");
    }
    fclose(f);
}

关于c - 文件锁定 + Fscanf/Lseek,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37896664/

相关文章:

.net - 网络掉线时在网络上使用 FileStream

c++ - 为什么锁定会减慢顺序文件解析器的速度?

C 编程 - 如何使用 While 循环将用户的值保存在数组中

c - 显示先前接收到的 UART 值

有人可以解释一下 C 语言中 gets() 的返回值吗?

Java - 适用于 Windows 的 getOpenFileDescriptorCount

unix - 如何在两个进程之间安全地共享文件描述符?

c++ - 指针算术(不是一个问题,而是一种测验)

c++ - move std::vector 的 std::unique_locks

c - 服务器/客户端消息在 C 中传递