Skip to content

Commit 91206db

Browse files
committed
Write pidfile atomically
1 parent 0407777 commit 91206db

File tree

4 files changed

+114
-4
lines changed

4 files changed

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

runsc/cmd/pidfile_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
t.Run("Write new file", func(t *testing.T) {
19+
path := filepath.Join(tempDir, "test-new.pid")
20+
if err := WritePidFile(path, 17); err != nil {
21+
t.Fatalf("failed to write pid file: %v", err)
22+
}
23+
24+
pidStr, err := os.ReadFile(path)
25+
if err != nil {
26+
t.Fatalf("failed to read pid file: %v", err)
27+
}
28+
if string(pidStr) != "17" {
29+
t.Fatalf("pid file did not contain pid '17'")
30+
}
31+
})
32+
33+
t.Run("Overwrite existing file", func(t *testing.T) {
34+
path := filepath.Join(tempDir, "test-overwrite.pid")
35+
if err := os.WriteFile(path, []byte("11"), 0600); err != nil {
36+
t.Fatalf("failed to write pid file: %v", err)
37+
}
38+
39+
if err := WritePidFile(path, 19); err != nil {
40+
t.Fatalf("failed to overwrite write pid file: %v", err)
41+
}
42+
pidStr, err := os.ReadFile(path)
43+
if err != nil {
44+
t.Fatalf("failed to read pid file: %v", err)
45+
}
46+
if string(pidStr) != "19" {
47+
t.Fatalf("pid file did not contain pid '19'")
48+
}
49+
})
50+
}

0 commit comments

Comments
 (0)