Skip to content

Commit 199b807

Browse files
committed
Write pidfile atomically
1 parent 3e0b53d commit 199b807

File tree

4 files changed

+94
-4
lines changed

4 files changed

+94
-4
lines changed

runsc/cmd/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ go_library(
5757
"mitigate_extras.go",
5858
"path.go",
5959
"pause.go",
60+
"pidfile.go",
6061
"platforms.go",
6162
"portforward.go",
6263
"ps.go",
@@ -144,6 +145,7 @@ go_test(
144145
"install_test.go",
145146
"list_test.go",
146147
"mitigate_test.go",
148+
"pidfile_test.go",
147149
],
148150
data = [
149151
"//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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package cmd
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/hex"
6+
"fmt"
7+
"os"
8+
"strconv"
9+
)
10+
11+
func fileExists(path string) bool {
12+
_, err := os.Stat(path)
13+
return !os.IsNotExist(err)
14+
}
15+
16+
// WritePidFile writes pid file atomically if possible.
17+
func WritePidFile(path string, pid int) error {
18+
pidStr := []byte(strconv.Itoa(pid))
19+
20+
if fileExists(path) {
21+
// If path exists, write in place, because file could be pipe or something.
22+
if err := os.WriteFile(path, pidStr, 0644); err != nil {
23+
return fmt.Errorf("failed to write pid file %s: %w", path, err)
24+
}
25+
} else {
26+
// Otherwise write using temp file to make write atomic.
27+
b := make([]byte, 8)
28+
_, err := rand.Read(b)
29+
if err != nil {
30+
return fmt.Errorf("failed to generate random bytes: %w", err)
31+
}
32+
tempPath := path + fmt.Sprintf(".%x", hex.EncodeToString(b))
33+
34+
if err := os.WriteFile(tempPath, pidStr, 0644); err != nil {
35+
return fmt.Errorf("failed to write temp pid file %s: %w", tempPath, err)
36+
}
37+
38+
if err := os.Rename(tempPath, path); err != nil {
39+
return fmt.Errorf("failed to rename temp pid file %s -> %s: %w", tempPath, path, err)
40+
}
41+
}
42+
43+
return nil
44+
}

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)