Linux 为什么 mmap() 会因文件复制程序的目标文件的权限被拒绝而失败?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17202741/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 23:15:54  来源:igfitidea点击:

Why does mmap() fail with permission denied for the destination file of a file copy program?

clinuxfile-iocopymmap

提问by James Russell

I'd like to give a try at copying the contents of a file over to another one by using memory mapped I/O in Linux via mmap(). The intention is to check by myself if that's better than using fread()and fwrite()and how would it deal with big files (like couple of GiBs for example, since the file is read whole I want to know if I need to have such amount of memory for it).

我想尝试通过在 Linux 中使用内存映射 I/O 将文件的内容复制到另一个文件mmap()。目的是自己检查这是否比使用更好fread()fwrite()以及它如何处理大文件(例如几个 GiB,因为文件是完整读取的,我想知道我是否需要为它提供如此多的内存)。

This is the code I'm working with right now:

这是我现在正在使用的代码:

// Open original file descriptor:
int orig_fd = open(argv[1], O_RDONLY);
// Check if it was really opened:
if (orig_fd == -1) {
    fprintf(stderr, "ERROR: File %s couldn't be opened:\n", argv[1]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    exit(EX_NOINPUT);
}
// Idem for the destination file:
int dest_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
// Check if it was really opened:
if (dest_fd == -1) {
    fprintf(stderr, "ERROR: File %s couldn't be opened:\n", argv[2]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close original file descriptor too:
    close(orig_fd);
    exit(EX_CANTCREAT);
}

// Acquire file size:
struct stat info = {0};
if (fstat(orig_fd, &info)) {
    fprintf(stderr, "ERROR: Couldn't get info on %s:\n", argv[1]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors:
    close(orig_fd);
    close(dest_fd);
    exit(EX_IOERR);
}
// Set destination file size:
if (ftruncate(dest_fd, info.st_size)) {
    fprintf(stderr, "ERROR: Unable to set %s file size:\n", argv[2]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors:
    close(orig_fd);
    close(dest_fd);
    exit(EX_IOERR);
}

// Map original file and close its descriptor:
char *orig = mmap(NULL, info.st_size, PROT_READ, MAP_PRIVATE, orig_fd, 0);
if (orig == MAP_FAILED) {
    fprintf(stderr, "ERROR: Mapping of %s failed:\n", argv[1]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors:
    close(orig_fd);
    close(dest_fd);
    exit(EX_IOERR);
}
close(orig_fd);
// Map destination file and close its descriptor:
char *dest = mmap(NULL, info.st_size, PROT_WRITE, MAP_SHARED, dest_fd, 0);
if (dest == MAP_FAILED) {
    fprintf(stderr, "ERROR: Mapping of %s failed:\n", argv[2]);
    fprintf(stderr, "%d - %s\n", errno, strerror(errno));
    // Close file descriptors and unmap first file:
    munmap(orig, info.st_size);
    close(dest_fd);
    exit(EX_IOERR);
}
close(dest_fd);

// Copy file contents:
int i = info.st_size;
char *read_ptr = orig, *write_ptr = dest;
while (--i) {
    *write_ptr++ = *read_ptr++;
}

// Unmap files:
munmap(orig, info.st_size);
munmap(dest, info.st_size);

I think it may be a way of doing it but I keep getting an error trying to map the destination file, concretely code 13 (permission denied).

我认为这可能是一种方法,但我一直在尝试映射目标文件时出错,具体是代码 13(权限被拒绝)。

I don't have a clue on why is it failing, I can write to that file since the file gets created and all and the file I'm trying to copy is just a couple of KiBs in size.

我不知道它为什么会失败,我可以写入该文件,因为文件已创建,并且我尝试复制的所有文件的大小只有几个 KiB。

Can anybody spot the problem? How come I had permission to map the original file but not the destination one?

任何人都可以发现问题吗?为什么我有权限映射原始文件而不是目标文件?

NOTE:If anyone is to use the loop to copy bytes posted in the question instead of memcpyfor example, the loop condition should be i--instead to copy all contents. Thanks to jxh for spotting that.

注意:如果有人要使用循环来复制问题中发布的字节而不是memcpy例如,则循环条件应该是i--复制所有内容。感谢 jxh 发现这一点。

采纳答案by jxh

From the mmap()man page:

mmap()手册页:

EACCES
A file descriptor refers to a non-regular file. Or MAP_PRIVATE was requested, but fd is not open for reading. Or MAP_SHARED was requested and PROT_WRITE is set, but fd is not open in read/write (O_RDWR) mode. Or PROT_WRITE is set, but the file is append-only.

EACCES
文件描述符是指非常规文件。或者 MAP_PRIVATE 被请求,但 fd 没有打开读取。或者请求了 MAP_SHARED 并设置了 PROT_WRITE,但 fd 未在读/写 (O_RDWR) 模式下打开。或者设置了 PROT_WRITE,但文件是仅附加的。

You are opening your destination file with O_WRONLY. Use O_RDWRinstead.

您正在打开目标文件O_WRONLY。使用O_RDWR来代替。

Also, you should use memcpyto copy the memory rather than using your own loop:

此外,您应该使用memcpy复制内存而不是使用您自己的循环:

memcpy(dest, orig, info.st_size);

Your loop has an off by 1 bug.

您的循环有 1 个错误。

回答by rptb1

This works for me. Note that I had to open the destination O_RDWR. I suspect the kernel attempts to map whole pages from the file into memory (reading it) because you're updating it a byte or word at a time, and that might not change the whole page.

这对我有用。请注意,我必须打开目标 O_RDWR。我怀疑内核试图将整个页面从文件映射到内存(读取它),因为您一次更新一个字节或一个字,而这可能不会更改整个页面。

A couple of other points:

其他几点:

  1. You don't need to close and unmap stuff on error if you're just going to exit.

  2. Use memcpy and don't write your own byte-copying loop. Memcpy will be a lot better optimised in general. (Though it's not always the absolute best.)

  3. You might want to read the source code to FreeBSD's "cp" utility. Take a look here and search for the use of mmap. http://svnweb.freebsd.org/base/stable/9/bin/cp/utils.c?revision=225736&view=markup

  1. 如果您只是要退出,则无需在出错时关闭和取消映射内容。

  2. 使用 memcpy 并且不要编写自己的字节复制循环。Memcpy 通常会得到更好的优化。(虽然它并不总是绝对最好的。)

  3. 您可能想阅读 FreeBSD 的“cp”实用程序的源代码。看看这里并搜索mmap的使用。http://svnweb.freebsd.org/base/stable/9/bin/cp/utils.c?revision=225736&view=markup



#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
        int s, d;
        struct stat st;
        void *sp, *dp;
        s = open(argv[1], O_RDONLY);
        if (s == -1) {
                perror("open source");
                exit(1);
        }
        d = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644);
        if (d == -1) {
                perror("open destintation");
                exit(1);
        }
        if (fstat(s, &st)) {
                perror("stat source");
                exit(1);
        }
        if (ftruncate(d, st.st_size)) {
                perror("truncate destination");
                exit(1);
        }
        sp = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, s, 0);
        if (sp == MAP_FAILED) {
                perror("map source");
                exit(1);
        }
        dp = mmap(NULL, st.st_size, PROT_WRITE | PROT_READ, MAP_SHARED, d, 0);
        if (dp == MAP_FAILED) {
                perror("map destintation");
                exit(1);
        }
        memcpy(dp, sp, st.st_size);
        return 0;
}

回答by CancerSoftware

Original File: O_RDONLY open, MAP_PRIVATE mmap

原始文件:O_RDONLY 打开,MAP_PRIVATE mmap

destination file: O_WRONLY open, MAP_SHARED mmap

目标文件:O_WRONLY 打开,MAP_SHARED mmap

You need to open with O_RDWR flag for using MAP_SHARED.

您需要使用 O_RDWR 标志打开才能使用 MAP_SHARED。

Don't you actually need to do MAP_FILE | MAP_SHARED ?

你真的不需要做 MAP_FILE | MAP_SHARED ?