Skip to content

Commit d3e9fb5

Browse files
stepanchegStepan Koltsov
authored andcommitted
Write pidfile atomically
1 parent 0407777 commit d3e9fb5

File tree

4 files changed

+109
-4
lines changed

4 files changed

+109
-4
lines changed

runsc/cmd/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ go_library(
5858
"mitigate_extras.go",
5959
"path.go",
6060
"pause.go",
61+
"pidfile.go",
6162
"platforms.go",
6263
"portforward.go",
6364
"ps.go",
@@ -145,6 +146,7 @@ go_test(
145146
"install_test.go",
146147
"list_test.go",
147148
"mitigate_test.go",
149+
"pidfile_test.go",
148150
],
149151
data = [
150152
"//runsc",

runsc/cmd/exec.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,16 @@ func (ex *Exec) exec(conf *config.Config, c *container.Container, e *control.Exe
197197

198198
// Write the sandbox-internal pid if required.
199199
if ex.internalPidFile != "" {
200-
pidStr := []byte(strconv.Itoa(int(pid)))
201-
if err := os.WriteFile(ex.internalPidFile, pidStr, 0644); err != nil {
202-
return util.Errorf("writing internal pid file %q: %v", ex.internalPidFile, err)
200+
if err := WritePidFile(ex.internalPidFile, int(pid)); err != nil {
201+
return util.Errorf("writing internal pid file: %v", err)
203202
}
204203
}
205204

206205
// Generate the pid file after the internal pid file is generated, so that
207206
// users can safely assume that the internal pid file is ready after
208207
// `runsc exec -d` returns.
209208
if ex.pidFile != "" {
210-
if err := os.WriteFile(ex.pidFile, []byte(strconv.Itoa(os.Getpid())), 0644); err != nil {
209+
if err := WritePidFile(ex.pidFile, os.Getpid()); err != nil {
211210
return util.Errorf("writing pid file: %v", err)
212211
}
213212
}

runsc/cmd/pidfile.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strconv"
8+
)
9+
10+
func fileExists(path string) bool {
11+
_, err := os.Stat(path)
12+
return !os.IsNotExist(err)
13+
}
14+
15+
// WritePidFile writes pid file atomically if possible.
16+
func WritePidFile(path string, pid int) error {
17+
pidStr := []byte(strconv.Itoa(pid))
18+
19+
if fileExists(path) {
20+
// If path exists, write in place, because file could be pipe or something.
21+
if err := os.WriteFile(path, pidStr, 0644); err != nil {
22+
return fmt.Errorf("failed to write pid file %s: %w", path, err)
23+
}
24+
} else {
25+
// Otherwise write using temp file to make write atomic.
26+
dir := filepath.Dir(path)
27+
tempFile, err := os.CreateTemp(dir, "pid-tmp-*")
28+
if err != nil {
29+
return fmt.Errorf("failed to create temp pid file in dir %s: %w", dir, err)
30+
}
31+
32+
tempFileRenamed := false
33+
defer func(tempFile *os.File) {
34+
_ = tempFile.Close()
35+
if !tempFileRenamed {
36+
_ = os.Remove(tempFile.Name())
37+
}
38+
}(tempFile)
39+
40+
if err := os.Chmod(tempFile.Name(), 0644); err != nil {
41+
return fmt.Errorf("failed to chmod pid file %s: %w", tempFile.Name(), err)
42+
}
43+
44+
if _, err := tempFile.Write(pidStr); err != nil {
45+
return fmt.Errorf("failed to write pid file %s: %w", tempFile.Name(), err)
46+
}
47+
48+
if err := tempFile.Close(); err != nil {
49+
return fmt.Errorf("failed to close temp pid file %s: %w", tempFile.Name(), err)
50+
}
51+
52+
if err := os.Rename(tempFile.Name(), path); err != nil {
53+
return fmt.Errorf("failed to rename temp pid file %s -> %s: %w", tempFile.Name(), path, err)
54+
}
55+
tempFileRenamed = true
56+
}
57+
58+
return nil
59+
}

runsc/cmd/pidfile_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func TestWritePidFile(t *testing.T) {
10+
tempDir, err := os.MkdirTemp("", "test-*")
11+
if err != nil {
12+
t.Fatalf("failed to create temp dir: %v", err)
13+
}
14+
defer func() {
15+
_ = os.RemoveAll(tempDir)
16+
}()
17+
18+
// Write new file.
19+
20+
path := filepath.Join(tempDir, "test.pid")
21+
if err := WritePidFile(path, 17); err != nil {
22+
t.Fatalf("failed to write pid file: %v", err)
23+
}
24+
25+
pidStr, err := os.ReadFile(path)
26+
if err != nil {
27+
t.Fatalf("failed to read pid file: %v", err)
28+
}
29+
if string(pidStr) != "17" {
30+
t.Fatalf("pid file did not contain pid '17'")
31+
}
32+
33+
// Overwrite existing file.
34+
35+
if err := WritePidFile(path, 19); err != nil {
36+
t.Fatalf("failed to overwrite write pid file: %v", err)
37+
}
38+
pidStr, err = os.ReadFile(path)
39+
if err != nil {
40+
t.Fatalf("failed to read pid file: %v", err)
41+
}
42+
if string(pidStr) != "19" {
43+
t.Fatalf("pid file did not contain pid '19'")
44+
}
45+
}

0 commit comments

Comments
 (0)