Is it possible to create a non-empty file without write_close and rename event

fileshard linkinotifysftp

The reason why I am asking is because I'm using iwatch (not to confuse with a gadget device) to watch for filesystem events (in my case – file creation/renaming).

What I cannot explain is this log:

/path/to/file.ext.filepart 0 IN_MODIFY 
/path/to/file.ext.filepart 0 IN_MODIFY 
/path/to/file.ext.filepart 0 IN_MODIFY 
/path/to/file.ext.filepart 0 IN_MODIFY 
/path/to/file.ext.filepart 0 IN_CLOSE_WRITE 
/path/to/file.ext 0 IN_CREATE 
/path/to/file.ext.filepart 0 IN_DELETE 
/path/to/file.ext 0 IN_ATTRIB 

To get it I've copied a file.ext from a remote machine using WinSCP with temporary file creation option turned on (so that it was either no file file.ext at all, in case if transfer was terminated, or the complete file was in the destination).

And what confuses me is that the /path/to/file.ext is only created IN_CREATE and its attributes modified IN_ATTRIB (not sure which ones though, but I think that's where all the magic happens).

The strangest thing here is that:

  1. The file.ext is not a result of moving file.ext.filepart – there would be a different move event
  2. The file.ext is not a result of copying file.ext.filepart – there would be a bunch of write events following by IN_CLOSE_WRITE

So my question is – what is happening here under the hood: how the file.ext was created with the contents without an explicit rename or data copy?

Best Answer

$ inotifywait -m /tmp
Setting up watches.
Watches established.
/tmp/ CREATE file.ext.filepart
/tmp/ OPEN file.ext.filepart
/tmp/ MODIFY file.ext.filepart
/tmp/ CLOSE_WRITE,CLOSE file.ext.filepart
/tmp/ CREATE file.ext
/tmp/ DELETE file.ext.filepart

Transcript from running

$ echo hello >/tmp/file.ext.filepart
$ ln /tmp/file.ext.filepart /tmp/file.ext         
$ rm /tmp/file.ext.filepart

Moving a file generates a move event, but creating a hard link generates the same create event as creating a new, empty file (as do mkfifo and other ways to create files).

Why does the SCP or SFTP server creates a hard link then remove the temporary file rather than moving the temporary file into place? In the source code of OpenSSH (portable 6.0), in sftp-server.c, in the function process_rename, I see the following code (reformatted and simplified to illustrate the part I want to show):

if (S_ISREG(sb.st_mode)) {
    /* Race-free rename of regular files */
    if (link(oldpath, newpath) == -1) {
         if (errno == EOPNOTSUPP || errno == ENOSYS) {
            /* fs doesn't support links, so fall back to stat+rename.  This is racy. */
            if (stat(newpath, &st) == -1) {
                rename(oldpath, newpath) == -1)
            }
        }
    } else {
        unlink(newpath);
    }
}

That is: try to create a hard link from the temporary file name to the desired file name, then remove the temporary file. If creating the hard link doesn't work because the OS or the filesystem doesn't support that, fall back to a different method: test if the desired file exists, and if doesn't, rename the temporary file. So the point is to rename the temporary file to its final location without risking overwriting a file that may have been created while the copy was in progress. Renaming wouldn't do because rename overwrites the target file if it exists.

Related Question