aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.github/scripts/install_deps.sh2
-rw-r--r--cmake.config/CMakeLists.txt1
-rw-r--r--cmake.config/config.h.in1
-rw-r--r--runtime/doc/editing.txt7
-rw-r--r--src/nvim/bufwrite.c12
-rw-r--r--src/nvim/eval/funcs.c3
-rw-r--r--src/nvim/os/fs.c97
-rw-r--r--test/old/testdir/test_writefile.vim23
8 files changed, 144 insertions, 2 deletions
diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh
index 6a4e163feb..05e07bda1d 100755
--- a/.github/scripts/install_deps.sh
+++ b/.github/scripts/install_deps.sh
@@ -12,7 +12,7 @@ done
os=$(uname -s)
if [[ $os == Linux ]]; then
sudo apt-get update
- sudo apt-get install -y build-essential cmake curl gettext ninja-build unzip
+ sudo apt-get install -y attr build-essential cmake curl gettext libattr1-dev ninja-build unzip
if [[ -n $TEST ]]; then
sudo apt-get install -y locales-all cpanminus
fi
diff --git a/cmake.config/CMakeLists.txt b/cmake.config/CMakeLists.txt
index 26a7a262c1..85d49e330e 100644
--- a/cmake.config/CMakeLists.txt
+++ b/cmake.config/CMakeLists.txt
@@ -36,6 +36,7 @@ check_symbol_exists(_NSGetEnviron crt_externs.h HAVE__NSGETENVIRON)
# Headers
check_include_files(langinfo.h HAVE_LANGINFO_H)
check_include_files(strings.h HAVE_STRINGS_H)
+check_include_files(attr/xattr.h HAVE_XATTR)
check_include_files(sys/utsname.h HAVE_SYS_UTSNAME_H)
check_include_files(termios.h HAVE_TERMIOS_H)
check_include_files(sys/uio.h HAVE_SYS_UIO_H)
diff --git a/cmake.config/config.h.in b/cmake.config/config.h.in
index 90916d55bd..fb12a7c558 100644
--- a/cmake.config/config.h.in
+++ b/cmake.config/config.h.in
@@ -27,6 +27,7 @@
#cmakedefine HAVE_STRINGS_H
#cmakedefine HAVE_STRNCASECMP
#cmakedefine HAVE_STRPTIME
+#cmakedefine HAVE_XATTR
#cmakedefine HAVE_SYS_SDT_H
#cmakedefine HAVE_SYS_UTSNAME_H
#cmakedefine HAVE_SYS_WAIT_H
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index abfe466b07..6e7963c066 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -1078,6 +1078,13 @@ will get the ACL info of the original file.
The ACL info is also used to check if a file is read-only (when opening the
file).
+ *xattr* *E1506* *E1507* *E1508* *E1509*
+xattr stands for Extended Attributes It is an advanced way to save metadata
+alongside the file in the filesystem. It depends on the actual filesystem
+being used and Vim supports it only on a Linux system.
+ Vim attempts to preserve the extended attribute info when writing a file.
+The backup file will get the extended attribute of the original file.
+
*read-only-share*
When MS-Windows shares a drive on the network it can be marked as read-only.
This means that even if the file read-only attribute is absent, and the ACL
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
index 40b95e5790..db813a3ae1 100644
--- a/src/nvim/bufwrite.c
+++ b/src/nvim/bufwrite.c
@@ -913,6 +913,9 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
&& os_chown(*backupp, (uv_uid_t)-1, (uv_gid_t)file_info_old->stat.st_gid) != 0) {
os_setperm(*backupp, ((int)perm & 0707) | (((int)perm & 07) << 3));
}
+# ifdef HAVE_XATTR
+ os_copy_xattr(fname, *backupp);
+# endif
#endif
// copy the file
@@ -929,6 +932,9 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
(double)file_info_old->stat.st_mtim.tv_sec);
#endif
os_set_acl(*backupp, acl);
+#ifdef HAVE_XATTR
+ os_copy_xattr(fname, *backupp);
+#endif
*err = set_err(NULL);
break;
}
@@ -1634,6 +1640,12 @@ restore_backup:
end = 0;
}
+ if (!backup_copy) {
+#ifdef HAVE_XATTR
+ os_copy_xattr(backup, wfname);
+#endif
+ }
+
#ifdef UNIX
// When creating a new file, set its owner/group to that of the original
// file. Get the new device and inode number.
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 710d9c73a5..5bfce7c272 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3171,6 +3171,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"windows",
"winaltkeys",
"writebackup",
+#ifdef HAVE_XATTR
+ "xattr",
+#endif
"nvim",
};
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 77c4766419..3efb575039 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -18,6 +18,8 @@
# include <shlobj.h>
#endif
+#include "auto/config.h"
+
#if defined(HAVE_ACL)
# ifdef HAVE_SYS_ACL_H
# include <sys/acl.h>
@@ -27,7 +29,11 @@
# endif
#endif
-#include "auto/config.h"
+#ifdef HAVE_XATTR
+# include <attr/xattr.h>
+# define XATTR_VAL_LEN 1024
+#endif
+
#include "nvim/ascii.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -55,6 +61,17 @@
# include "os/fs.c.generated.h"
#endif
+#ifdef HAVE_XATTR
+static const char e_xattr_erange[]
+ = N_("E1506: Buffer too small to copy xattr value or key");
+static const char e_xattr_enotsup[]
+ = N_("E1507: Extended attributes are not supported by the filesystem");
+static const char e_xattr_e2big[]
+ = N_("E1508: size of the extended attribute value is larger than the maximum size allowed");
+static const char e_xattr_other[]
+ = N_("E1509: error occured when reading or writing extended attribute");
+#endif
+
struct iovec;
#define RUN_UV_FS_FUNC(ret, func, ...) \
@@ -743,6 +760,84 @@ int os_setperm(const char *const name, int perm)
return (r == kLibuvSuccess ? OK : FAIL);
}
+#ifdef HAVE_XATTR
+/// Copy extended attributes from_file to to_file
+void os_copy_xattr(const char *from_file, const char *to_file)
+{
+ if (from_file == NULL) {
+ return;
+ }
+
+ // get the length of the extended attributes
+ ssize_t size = listxattr((char *)from_file, NULL, 0);
+ // not supported or no attributes to copy
+ if (errno == ENOTSUP || size <= 0) {
+ return;
+ }
+ char *xattr_buf = xmalloc((size_t)size);
+ size = listxattr(from_file, xattr_buf, (size_t)size);
+ ssize_t tsize = size;
+
+ errno = 0;
+
+ ssize_t max_vallen = 0;
+ char *val = NULL;
+ const char *errmsg = NULL;
+
+ for (int round = 0; round < 2; round++) {
+ char *key = xattr_buf;
+ if (round == 1) {
+ size = tsize;
+ }
+
+ while (size > 0) {
+ ssize_t vallen = getxattr(from_file, key, val, round ? (size_t)max_vallen : 0);
+ // only set the attribute in the second round
+ if (vallen >= 0 && round
+ && setxattr(to_file, key, val, (size_t)vallen, 0) == 0) {
+ //
+ } else if (errno) {
+ switch (errno) {
+ case E2BIG:
+ errmsg = e_xattr_e2big;
+ goto error_exit;
+ case ENOTSUP:
+ errmsg = e_xattr_enotsup;
+ goto error_exit;
+ case ERANGE:
+ errmsg = e_xattr_erange;
+ goto error_exit;
+ default:
+ errmsg = e_xattr_other;
+ goto error_exit;
+ }
+ }
+
+ if (round == 0 && vallen > max_vallen) {
+ max_vallen = vallen;
+ }
+
+ // add one for terminating null
+ ssize_t keylen = (ssize_t)strlen(key) + 1;
+ size -= keylen;
+ key += keylen;
+ }
+ if (round) {
+ break;
+ }
+
+ val = xmalloc((size_t)max_vallen + 1);
+ }
+error_exit:
+ xfree(xattr_buf);
+ xfree(val);
+
+ if (errmsg != NULL) {
+ emsg(errmsg);
+ }
+}
+#endif
+
// Return a pointer to the ACL of file "fname" in allocated memory.
// Return NULL if the ACL is not available for whatever reason.
vim_acl_T os_get_acl(const char *fname)
diff --git a/test/old/testdir/test_writefile.vim b/test/old/testdir/test_writefile.vim
index 312d45e18f..62ec901dbf 100644
--- a/test/old/testdir/test_writefile.vim
+++ b/test/old/testdir/test_writefile.vim
@@ -990,4 +990,27 @@ func Test_wq_quitpre_autocommand()
call delete('Xsomefile')
endfunc
+func Test_write_with_xattr_support()
+ CheckLinux
+ CheckExecutable setfattr
+
+ let contents = ["file with xattrs", "line two"]
+ call writefile(contents, 'Xwattr.txt', 'D')
+ " write a couple of xattr
+ call system('setfattr -n user.cookie -v chocolate Xwattr.txt')
+ call system('setfattr -n user.frieda -v bar Xwattr.txt')
+ call system('setfattr -n user.empty Xwattr.txt')
+
+ set backupcopy=no writebackup& backup&
+ sp Xwattr.txt
+ w
+ $r! getfattr -d %
+ let expected = ['file with xattrs', 'line two', '# file: Xwattr.txt', 'user.cookie="chocolate"', 'user.empty=""', 'user.frieda="bar"', '']
+ call assert_equal(expected, getline(1,'$'))
+
+ set backupcopy&
+ bw!
+
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab