Why does this work?

cp image.bin /dev/mapper/loop0p1

image.bin is a partition image.

I have tried it and works, but why? Shouldn't a dd be used?

  • Are you sure you don't mean /dev/loop0p1? There won't be a loop in /dev/mapper unless you have done some hacking around with dmsetup. – psusi Apr 17 '13 at 13:36
  • @psusi: loop partitions are mapped devices, they aren't there by default – frostschutz Apr 17 '13 at 13:43
  • @frostschutz, they are if you boot with the loop.max_parts argument, or you can use partprobe or partx to activate them otherwise. There's no need to involve the device-mapper. – psusi Apr 17 '13 at 15:42
  • @psusi, thanks. It's kpartx that uses device mapper instead. I guess it doesn't really matter for a temporary loop device. – frostschutz Apr 17 '13 at 16:53

GNU coreutils cp works because it was written that way. Writing to block devices isn't complicated, it's essentially the same operation as writing to a regular file.

However, you shouldn't use cp that way. You can do it if you're sure it's GNU coreutils, which works. But there are other flavours of cp around, for example busybox cp does not support writing to devices at all; it unlinks (deletes) the device node and creates a new file in its place instead.

dd is safer in that regard. It was made for writing to devices and brings the tools (bs, seek, skip, count, ...) you often need when dealing with devices. It should do the expected thing in all flavours of dd.

  • How can "cp be written that way" if "it's essentially the same operation"? Is leaving out a check for a block device and not exiting due to that "written that way"? 8-) The busybox behaviour seems stupid to me. Is that POSIX compliant? You (may) change access rights by that. And if the target cannot be unlinked, does it fail then? :-/ – Hauke Laging Apr 17 '13 at 11:59
  • Stupid or not, I just wanted to point it out. – frostschutz Apr 17 '13 at 12:20
  • That does sound like a bug in busybox... – psusi Apr 17 '13 at 13:34
  • How about cat image.bin > /dev/mapper/loop0p1 in busybox? – jdh8 Feb 10 '17 at 17:52

Your expectation differs from program / system design. What shall we say about that? :-)

"Everything is a file"...

You could run cp and/or dd through strace -e trace=open and will see that the syscall is the same for both regular files and block devices. If the syscall does not tell them apart, why should cp care?

  • Can you do the opposite? cp /dev/sdb1 ~/disk.iso – Guerrio Apr 17 '13 at 11:46
  • 1
    Yes. You can use cp, dd or even cat (and probably a few other commands). – Hennes Apr 17 '13 at 11:49
  • @Guerrio Indeed. But usually dd is taken for that because it is more flexible. cp can copy whole "files" only. – Hauke Laging Apr 17 '13 at 11:53
  • This works in some shells: < /dev/sdb1 > ~/disk.iso. – user26112 Apr 18 '13 at 0:40

Well as far as I know, all that cp does is

  • open the target file in write mode
  • write the data from the source file to the target file (not sure about the chunk size though, but that's just details)

With normal files, this can result in either

  • creating a new file that will grow with each write call
  • Overwriting an existing file that will be overwritten:
    The first write call will erase the file's contents and put the new data in it. From that point on the file grows exactly like if it was a new one.

Now /dev/mapper/* are block devices (or to be specific, symbolic links to block devices). Those do have static file sizes. Therefore, if you open those files, each write() call will simply overwrite the n bytes of the target file you sent to it (assuming there's no fseek() call anywhere).

So, let's write our own poor-man's 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);


    return 0;

This cp will write 2048 byte chunks from file1 to file2. If you just copy regular files, the output will look like that:

# 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

I'm running it twice to show you that it doesn't matter if the target file existed before opening it in write mode. As soon as you write to it, its contents get fully overwritten and the file size reflects that.

So, let's try something different: $ sudo ./cp /var/log/syslog /dev/null inFile: /var/log/syslog, outfile: /dev/null Pos: 0, Size: 0 Pos: 2048, Size: 0 Pos: 974, Size: 0

/dev/null is a character device. Those always have size 0. The output is simply written to them (e.g. to the serial port) and forgotten afterwards.

But let's get to your question: What happens if you write to a block device (Caution! Executing this will make all the data on the device unreadable as it destroys the drive's meta information! I've used an old USB drive for this demo)

$ 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

The block file is simply opened at position 0 and overwritten byte by byte (without touching all the other data).

  • I don't get the sense of the last paragraph... – Hauke Laging Apr 17 '13 at 11:28
  • I think the last paragraph hints at some other very useful dd copying. Just one which has nothing to do with the OPs question. – Hennes Apr 17 '13 at 11:50
  • @Hennes OK, now I got it. But what is surprising about that? Should cp pad the rest of the block device with zeroes or whatever? :-S – Hauke Laging Apr 17 '13 at 11:55
  • Sry, the last paragraph was a little confusing. I replaced it by some example code and some executions that should cover all the different target file types (regular file, character and block dev) – mreithub Apr 17 '13 at 11:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.