From 87ce5738e1ed109eed2311f24e546f70d60abf15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:59:48 +0200 Subject: [PATCH 1/2] save: gofmt --- internal/buffer/save.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/buffer/save.go b/internal/buffer/save.go index c7bed4856c..2336f9aac6 100644 --- a/internal/buffer/save.go +++ b/internal/buffer/save.go @@ -374,7 +374,6 @@ func (b *Buffer) safeWrite(path string, withSudo bool, newFile bool) (int, error } }() - // Try to backup first before writing backupName, err := b.writeBackup(path) if err != nil { From 165a5a48963c216ac72bfb62cdf274d9ff287626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:00:22 +0200 Subject: [PATCH 2/2] save: Use `dd` with the `notrunc` & `fsync` option Using notrunc will stop the overall truncation of the target file done by sudo. We need to do this because dd, like other coreutils, already truncates the file on open(). In case we can't store the backup file afterwards we would end up in a truncated file for which the user has no write permission by default. Instead we use a second call of `dd` to perform the necessary truncation on the command line. With the fsync option we force the dd process to synchronize the written file to the underlying device. --- internal/buffer/save.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/internal/buffer/save.go b/internal/buffer/save.go index 2336f9aac6..349d4c66f9 100644 --- a/internal/buffer/save.go +++ b/internal/buffer/save.go @@ -26,6 +26,7 @@ import ( const LargeFileThreshold = 50000 type wrappedFile struct { + name string writeCloser io.WriteCloser withSudo bool screenb bool @@ -83,7 +84,13 @@ func openFile(name string, withSudo bool) (wrappedFile, error) { var sigChan chan os.Signal if withSudo { - cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name) + conv := "notrunc" + // TODO: both platforms do not support dd with conv=fsync yet + if !(runtime.GOOS == "illumos" || runtime.GOOS == "netbsd") { + conv += ",fsync" + } + + cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "conv="+conv, "of="+name) writeCloser, err = cmd.StdinPipe() if err != nil { return wrappedFile{}, err @@ -113,7 +120,18 @@ func openFile(name string, withSudo bool) (wrappedFile, error) { } } - return wrappedFile{writeCloser, withSudo, screenb, cmd, sigChan}, nil + return wrappedFile{name, writeCloser, withSudo, screenb, cmd, sigChan}, nil +} + +func (wf wrappedFile) Truncate() error { + if wf.withSudo { + // we don't need to stop the screen here, since it is still stopped + // by openFile() + // truncate might not be available on every platfom, so use dd instead + cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "count=0", "of="+wf.name) + return cmd.Run() + } + return wf.writeCloser.(*os.File).Truncate(0) } func (wf wrappedFile) Write(b *Buffer) (int, error) { @@ -134,12 +152,9 @@ func (wf wrappedFile) Write(b *Buffer) (int, error) { eol = []byte{'\n'} } - if !wf.withSudo { - f := wf.writeCloser.(*os.File) - err := f.Truncate(0) - if err != nil { - return 0, err - } + err := wf.Truncate() + if err != nil { + return 0, err } // write lines