Skip to content

Commit 5dabe17

Browse files
authored
Merge pull request #4052 from niltonperimneto/master
fix sudo TTY race and terminal state corruption
2 parents 1c88f24 + 8f14c3c commit 5dabe17

File tree

1 file changed

+46
-30
lines changed

1 file changed

+46
-30
lines changed

internal/buffer/save.go

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
const LargeFileThreshold = 50000
2626

2727
type wrappedFile struct {
28-
name string
2928
writeCloser io.WriteCloser
3029
withSudo bool
3130
screenb bool
@@ -82,13 +81,13 @@ func openFile(name string, withSudo bool) (wrappedFile, error) {
8281
var sigChan chan os.Signal
8382

8483
if withSudo {
85-
conv := "notrunc"
84+
args := []string{"dd", "bs=4k", "of=" + name}
8685
// TODO: both platforms do not support dd with conv=fsync yet
8786
if !(runtime.GOOS == "illumos" || runtime.GOOS == "netbsd") {
88-
conv += ",fsync"
87+
args = append(args, "conv=fsync")
8988
}
9089

91-
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "conv="+conv, "of="+name)
90+
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), args...)
9291
writeCloser, err = cmd.StdinPipe()
9392
if err != nil {
9493
return wrappedFile{}, err
@@ -99,16 +98,16 @@ func openFile(name string, withSudo bool) (wrappedFile, error) {
9998
signal.Notify(sigChan, os.Interrupt)
10099

101100
screenb = screen.TempFini()
101+
102102
// need to start the process now, otherwise when we flush the file
103103
// contents to its stdin it might hang because the kernel's pipe size
104104
// is too small to handle the full file contents all at once
105105
err = cmd.Start()
106106
if err != nil {
107+
writeCloser.Close()
107108
screen.TempStart(screenb)
108-
109109
signal.Notify(util.Sigterm, os.Interrupt)
110110
signal.Stop(sigChan)
111-
112111
return wrappedFile{}, err
113112
}
114113
} else {
@@ -118,18 +117,7 @@ func openFile(name string, withSudo bool) (wrappedFile, error) {
118117
}
119118
}
120119

121-
return wrappedFile{name, writeCloser, withSudo, screenb, cmd, sigChan}, nil
122-
}
123-
124-
func (wf wrappedFile) Truncate() error {
125-
if wf.withSudo {
126-
// we don't need to stop the screen here, since it is still stopped
127-
// by openFile()
128-
// truncate might not be available on every platfom, so use dd instead
129-
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "count=0", "of="+wf.name)
130-
return cmd.Run()
131-
}
132-
return wf.writeCloser.(*os.File).Truncate(0)
120+
return wrappedFile{writeCloser, withSudo, screenb, cmd, sigChan}, nil
133121
}
134122

135123
func (wf wrappedFile) Write(b *SharedBuffer) (int, error) {
@@ -150,9 +138,12 @@ func (wf wrappedFile) Write(b *SharedBuffer) (int, error) {
150138
eol = []byte{'\n'}
151139
}
152140

153-
err := wf.Truncate()
154-
if err != nil {
155-
return 0, err
141+
if !wf.withSudo {
142+
f := wf.writeCloser.(*os.File)
143+
err := f.Truncate(0)
144+
if err != nil {
145+
return 0, err
146+
}
156147
}
157148

158149
// write lines
@@ -247,6 +238,13 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
247238
return errors.New("Cannot save scratch buffer")
248239
}
249240

241+
if withSudo {
242+
_, err := exec.LookPath(config.GlobalSettings["sucmd"].(string))
243+
if err != nil {
244+
return err
245+
}
246+
}
247+
250248
if !autoSave && b.Settings["rmtrailingws"].(bool) {
251249
for i, l := range b.lines {
252250
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
@@ -354,26 +352,44 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
354352
// This means that the file is not overwritten directly but by writing to the
355353
// backup file first.
356354
func (b *SharedBuffer) safeWrite(path string, withSudo bool, newFile bool) (int, error) {
357-
file, err := openFile(path, withSudo)
358-
if err != nil {
359-
return 0, err
360-
}
355+
var err error
361356

362357
defer func() {
363358
if newFile && err != nil {
364359
os.Remove(path)
365360
}
366361
}()
367362

368-
// Try to backup first before writing
369-
backupName, resolveName, err := b.writeBackup(path)
363+
// When using sudo, create the backup before opening the file because
364+
// openFile() truncates the target immediately. For non-sudo saves,
365+
// openFile() does not truncate on open, i.e. open is non-destructive,
366+
// so only create the backup after open succeeds.
367+
var backupName, resolveName string
368+
if withSudo {
369+
backupName, resolveName, err = b.writeBackup(path)
370+
if err != nil {
371+
return 0, err
372+
}
373+
delete(requestedBackups, b)
374+
}
375+
376+
file, err := openFile(path, withSudo)
370377
if err != nil {
371-
file.Close()
378+
if withSudo {
379+
return 0, util.OverwriteError{err, backupName}
380+
}
372381
return 0, err
373382
}
374383

375-
// Backup saved, so cancel pending periodic backup, if any
376-
delete(requestedBackups, b)
384+
if !withSudo {
385+
backupName, resolveName, err = b.writeBackup(path)
386+
if err != nil {
387+
file.Close()
388+
return 0, err
389+
}
390+
// Backup saved, so cancel pending periodic backup, if any
391+
delete(requestedBackups, b)
392+
}
377393

378394
b.forceKeepBackup = true
379395
size := 0

0 commit comments

Comments
 (0)