carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package privatefile
4
5import (
6 "bytes"
7 "errors"
8 "os"
9 "path/filepath"
10 "sync"
11 "sync/atomic"
12 "testing"
13
14 "golang.org/x/sys/unix"
15)
16
17func privateDir(t *testing.T) string {
18 t.Helper()
19 d := t.TempDir()
20 // The test runner may put TMPDIR behind a symlink. Positive fixtures
21 // use its real directory; symlink-refusal cases add their own links.
22 d, err := filepath.EvalSymlinks(d)
23 if err != nil {
24 t.Fatal(err)
25 }
26 if e := os.Chmod(d, 0700); e != nil {
27 t.Fatal(e)
28 }
29 return d
30}
31func TestExactAndExclusive(t *testing.T) {
32 p := filepath.Join(privateDir(t), "result")
33 b := bytes.Repeat([]byte{0, 255, 10, 32}, 16384)
34 if e := CreateExclusiveDurable(p, b, 65536); e != nil {
35 t.Fatal(e)
36 }
37 got, e := Read(p, 65536)
38 if e != nil || !bytes.Equal(got, b) {
39 t.Fatal("exact read failed", e)
40 }
41 if CreateExclusiveDurable(p, []byte("replacement"), 65536) != ErrUnavailable {
42 t.Fatal("overwrote")
43 }
44 if _, e = Read(p, 65535); e != ErrUnavailable {
45 t.Fatal("accepted oversize")
46 }
47 if CreateExclusiveDurable(p+"big", append(b, 0), 65536) != ErrUnavailable {
48 t.Fatal("accepted oversize create")
49 }
50}
51func TestUnsafeFiles(t *testing.T) {
52 for _, kind := range []string{"mode", "symlink", "ancestor", "hardlink", "fifo", "directory", "relative", "missing", "parent"} {
53 t.Run(kind, func(t *testing.T) {
54 d := privateDir(t)
55 p := filepath.Join(d, "input")
56 if e := os.WriteFile(p, []byte("private"), 0600); e != nil {
57 t.Fatal(e)
58 }
59 switch kind {
60 case "mode":
61 os.Chmod(p, 0640)
62 case "symlink":
63 q := p + "link"
64 os.Symlink(p, q)
65 p = q
66 case "ancestor":
67 q := d + "link"
68 os.Symlink(d, q)
69 t.Cleanup(func() { os.Remove(q) })
70 p = filepath.Join(q, "input")
71 case "hardlink":
72 os.Link(p, p+"link")
73 case "fifo":
74 os.Remove(p)
75 if e := unix.Mkfifo(p, 0600); e != nil {
76 t.Fatal(e)
77 }
78 case "directory":
79 p = d
80 case "relative":
81 p = "relative"
82 case "missing":
83 p += "missing"
84 case "parent":
85 os.Chmod(d, 0755)
86 if CreateExclusiveDurable(p+"new", nil, 10) != ErrUnavailable {
87 t.Fatal("public parent")
88 }
89 return
90 }
91 if _, e := Read(p, 100); e != ErrUnavailable {
92 t.Fatal("unsafe read accepted", e)
93 }
94 if kind == "missing" {
95 return
96 }
97 if e := CreateExclusiveDurable(p, nil, 100); e != ErrUnavailable {
98 t.Fatal("unsafe create accepted", e)
99 }
100 })
101 }
102}
103func TestConcurrentCreate(t *testing.T) {
104 p := filepath.Join(privateDir(t), "race")
105 var wg sync.WaitGroup
106 var successes atomic.Int32
107 for i := 0; i < 32; i++ {
108 wg.Add(1)
109 go func() {
110 defer wg.Done()
111 if CreateExclusiveDurable(p, []byte("exact"), 100) == nil {
112 successes.Add(1)
113 }
114 }()
115 }
116 wg.Wait()
117 if successes.Load() != 1 {
118 t.Fatal(successes.Load())
119 }
120}
121
122type failingFile struct {
123 *os.File
124 stage string
125}
126
127func (f failingFile) Write(b []byte) (int, error) {
128 if f.stage == "write" {
129 n, _ := f.File.Write(b[:1])
130 return n, errors.New("private diagnostic")
131 }
132 return f.File.Write(b)
133}
134func (f failingFile) Sync() error {
135 if f.stage == "sync" {
136 return errors.New("private diagnostic")
137 }
138 return f.File.Sync()
139}
140func (f failingFile) Close() error {
141 e := f.File.Close()
142 if f.stage == "close" {
143 return errors.New("private diagnostic")
144 }
145 return e
146}
147
148type failingParent struct{}
149
150func (failingParent) Sync() error { return errors.New("private diagnostic") }
151func TestFailurePreservesFileWithoutCompletionClaim(t *testing.T) {
152 for _, stage := range []string{"write", "sync", "close", "parent"} {
153 t.Run(stage, func(t *testing.T) {
154 d := privateDir(t)
155 p := filepath.Join(d, "interrupted")
156 f, e := os.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
157 if e != nil {
158 t.Fatal(e)
159 }
160 parent, e := os.Open(d)
161 if e != nil {
162 t.Fatal(e)
163 }
164 defer parent.Close()
165 var barrier interface{ Sync() error } = parent
166 if stage == "parent" {
167 barrier = failingParent{}
168 }
169 if e = finish(failingFile{f, stage}, barrier, []byte("private")); e != ErrUnavailable {
170 t.Fatal(e)
171 }
172 if _, e = os.Stat(p); e != nil {
173 t.Fatal("failed file removed")
174 }
175 if CreateExclusiveDurable(p, []byte("replacement"), 100) != ErrUnavailable {
176 t.Fatal("failed file overwritten")
177 }
178 // A read can succeed even after failed sync; it never proves durability.
179 if _, e = Read(p, 100); e != nil {
180 t.Fatal(e)
181 }
182 })
183 }
184}
185
186func TestCreateThroughSymlinkAndBounds(t *testing.T) {
187 d := privateDir(t)
188 alias := d + "-alias"
189 if e := os.Symlink(d, alias); e != nil {
190 t.Fatal(e)
191 }
192 t.Cleanup(func() { os.Remove(alias) })
193 if CreateExclusiveDurable(filepath.Join(alias, "new"), []byte("secret"), 10) != ErrUnavailable {
194 t.Fatal("ancestor symlink followed")
195 }
196 if _, e := os.Stat(filepath.Join(d, "new")); !os.IsNotExist(e) {
197 t.Fatal("created through alias")
198 }
199 for _, max := range []int{0, -1} {
200 if CreateExclusiveDurable(filepath.Join(d, "new"), nil, max) != ErrUnavailable {
201 t.Fatal("invalid bound")
202 }
203 if _, e := Read(filepath.Join(d, "new"), max); e != ErrUnavailable {
204 t.Fatal("invalid read bound")
205 }
206 }
207}
208