Skip to content

Commit e992734

Browse files
committed
Delete temporary file on termination.
Signed-off-by: Felix Fontein <felix@fontein.de>
1 parent d4bcce9 commit e992734

1 file changed

Lines changed: 57 additions & 8 deletions

File tree

cmd/sops/edit.go

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package main
33
import (
44
"bufio"
55
"bytes"
6+
"context"
67
"crypto/sha256"
78
"fmt"
89
"io"
910
"os"
11+
"os/signal"
1012
"path/filepath"
1113
"strings"
14+
"syscall"
1215

1316
"github.com/getsops/sops/v3"
1417
"github.com/getsops/sops/v3/cmd/sops/codes"
@@ -96,6 +99,24 @@ func edit(opts editOpts) ([]byte, error) {
9699
return editTree(opts, tree, dataKey)
97100
}
98101

102+
type cancelError struct{}
103+
104+
func (err *cancelError) Error() string {
105+
return "User canceled operation"
106+
}
107+
108+
type editTreeResult struct {
109+
value []byte
110+
err error
111+
}
112+
113+
func createError(err error) editTreeResult {
114+
return editTreeResult{
115+
value: nil,
116+
err: err,
117+
}
118+
}
119+
99120
func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) {
100121
// Create temporary file for editing
101122
tmpdir, err := os.MkdirTemp("", "")
@@ -117,33 +138,58 @@ func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) {
117138

118139
tmpfileName := tmpfile.Name()
119140

141+
// Catch when the user presses Ctrl+C, or kills SOPS.
142+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
143+
defer stop()
144+
145+
result := make(chan editTreeResult, 1)
146+
147+
// This goroutine handles signals that exit SOPS, that usually lead to termination
148+
// before editTree() can clean up the temporary directory and file.
149+
go func() {
150+
<-ctx.Done()
151+
result <- createError(&cancelError{})
152+
}()
153+
154+
// This goroutine handles regular execution of editing.
155+
go func() {
156+
result <- editTreeImpl(tmpfile, tmpfileName, opts, tree, dataKey)
157+
}()
158+
159+
// Wait until the first result shows up (either an exit is requested, or editTreeImpl returns).
160+
res := <-result
161+
return res.value, res.err
162+
}
163+
164+
func editTreeImpl(tmpfile *os.File, tmpfileName string, opts editOpts, tree *sops.Tree, dataKey []byte) editTreeResult {
120165
// Write to temporary file
121166
var out []byte
167+
var err error
122168
if opts.ShowMasterKeys {
123169
out, err = opts.OutputStore.EmitEncryptedFile(*tree)
124170
} else {
125171
out, err = opts.OutputStore.EmitPlainFile(tree.Branches)
126172
}
127173
if err != nil {
128-
return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
174+
return createError(common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree))
129175
}
130176
_, err = tmpfile.Write(out)
131177
if err != nil {
132-
return nil, common.NewExitError(fmt.Sprintf("Could not write output file: %s", err), codes.CouldNotWriteOutputFile)
178+
return createError(common.NewExitError(fmt.Sprintf("Could not write output file: %s", err), codes.CouldNotWriteOutputFile))
133179
}
134180

135181
// Compute file hash to detect if the file has been edited
136182
origHash, err := hashFile(tmpfileName)
137183
if err != nil {
138-
return nil, common.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile)
184+
return createError(common.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile))
139185
}
140186

141187
// Close the temporary file, so that an editor can open it.
142188
// We need to do this because some editors (e.g. VSCode) will refuse to
143189
// open a file on Windows due to the Go standard library not opening
144190
// files with shared delete access.
145191
if err := tmpfile.Close(); err != nil {
146-
return nil, err
192+
return createError(err)
147193
}
148194

149195
// Let the user edit the file
@@ -155,23 +201,26 @@ func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) {
155201
ShowMasterKeys: opts.ShowMasterKeys,
156202
Tree: tree})
157203
if err != nil {
158-
return nil, err
204+
return createError(err)
159205
}
160206

161207
// Encrypt the file
162208
err = common.EncryptTree(common.EncryptTreeOpts{
163209
DataKey: dataKey, Tree: tree, Cipher: opts.Cipher,
164210
})
165211
if err != nil {
166-
return nil, err
212+
return createError(err)
167213
}
168214

169215
// Output the file
170216
encryptedFile, err := opts.OutputStore.EmitEncryptedFile(*tree)
171217
if err != nil {
172-
return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
218+
return createError(common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree))
219+
}
220+
return editTreeResult{
221+
value: encryptedFile,
222+
err: nil,
173223
}
174-
return encryptedFile, nil
175224
}
176225

177226
const pressKeyMsg = "Press enter to return to the editor, or Ctrl+C to exit."

0 commit comments

Comments
 (0)