diff options
-rwxr-xr-x | .github/scripts/install_deps.sh | 2 | ||||
-rw-r--r-- | cmake.config/CMakeLists.txt | 1 | ||||
-rw-r--r-- | cmake.config/config.h.in | 1 | ||||
-rw-r--r-- | runtime/doc/editing.txt | 7 | ||||
-rw-r--r-- | src/nvim/bufwrite.c | 12 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 3 | ||||
-rw-r--r-- | src/nvim/os/fs.c | 97 | ||||
-rw-r--r-- | test/old/testdir/test_writefile.vim | 23 |
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 |