I had an interesting question today: “What happens if I Ctrl C in the middle of an mv operation? Will the file be corrupted?” Without considering error handling, this question needs to be discussed in separate cases.

  • Is it on the same file system?
  • Is the object of the mv a file or a folder?

First, the simplest case: mv a file on the same filesystem, where mv is done using rename syscall.

1
2
3
4
5
6
execve("/usr/bin/mv", ["mv", "a", "b"], 0x7ffe6ae9f580 /* 51 vars */) = 0
...
renameat2(AT_FDCWD, "a", AT_FDCWD, "b", RENAME_NOREPLACE) = 0
...
exit_group(0)                           = ?
+++ exited with 0 +++

By POSIX definition, the rename operation is atomic, and subsequent additions of renameat and renameat2 to the Linux kernel also meet this requirement. This is made clear in rename(2).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
rename() renames a file, moving it between directories if
required.  Any other hard links to the file (as created using
link(2)) are unaffected.  Open file descriptors for oldpath are
also unaffected.

If newpath already exists, it will be atomically replaced, so
that there is no point at which another process attempting to
access newpath will find it missing.  However, there will
probably be a window in which both oldpath and newpath refer to
the file being renamed.

If newpath exists but the operation fails for some reason,
rename() guarantees to leave an instance of newpath in place.

You can also use rename to mv a folder on the same filesystem, so I won’t go into that again.

In summary, mv operations on the same filesystem are always atomic, and there is no chance of being interrupted by Ctrl-C, which is often referred to as uninterruptable.

Next, we deal with the case of not being on the same file system. For Linux, not being on the same filesystem is actually more strictly limited to not being under the same mount point. That is, rename syscall will return an EXDEV error regardless of whether they are on the same filesystem at the bottom, as long as they have different mount points.

1
2
3
4
5
EXDEV  oldpath and newpath are not on the same mounted
        filesystem.  (Linux permits a filesystem to be mounted at
        multiple points, but rename() does not work across
        different mount points, even if the same filesystem is
        mounted on both.)

It would obviously be very uncomfortable for mv, which is exposed for user use, to not support moving around the filesystem, and these considerations are documented in mv.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
The rename() function is able to move directories within the same
file system. Some historical versions of mv have been able to
move directories, but not to a different file system.  The
standard developers considered that this was an annoying
inconsistency, so this volume of POSIX.1‐2017 requires
directories to be able to be moved even across file systems.
There is no -R option to confirm that moving a directory is
actually intended, since such an option was not required for
moving directories in historical practice. Requiring the
application to specify it sometimes, depending on the
destination, seemed just as inconsistent. The semantics of the
rename() function were preserved as much as possible. For
example, mv is not permitted to ``rename'' files to or from
directories, even though they might be empty and removable.

Specifically, mv is implemented by degenerating to copy & unlink to move across filesystems, as we can confirm by reading the source code and analyzing strace. archlinux uses coreutils, so take a brief look at it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static bool
do_move (char const *source, char const *dest, const struct cp_options *x)
{
  bool copy_into_self;
  bool rename_succeeded;
  bool ok = copy (source, dest, false, x, &copy_into_self, &rename_succeeded);

  if (ok)
    {
        ...

      if (dir_to_remove != NULL)
        {
         ...

          status = rm ((void*) dir, &rm_options);
          assert (VALID_STATUS (status));
          if (status == RM_ERROR)
            ok = false;
        }
    }

  return ok;
}

So if you call Ctrl C while mving a file across filesystems, mv may leave an incomplete file at src or dst. But mv will always make sure that one of src or dst is complete, not that both src and dst are incomplete. As mentioned in the manual page.

1
2
3
4
5
6
If the copying or removal of source_file is prematurely
terminated by a signal or error, mv may leave a partial copy of
source_file at the source or destination. The mv utility shall
not modify both source_file and the destination path
simultaneously; termination at any point shall leave either
source_file or the destination path complete.

The same guarantee mentioned above applies to mv folders across file systems. src/dst always has a complete file in one place, and mv will always make sure that all files are copied before it starts deleting them.

In general, Ctrl C will not destroy files during mv, and in the most extreme cases, only dst will be copied incompletely or src will be deleted incompletely.