web-dev-qa-db-ja.com

ファイルコピープログラムの宛先ファイルのアクセス許可が拒否されてmmap()が失敗するのはなぜですか?

Linuxでmmap()を介してメモリマップドI/Oを使用して、ファイルの内容を別のファイルにコピーしてみます。 fread()fwrite()を使用するよりも、それが優れているかどうかを自分で確認し、大きなファイル(たとえば、ファイル全体が読み取られるため、いくつかのGiBなど)をどのように処理するかを確認することを目的としています。そのためにそのような量のメモリが必要かどうかを知りたい)。

これは私が今使っているコードです:

// 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);

それはそれを行う方法かもしれないと思いますが、宛先ファイル、具体的にはコード13(許可が拒否されました)をマップしようとするとエラーが発生し続けます。

なぜ失敗するのかわかりません。ファイルが作成され、コピーしようとしているファイルのサイズが数KiBであるため、そのファイルに書き込むことができます。

誰かが問題を見つけることができますか?元のファイルをマップする許可を得たが、宛先ファイルをマップする許可がなかったのはなぜですか?

注:たとえば、質問に投稿されたバイトをmemcpyの代わりにループを使用してコピーする場合、すべてのコンテンツをコピーする代わりに、ループ条件をi--にする必要があります。それを見つけてくれたjxhに感謝します。

12
James Russell

mmap()のマニュアルページから:

EACCES
ファイル記述子は非正規ファイルを参照します。または、MAP_PRIVATEが要求されましたが、fdは読み取り用に開かれていません。または、MAP_SHAREDが要求され、PROT_WRITEが設定されていますが、fdが読み取り/書き込み(O_RDWR)モードで開かれていません。または、PROT_WRITEが設定されていますが、ファイルは追加専用です。

宛先ファイルをO_WRONLYで開いています。代わりにO_RDWRを使用してください。

また、独自のループを使用するのではなく、memcpyを使用してメモリをコピーする必要があります。

memcpy(dest, orig, info.st_size);

ループには1つのバグがあります。

12
jxh

元のファイル:O_RDONLYオープン、MAP_PRIVATE mmap

宛先ファイル:O_WRONLYオープン、MAP_SHARED mmap

MAP_SHAREDを使用するには、O_RDWRフラグを使用して開く必要があります。

実際にMAP_FILEを実行する必要はありません| MAP_SHARED?

1
CancerSoftware

これは私にとってはうまくいきます。宛先O_RDWRを開かなければならなかったことに注意してください。一度に1バイトまたはWordを更新しているため、カーネルがファイルからメモリにページ全体をマップ(読み取り)しようとしていると思われますが、ページ全体が変更されない可能性があります。

他のいくつかのポイント:

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