Repository: neovim
Issue: Option to disable writing extended attributes
Labels: enhancement,performance,bug-vim,needs:vim-patch,filesystem

### Problem

Hi,

I am working for a large software company. Our codebase is remote, and mounted locally on the workstations. We also have various automation tools which pick up changes to the files and perform build, test, deployment, etc.

Ever since Neovim 0.10+, there appeared quite annoying behavior in this workflow. When you save the file in Neovim, our automation tools first pick up a change to the file having no contents (as if the file was written completely empty), the build/test fails, and only on the second attempt it succeeds.

I have debugged this problem and traced the root cause to writing extended attributes from Neovim. If I run `strace` on Neovim 0.9+ vs any commit after 0.10+, then I see the difference of making various xattr calls in Neovim 0.10.+ (see strace output attached below). Since there are many attributes to set, the file write operation is also significantly slower (although that is still tolerable).

I have also verified in a local build that if I remove the calls to [os_copy_xattr](https://github.com/neovim/neovim/blob/69553f7bf55c060733553d96a068c1104c885bce/src/nvim/os/fs.c#L790), then the issue disappears. 

Do you think it would be possible to add a configuration option to disable the handling of extended attributes?

Here is the output of `strace -e trace=file --decode-fds=path -p xxx` showing a single ":w" call. Note that file names and attribute names are masked. See the block of xattr calls in the middle of the trace.

```
# These calls are 
statx(AT_FDCWD</masked>, "/masked/filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
statx(AT_FDCWD</masked>, "/masked/filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
statx(AT_FDCWD</masked>, "/masked/filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
access("/masked/filename.cc", W_OK) = 0
statx(AT_FDCWD</masked>, "/masked/filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
statx(AT_FDCWD</masked>, "filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
access("filename.cc", W_OK) = 0
statx(AT_FDCWD</masked>, "filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
statx(AT_FDCWD</masked>, "filename.cc", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
statx(AT_FDCWD</masked>, "masked/4913", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, 0x7ffd0fa2f500) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD</masked>, "masked/4913", O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, 0100664) = 16</masked/masked/4913>
statx(AT_FDCWD</masked>, "masked/4913", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=0, ...}) = 0
unlink("masked/4913") = 0
statx(AT_FDCWD</masked>, "filename.cc~", AT_STATX_SYNC_AS_STAT, STATX_ALL, 0x7ffd0fa2f6e0) = -1 ENOENT (No such file or directory)
statx(AT_FDCWD</masked>, "filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14205, ...}) = 0
statx(AT_FDCWD</masked>, "filename.cc~", AT_STATX_SYNC_AS_STAT, STATX_ALL, 0x7ffd0fa2f380) = -1 ENOENT (No such file or directory)
unlink("filename.cc~") = -1 ENOENT (No such file or directory)
rename("filename.cc", "filename.cc~") = 0
openat(AT_FDCWD</masked>, "filename.cc", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0664) = 16</masked/filename.cc>

# These calls are made only in Neovim 0.10+
listxattr("filename.cc~", NULL, 0) = 122
listxattr("filename.cc~", "masked"..., 122) = 122
getxattr("filename.cc~", "masked", NULL, 0) = 103
getxattr("filename.cc~", "masked", NULL, 0) = 32
getxattr("filename.cc~", "masked", NULL, 0) = 16
getxattr("filename.cc~", "masked", NULL, 0) = 36
getxattr("filename.cc~", "masked", NULL, 0) = 64
getxattr("filename.cc~", "masked", NULL, 0) = 60
getxattr("filename.cc~", "masked", "masked"..., 103) = 103
setxattr("filename.cc", "masked", "masked"..., 103, 0) = -1 EOPNOTSUPP (Operation not supported)
getxattr("filename.cc~", "masked", "...", 103) = 32
setxattr("filename.cc", "masked", "...", 32, 0) = -1 EOPNOTSUPP (Operation not supported)
getxattr("filename.cc~", "masked", "...", 103) = 16
setxattr("filename.cc", "masked", "...", 16, 0) = -1 EOPNOTSUPP (Operation not supported)
getxattr("filename.cc~", "masked", "..."..., 103) = 36
setxattr("filename.cc", "masked", "..."..., 36, 0) = -1 EOPNOTSUPP (Operation not supported)
getxattr("filename.cc~", "masked", "..."..., 103) = 64
setxattr("filename.cc", "masked", "..."..., 64, 0) = -1 EOPNOTSUPP (Operation not supported)
getxattr("filename.cc~", "masked", "//masked"..., 103) = 60
setxattr("filename.cc", "masked", "//masked"..., 60, 0) = -1 EOPNOTSUPP (Operation not supported)
# End of Neovim xattr calls

statx(AT_FDCWD</masked>, "filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14206, ...}) = 0
statx(AT_FDCWD</masked>, "filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14206, ...}) = 0
chmod("filename.cc", 0100664) = 0
statx(AT_FDCWD</masked>, "/masked/filename.cc", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0664, stx_size=14206, ...}) = 0
unlink("filename.cc~") = 0
```

### Expected behavior

Make it possible to disable copying of extended attributes via a configuration option.