なぜこれが起こるのですか: "cp image.bin /dev/mapper/loop0p1"?

なぜこれが起こるのですか: "cp image.bin /dev/mapper/loop0p1"?

これがうまくいくのはなぜですか?

cp image.bin /dev/mapper/loop0p1

image.binパーティションイメージです。

試してみましたが、うまくいきますが、なぜですか? aを使用してはいけませんかdd

答え1

GNU coreutilsはcpそのように書かれているのでうまくいきます。ブロックデバイスへの書き込みは複雑ではなく、基本的に通常のファイルへの書き込みと同じ作業です。

ただし、このアプローチを使用しないでくださいcp。 GNU coreutilsが機能すると確信している場合はこれを行うことができます。しかし、他の特徴もありますcp。たとえば、busybox cpデバイスへの書き込みをまったくサポートせずにデバイスノードのリンクを解除(削除)し、その場所に新しいファイルを作成します。

ddこの点ではより安全です。これはデバイスに書き込むように設計されており、デバイスを操作するときに頻繁に必要なツール(bs、ナビゲーション、スキップ、計算...)を提供します。すべてのスタイルで期待どおりに機能する必要がありますdd

答え2

あなたの期待はプログラム/システム設計とは異なります。これについて何を言うことができますか? :-)

「すべてはファイルだ」…

cpおよび/またはddを実行して、strace -e trace=open通常のファイルとブロックデバイスへのシステムコールが同じであることを確認できます。システムコールがそれらを区別できない場合、なぜcpは気にする必要がありますか?

答え3

私が知る限り、するcpことは

  • 書き込みモードでターゲットファイルを開く
  • ソースファイルからターゲットファイルへのデータの書き込み(ブロックサイズはわかりませんが、詳細のみです)

通常のファイルの場合、これは次のようになります。

  • 各書き込み呼び出しに応じて大きくなる新しいファイルを生成します。
  • 上書き既存のファイルの上書き:
    最初の書き込み呼び出しはファイルの内容を消去し、新しいデータをファイルに入れます。その時点から、ファイルは新しいファイルのように大きくなります。

これで、/dev/mapper/* はブロックデバイス(または具体的にはブロックデバイスへのシンボリックリンク)です。これには静的ファイルサイズがあります。したがって、これらのファイルを開くと、各write()呼び出しは転送された宛先ファイルのnバイトを上書きします(どこにも呼び出しがないと仮定fseek())。

それでは、私たち自身の貧しい人々のCPを書いてみましょう。

#include <stdlib.h>
#include <stdio.h>

void usage() {fprintf(stderr, "Usage: cp <srcFile> <tgtFile>\n"); exit(1);}
void error(const char *msg) {fprintf(stderr, "Error: %s\n", msg); exit(2);}

void printPosAndSize(FILE *f) {
    off_t curPos = ftello(f);
    fseeko(f, 0, SEEK_END);
    off_t size = ftell(f);
    fseeko(f, curPos, SEEK_SET);
    printf("Pos: %llu,\tSize: %llu\n", curPos, size);
}

int main(int argc, const char *argv[]) {
    if (argc != 3) usage();

    const char *srcPath = argv[1];
    const char *tgtPath = argv[2];

    FILE *inFile = fopen(srcPath, "rb");
    FILE *outFile = fopen(tgtPath, "wb");

    printf("inFile: %s, outfile: %s\n", srcPath, tgtPath);

    if (!inFile) error("Couldn't open source file!");
    if (!outFile) error("Couldn't open target file!");

    while (!feof(inFile)) {
        char buff[2048];
        size_t count = fread(buff, 1, sizeof(buff), inFile);
        fwrite(buff, 1, count, outFile);
        printPosAndSize(outFile);
    }

    fclose(inFile);
    fclose(outFile);

    return 0;
}

このcpは、file1からfile2に2048バイトのブロックを書き込みます。通常のファイルをコピーすると、出力は次のようになります。

# copying to new file:
$ sudo ./cp /var/log/syslog /tmp/foo.txt 
inFile: /var/log/syslog, outfile: /tmp/foo.txt
Pos: 2048,      Size: 2048
Pos: 4096,      Size: 4096
Pos: 4949,      Size: 4949

# overwriting existing file:
$ sudo ./cp /var/log/syslog /tmp/foo.txt 
inFile: /var/log/syslog, outfile: /tmp/foo.txt
Pos: 2048,      Size: 2048
Pos: 4096,      Size: 4096
Pos: 4949,      Size: 4949

書き込みモードで開く前に、ターゲットファイルが存在するかどうかは重要ではないことを示すために2回実行しました。作成すると、内容が完全に上書きされ、ファイルサイズに反映されます。

だから別のことを試してみましょう。 $ sudo ./cp /var/log/syslog /dev/null inFile: /var/log/syslog, outfile: /dev/null Pos: 0, Size: 0 Pos: 2048 , Size: 0 位置: 974, サイズ: 0

/dev/nullキャラクターデバイスです。サイズは常に0です。出力は(シリアルポートなど)に書き込まれた後に忘れられます。

しかし、あなたの質問に答えてみましょう。ブロックデバイスに書き込む場合(警告する!これにより、ドライブのメタ情報が破壊されるため、デバイス上のすべてのデータを読み取ることができなくなります。このデモでは古いUSBドライブを使用しました。)

$ sudo ./cp /var/log/syslog /dev/sdb
inFile: /var/log/syslog, outfile: /dev/sdb
Pos: 2048,      Size: 2003828736
Pos: 4096,      Size: 2003828736
Pos: 5306,      Size: 2003828736

ブロックファイルは単に位置0で開き、バイト単位で上書きされます(他のすべてのデータはそのまま残ります)。

関連情報