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 unix
2
3package hostfiles
4
5import (
6 "context"
7 "os"
8 "path/filepath"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/release"
12)
13
14// A parent directory swapped for a symlink, to outside or to an excluded
15// directory inside, must not let a queued path be read through it.
16func TestPublishOneNeverReadsThroughASwappedAncestor(t *testing.T) {
17 for name, target := range map[string]string{"a link to outside": "OUTSIDE", "a link to an excluded directory inside": "cache"} {
18 t.Run(name, func(t *testing.T) {
19 ctx, s, src, outside := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir(), t.TempDir()
20 write(t, src, "sub/x", "ours")
21 write(t, src, "cache/x", "excluded")
22 if err := os.WriteFile(filepath.Join(outside, "x"), []byte("outside secret"), 0o600); err != nil {
23 t.Fatal(err)
24 }
25 skip := func(rel string, isDir bool) bool { return isDir && rel == "cache" }
26 p := open(t, s)
27 if _, err := SyncDir(ctx, p, src, skip); err != nil {
28 t.Fatal(err)
29 }
30 commit(t, p)
31 if target == "OUTSIDE" {
32 target = outside
33 }
34 swapAt(t, 1, func() { // before "sub" is opened
35 if err := os.RemoveAll(filepath.Join(src, "sub")); err != nil {
36 t.Error(err)
37 }
38 if err := os.Symlink(target, filepath.Join(src, "sub")); err != nil {
39 t.Error(err)
40 }
41 })
42 if _, err := publishOne(ctx, p, tree(t, src), "sub/x", skip); err != nil {
43 t.Fatal(err)
44 }
45 commit(t, p)
46 m, _, _ := Load(ctx, s, pfx)
47 if e := m.Entries["sub/x"]; !e.Deleted {
48 t.Fatalf("sub/x = %+v; the directory is gone, so the file is: want a tombstone, and no content read through the link", e)
49 }
50 })
51 }
52}
53
54// A queued path whose file exists but cannot be read is an error, and
55// what is published for it stays.
56func TestPublishOneKeepsAnUnreadableFilesPublishedEntry(t *testing.T) {
57 if os.Getuid() == 0 {
58 t.Skip("root can read anything")
59 }
60 ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir()
61 write(t, src, "locked", "v1")
62 p := open(t, s)
63 if _, err := SyncDir(ctx, p, src, nil); err != nil {
64 t.Fatal(err)
65 }
66 commit(t, p)
67 if err := os.Chmod(filepath.Join(src, "locked"), 0); err != nil {
68 t.Fatal(err)
69 }
70 if _, err := publishOne(ctx, p, tree(t, src), "locked", nil); err == nil {
71 t.Fatal("publishOne reported success for a file it could not read")
72 }
73 if p.Dirty() {
74 t.Fatal("publishOne staged a change for a file it could not read")
75 }
76}
77
78// A regular file replaced by another regular file mid-look is read again
79// at the next deadline, not tombstoned.
80func TestPublishOneRetriesARegularFileReplacedMidLook(t *testing.T) {
81 ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir()
82 write(t, src, "f", "v1")
83 p := open(t, s)
84 if _, err := SyncDir(ctx, p, src, nil); err != nil {
85 t.Fatal(err)
86 }
87 commit(t, p)
88 swapAt(t, 1, func() {
89 tmp := filepath.Join(src, "f.new")
90 if err := os.WriteFile(tmp, []byte("v2"), 0o640); err != nil {
91 t.Error(err)
92 }
93 if err := os.Rename(tmp, filepath.Join(src, "f")); err != nil {
94 t.Error(err)
95 }
96 })
97 again, err := publishOne(ctx, p, tree(t, src), "f", nil)
98 if err != nil || !again || p.Dirty() {
99 t.Fatalf("again = %v, err = %v, dirty = %v; want a retry and nothing staged", again, err, p.Dirty())
100 }
101 again, err = publishOne(ctx, p, tree(t, src), "f", nil)
102 if err != nil || again {
103 t.Fatalf("second look: again = %v, err = %v", again, err)
104 }
105 commit(t, p)
106 if !manifestHas(s, "f", sum([]byte("v2"))) {
107 t.Fatal("the replacement was not published")
108 }
109}
110
111// descend must never leave the tree: a component that is empty, "." or
112// ".." is an error, not a missing directory.
113func TestDescendRefusesAnythingButPlainNames(t *testing.T) {
114 src := t.TempDir()
115 write(t, src, "a/x", "x")
116 write(t, src, "b/x", "x")
117 root := tree(t, src)
118 for _, rel := range []string{"..", "a/../b", "./a", "a//b", ".", "a/.", "/a", "a/"} {
119 sub, gone, err := root.descend(rel)
120 if err == nil || gone || sub != nil {
121 if sub != nil && sub != root {
122 sub.Close()
123 }
124 t.Errorf("descend(%q) = (%v, gone=%v, %v); want an error", rel, sub, gone, err)
125 }
126 }
127 if sub, gone, err := root.descend(""); err != nil || gone || sub != root {
128 t.Errorf("descend(\"\") = (%v, %v, %v); want the root itself", sub, gone, err)
129 }
130 sub, gone, err := root.descend("a")
131 if err != nil || gone || sub == nil {
132 t.Fatalf("descend(a) = (%v, %v, %v)", sub, gone, err)
133 }
134 sub.Close()
135}
136