forest's answer, albeit pretty (and in many cases correct) isn't what you'll see on modern systems⁰. They're right – under no circumstances would this corrupt your file system. But under no practical circumstances would you get half a copy these days!
Assume I do this (just to generate a large file yesfile
, and copy it to a file copy
(doesn't have to be on the same file system), while logging all the system calls mady by cp
):
cd /tmp
yes | head -n$((10**7)) > yesfile
strace -o strace.output cp yesfile copy
I get a different picture: the userland process cp
does not actually read the content of the file and does not write it to another file; that would be bad, performance-wise: it would require at least two context switches! The userland programm calls read
, switch, gets data, calls write
, switch; rinse and repeat if the file is larger than a single read-buffer. Now this exact repeating model, reading only a buffer of limited size, is what could lead to half-copied files on interruption.
Instead, it uses the copy_file_range
system call (see trace below¹); man copy_file_range
tells us:
The copy_file_range()
system call performs an in-kernel copy between two file descriptors without the additional cost of transferring data from the kernel to user space and then back into the kernel. It copies up to len
bytes of data from the source file descriptor fd_in
to the target file descriptor fd_out
, overwriting any data that exists within the requested range of the target file.
So, there is an atomic copy-this-file system call, which is usually used, so interrupting cp
can not interrupt the copying.
Things get even better if your source and target file system are the same, and Btrfs, CIFS, NFS 4.2, OCFS2, overlayfs, or XFSsource (at point of writing, only for these Linux has the reflink feature): if
ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3)
succeeds, the system doesn't need to copy the file contents at all – instead, just the list of blocks belonging to the source file is copied to the target file; each block has a reference counter that gets increased, so the moment any process writes to either of these files, the file system transparently does a copy-on-write on that. So, these things are even more atomic!
⁰ At least, if my GNU coreutils 8.32 with fedora 34's backported copy_file_range
patches /Linux 5.13.5 are considered modern.
¹ relevant strace output
156 │ newfstatat(AT_FDCWD, "yesfile", {st_mode=S_IFREG|0644, st_size=20000000, ...}, 0) = 0
157 │ newfstatat(AT_FDCWD, "copy", 0x7fff982d5e70, 0) = -1 ENOENT (No such file or directory)
158 │ openat(AT_FDCWD, "yesfile", O_RDONLY) = 3
159 │ newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=20000000, ...}, AT_EMPTY_PATH) = 0
160 │ openat(AT_FDCWD, "copy", O_WRONLY|O_CREAT|O_EXCL, 0644) = 4
161 │ newfstatat(4, "", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_EMPTY_PATH) = 0
162 │ ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = -1 EOPNOTSUPP (Operation not supported)
163 │ fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
164 │ mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0be58ca000
165 │ uname({sysname="Linux", nodename="workhorse", ...}) = 0
166 │ copy_file_range(3, NULL, 4, NULL, 9223372035781033984, 0) = 20000000
167 │ copy_file_range(3, NULL, 4, NULL, 9223372035781033984, 0) = 0
168 │ close(4) = 0
169 │ close(3) = 0
170 │ munmap(0x7f0be58ca000, 139264) = 0