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 host
4
5import (
6 "errors"
7 "os"
8 "path/filepath"
9 "strconv"
10 "testing"
11
12 "github.com/carlosframework/platform/internal/stopevidence"
13)
14
15func TestRev4WritersPrefixCoverage(t *testing.T) {
16 for _, shape := range []string{"deleted-wal", "nested", "deleted-nested", "empty-directory"} {
17 t.Run(shape, func(t *testing.T) {
18 fx := newWritersKernelFixture(t)
19 fx.dropAgentHolds(t)
20 // No journal identity exists for a file first opened after launch.
21 fx.journal.Files = nil
22 target := fx.db + "-wal"
23 if shape == "nested" || shape == "deleted-nested" {
24 target = filepath.Join(filepath.Dir(fx.db), "sqlite", "later.db")
25 }
26 if err := os.MkdirAll(filepath.Dir(target), 0700); err != nil {
27 t.Fatal(err)
28 }
29 if err := os.WriteFile(target, nil, 0600); err != nil {
30 t.Fatal(err)
31 }
32 held, err := os.Open(target)
33 if err != nil {
34 t.Fatal(err)
35 }
36 defer held.Close()
37 if shape != "nested" {
38 if err := os.Remove(target); err != nil {
39 t.Fatal(err)
40 }
41 target += " (deleted)"
42 }
43 if shape == "empty-directory" {
44 if err := os.Remove(fx.db); err != nil {
45 t.Fatal(err)
46 }
47 }
48 // A real descriptor retains its inode after unlink, unlike a dangling fixture symlink.
49 pid := strconv.Itoa(os.Getpid())
50 if err := os.Symlink(filepath.Join("/proc", pid), filepath.Join(fx.proc, pid)); err != nil {
51 t.Fatal(err)
52 }
53 if _, err := fx.verify(t); !errors.Is(err, stopevidence.ErrForeignHolder) {
54 t.Fatalf("foreign %s holder passed writers-stopped: %v", shape, err)
55 }
56 })
57 }
58}
59
60func TestRev4WritersPrefixRetainsDescriptorIdentity(t *testing.T) {
61 fx := newWritersKernelFixture(t)
62 target := filepath.Join(filepath.Dir(fx.db), "sqlite", "later.db")
63 if err := os.MkdirAll(filepath.Dir(target), 0700); err != nil {
64 t.Fatal(err)
65 }
66 if err := os.WriteFile(target, nil, 0600); err != nil {
67 t.Fatal(err)
68 }
69 rev3HoldTarget(t, fx, 20, 8, target)
70 dev, ino := fileID(t, target)
71 fx.hints = append(fx.hints, stopevidence.DescriptorHint{FD: 8, Device: dev, Inode: ino})
72 got, err := fx.verify(t)
73 if err != nil {
74 t.Fatal(err)
75 }
76 want := stopevidence.Descriptor{PID: 20, TID: 20, FD: 8, Device: dev, Inode: ino}
77 if len(got.Retained) != 2 || got.Retained[1] != want {
78 t.Fatalf("lost prefix descriptor identity: %+v", got.Retained)
79 }
80 fx.hints = fx.hints[:1]
81 if _, err := fx.verify(t); !errors.Is(err, stopevidence.ErrForeignHolder) {
82 t.Fatalf("unhinted prefix: %v", err)
83 }
84}
85
86func TestRev4WritersPrefixBoundaryAndScanErrors(t *testing.T) {
87 t.Run("sibling", func(t *testing.T) {
88 fx := newWritersKernelFixture(t)
89 target := filepath.Join(filepath.Dir(fx.db)+"-old", "other.db")
90 if err := os.MkdirAll(filepath.Dir(target), 0700); err != nil {
91 t.Fatal(err)
92 }
93 if err := os.WriteFile(target, nil, 0600); err != nil {
94 t.Fatal(err)
95 }
96 rev3HoldTarget(t, fx, 31, 8, target)
97 if _, err := fx.verify(t); err != nil {
98 t.Fatalf("sibling matched: %v", err)
99 }
100 })
101 t.Run("empty-unreadable", func(t *testing.T) {
102 fx := newWritersKernelFixture(t)
103 fx.dropAgentHolds(t)
104 for _, p := range []string{fx.db, fx.db + "-wal"} {
105 if err := os.Remove(p); err != nil {
106 t.Fatal(err)
107 }
108 }
109 dir := filepath.Join(fx.proc, "31", "task", "31", "fd")
110 if err := os.MkdirAll(dir, 0700); err != nil {
111 t.Fatal(err)
112 }
113 if err := os.WriteFile(filepath.Join(dir, "8"), nil, 0600); err != nil {
114 t.Fatal(err)
115 }
116 if _, err := fx.verify(t); err == nil {
117 t.Fatal("unreadable table yielded writers-stopped")
118 }
119 })
120}
121
122func TestRev4WritersDeletedReplicatorIdentity(t *testing.T) {
123 fx := newWritersKernelFixture(t)
124 fx.dropAgentHolds(t)
125 path := fx.db + "-wal"
126 held, err := os.Open(path)
127 if err != nil {
128 t.Fatal(err)
129 }
130 defer held.Close()
131 dev, ino := fileID(t, path)
132 if err := os.Remove(path); err != nil {
133 t.Fatal(err)
134 }
135 // Keep one real kernel descriptor table under the authenticated fixture
136 // process. Readlink reports the deleted name; Stat still reaches the inode.
137 fdDir := filepath.Join(fx.proc, "20", "task", "20", "fd")
138 if err := os.Remove(fdDir); err != nil {
139 t.Fatal(err)
140 }
141 if err := os.Symlink("/proc/self/fd", fdDir); err != nil {
142 t.Fatal(err)
143 }
144 hint := stopevidence.DescriptorHint{FD: int(held.Fd()), Device: dev, Inode: ino}
145 fx.hints = []stopevidence.DescriptorHint{hint}
146 got, err := fx.verify(t)
147 if err != nil {
148 t.Fatal(err)
149 }
150 want := stopevidence.Descriptor{PID: 20, TID: 20, FD: hint.FD, Device: dev, Inode: ino}
151 if len(got.Retained) != 1 || got.Retained[0] != want {
152 t.Fatalf("deleted replicator identity: %+v", got.Retained)
153 }
154 fx.hints[0].Inode++
155 if _, err := fx.verify(t); !errors.Is(err, stopevidence.ErrForeignHolder) {
156 t.Fatalf("substituted hint exempted deleted file: %v", err)
157 }
158}
159