commit 1e44a001ecdbfcd7155b3b18adaece3c3c816e36
parent ab5a92bff67d654c543d89b4803a64b2e648253a
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sun, 28 Dec 2025 08:14:45 +0800
vim-patch:9.1.2024: 'fsync' option cannot be set per buffer (#37129)
Problem: 'fsync' option cannot be set per buffer
Solution: Make 'fsync' option global-local
(glepnir)
closes: vim/vim#19019
https://github.com/vim/vim/commit/4d5b30372663e8ea356b25fe94334558c6ae283f
Co-authored-by: glepnir <glephunter@gmail.com>
Diffstat:
10 files changed, 43 insertions(+), 16 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -326,7 +326,7 @@ OPTIONS
• 'diffanchors' specifies addresses to anchor a diff.
• 'diffopt' `inline:` configures diff highlighting for changes within a line.
• 'fillchars' has new flag "foldinner".
-• 'grepformat' is now a |global-local| option.
+• 'fsync' and 'grepformat' are now |global-local| options.
• 'jumpoptions' flag "view" now applies when popping the |tagstack|.
• 'maxsearchcount' sets maximum value for |searchcount()| and defaults to 999.
• 'pummaxwidth' sets maximum width for the completion popup menu.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -3197,7 +3197,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'fsync'* *'fs'* *'nofsync'* *'nofs'*
'fsync' 'fs' boolean (default on)
- global
+ global or local to buffer |global-local|
When on, the OS function fsync() will be called after saving a file
(|:write|, |writefile()|, …), |swap-file|, |undo-persistence| and |shada-file|.
This flushes the file to disk, ensuring that it is safely written.
@@ -3210,6 +3210,8 @@ A jump table for the options with a short description can be found at |Q_op|.
- system signals low battery life
- Nvim exits abnormally
+ This is a |global-local| option, so it can be set per buffer, for
+ example when writing to a slow filesystem.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -3002,12 +3002,16 @@ vim.go.fp = vim.go.formatprg
--- - system signals low battery life
--- - Nvim exits abnormally
---
+--- This is a `global-local` option, so it can be set per buffer, for
+--- example when writing to a slow filesystem.
--- This option cannot be set from a `modeline` or in the `sandbox`, for
--- security reasons.
---
--- @type boolean
vim.o.fsync = true
vim.o.fs = vim.o.fsync
+vim.bo.fsync = vim.o.fsync
+vim.bo.fs = vim.bo.fsync
vim.go.fsync = vim.o.fsync
vim.go.fs = vim.go.fsync
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
@@ -2145,6 +2145,7 @@ void free_buf_options(buf_T *buf, bool free_p_ff)
clear_string_option(&buf->b_p_qe);
buf->b_p_ac = -1;
buf->b_p_ar = -1;
+ buf->b_p_fs = -1;
buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
clear_string_option(&buf->b_p_lw);
clear_string_option(&buf->b_p_bkc);
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
@@ -586,6 +586,7 @@ struct file_buffer {
char *b_p_fp; ///< 'formatprg'
char *b_p_fex; ///< 'formatexpr'
uint32_t b_p_fex_flags; ///< flags for 'formatexpr'
+ int b_p_fs; ///< 'fsync'
char *b_p_kp; ///< 'keywordprg'
int b_p_lisp; ///< 'lisp'
char *b_p_lop; ///< 'lispoptions'
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
@@ -1226,7 +1226,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
// the original file.
// Don't do this if there is a backup file and we are exiting.
if (reset_changed && !newfile && overwriting && !(exiting && backup != NULL)) {
- ml_preserve(buf, false, !!p_fs);
+ ml_preserve(buf, false, !!(buf->b_p_fs >= 0 ? buf->b_p_fs : p_fs));
if (got_int) {
err = set_err(_(e_interr));
goto restore_backup;
@@ -1575,9 +1575,9 @@ restore_backup:
// (could be a pipe).
// If the 'fsync' option is false, don't fsync(). Useful for laptops.
int error;
- if (p_fs && (error = os_fsync(fd)) != 0 && !device
+ if ((buf->b_p_fs >= 0 ? buf->b_p_fs : p_fs) && (error = os_fsync(fd)) != 0
// fsync not supported on this storage.
- && error != UV_ENOTSUP) {
+ && error != UV_ENOTSUP && !device) {
err = set_err_arg(e_fsync, error);
end = 0;
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
@@ -387,6 +387,7 @@ void set_init_1(bool clean_arg)
curbuf->b_p_initialized = true;
curbuf->b_p_ac = -1;
curbuf->b_p_ar = -1; // no local 'autoread' value
+ curbuf->b_p_fs = -1; // no local 'fsync' value
curbuf->b_p_ul = NO_LOCAL_UNDOLEVEL;
check_buf_options(curbuf);
check_win_options(curwin);
@@ -3391,6 +3392,7 @@ static OptVal get_option_unset_value(OptIndex opt_idx)
switch (opt_idx) {
case kOptAutocomplete:
case kOptAutoread:
+ case kOptFsync:
return BOOLEAN_OPTVAL(kNone);
case kOptScrolloff:
case kOptSidescrolloff:
@@ -4434,6 +4436,8 @@ void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win)
switch (opt_idx) {
case kOptFormatprg:
return &(buf->b_p_fp);
+ case kOptFsync:
+ return &(buf->b_p_fs);
case kOptFindfunc:
return &(buf->b_p_ffu);
case kOptErrorformat:
@@ -4567,6 +4571,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var;
case kOptFormatprg:
return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var;
+ case kOptFsync:
+ return buf->b_p_fs >= 0 ? &(buf->b_p_fs) : p->var;
case kOptFindfunc:
return *buf->b_p_ffu != NUL ? &(buf->b_p_ffu) : p->var;
case kOptErrorformat:
@@ -5241,6 +5247,7 @@ void buf_copy_options(buf_T *buf, int flags)
// are not copied, start using the global value
buf->b_p_ac = -1;
buf->b_p_ar = -1;
+ buf->b_p_fs = -1;
buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
buf->b_p_bkc = empty_string_option;
buf->b_bkc_flags = 0;
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -3813,11 +3813,13 @@ local options = {
- system signals low battery life
- Nvim exits abnormally
+ This is a |global-local| option, so it can be set per buffer, for
+ example when writing to a slow filesystem.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
]=],
full_name = 'fsync',
- scope = { 'global' },
+ scope = { 'global', 'buf' },
secure = true,
short_desc = N_('whether to invoke fsync() after file write'),
type = 'boolean',
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
@@ -1340,7 +1340,8 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
}
#endif
- if (p_fs && fflush(fp) == 0 && os_fsync(fd) != 0) {
+ if ((buf->b_p_fs >= 0 ? buf->b_p_fs : p_fs) && fflush(fp) == 0
+ && os_fsync(fd) != 0) {
write_ok = false;
}
diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
@@ -1662,27 +1662,36 @@ endfunc
" Test for setting boolean global-local option value
func Test_set_boolean_global_local_option()
- setglobal autoread
- setlocal noautoread
+ CheckUnix
+
+ setglobal autoread fsync
+ setlocal noautoread nofsync
call assert_equal(1, &g:autoread)
call assert_equal(0, &l:autoread)
call assert_equal(0, &autoread)
+ call assert_equal(1, &g:fsync)
+ call assert_equal(0, &l:fsync)
+ call assert_equal(0, &fsync)
" :setlocal {option}< set the effective value of {option} to its global value.
- "set autoread<
- setlocal autoread<
+ "set autoread< fsync<
+ setlocal autoread< fsync<
call assert_equal(1, &l:autoread)
call assert_equal(1, &autoread)
+ call assert_equal(1, &l:fsync)
+ call assert_equal(1, &fsync)
" :set {option}< removes the local value, so that the global value will be used.
- setglobal noautoread
- setlocal autoread
- "setlocal autoread<
- set autoread<
+ setglobal noautoread nofsync
+ setlocal autoread fsync
+ "setlocal autoread< fsync<
+ set autoread< fsync<
call assert_equal(-1, &l:autoread)
call assert_equal(0, &autoread)
+ call assert_equal(-1, &l:fsync)
+ call assert_equal(0, &fsync)
- set autoread&
+ set autoread& fsync&
endfunc
func Test_set_in_sandbox()