[AtomicWriter] fsync() parent directory after rename
[AtomicWriter] fsync() parent directory after rename

To ensure that the rename() has been committed to disk, we must call
fsync() on the containing directory. Without this fsync() call,
powerloss (or VM reset, or crash), could result in data loss. Thus,
this affects the durability of the AtomicWriter but not the atomicity
as far as I know.

** Special Casing InvalidData

Not all filesystems support fsync() on directories. For instance, some
filesystems don't even define the fsync operation on directory
descriptors. In this case Linux returns EINVAL:

https://github.com/torvalds/linux/blob/6f0d349d922ba44e4348a17a78ea51b7135965b1/fs/sync.c#L193-L194

The linux manual page for fsync() says:

> EROFS, EINVAL
>   fd is bound to a special file (e.g., a pipe, FIFO, or socket)
>   which does not support synchronization.

(http://man7.org/linux/man-pages/man2/fsync.2.html)

One such filesystem is vboxsf. While we've had a similar directory
fsync in place for package installations without issues, I opted to
add a special case for EINVAL errors to avoid any unintentional
incompatibilities. We cannot also special case EROFS and EBADF because
those currently get mapped, along with many other errors the
ErrorKind::Other and thus we would be ignoring more than we expected.

** What other software does

- Postgresql syncs the parent directory and doesn't ignore the
  error. Note that they include 2 additional fsyncs we don't (one on
  the dest file /before/ the rename and on the dest file /after/ the
  rename); however, it isn't clear to me that those are necessary:

  https://github.com/postgres/postgres/blob/97c39498e5ca9208d3de5a443a2282923619bf91/src/common/file_utils.c#L392-L401

- SQLite syncs the parent directory but ignores the error because of
  systems such as AIX where it isn't supported (the official sqlite
  source is hard to link to):

  https://github.com/mackyle/sqlite/blob/master/src/os_unix.c#L3721-L3740

- MySQL ignores the EINVAL, EBADF, and EROFS:

  https://github.com/mysql/mysql-server/blob/4f1d7cf5fcb11a3f84cff27e37100d7295e7d5ca/mysys/my_sync.cc#L139-L157

- Redis doesn't do this fsync:

  https://github.com/antirez/redis/blob/4dc69497f509956d298455492fe5ab98a9738cb6/src/aof.c#L1429-L1444

- RocksDB does the fsync after most dir ops, different call sites
  handle the error differently:

  https://github.com/facebook/rocksdb/blob/8959063c9c98be4c97b6724ef5135aa4a40363a0/util/filename.cc#L380

** Windows

We only do this on Windows because FlushFileBuffers() returns an error
if called on a directory, so we can't call this on Windows. However, I
haven't done research into whether the rename() is guaranteed to be
synced to disk on Windows.

Signed-off-by: Steven Danna <9ce5770b3bb4b2a1d59be2d97e34379cd192299f@chef.io>

rsertelon/habitat,habitat-sh/habitat,habitat-sh/habitat,rsertelon/habitat,rsertelon/habitat,habitat-sh/habitat,habitat-sh/habitat,habitat-sh/habitat,habitat-sh/habitat,rsertelon/habitat,rsertelon/habitat,rsertelon/habitat,habitat-sh/habitat,habitat-sh/habitat,rsertelon/habitat