Skip to content

Commit 6b64718

Browse files
alexwlchannickkhyl
authored andcommitted
tka: don't try to read AUMs which are partway through being written
Fixes tailscale#17600 Signed-off-by: Alex Chan <alexc@tailscale.com> (cherry picked from commit 23359dc)
1 parent fa514c7 commit 6b64718

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

tka/tailchonk.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"bytes"
1010
"errors"
1111
"fmt"
12+
"log"
1213
"os"
1314
"path/filepath"
1415
"slices"
@@ -403,9 +404,16 @@ func (c *FS) scanHashes(eachHashInfo func(*fsHashInfo)) error {
403404
return fmt.Errorf("reading prefix dir: %v", err)
404405
}
405406
for _, file := range files {
407+
// Ignore files whose names aren't valid AUM hashes, which may be
408+
// temporary files which are partway through being written, or other
409+
// files added by the OS (like .DS_Store) which we can ignore.
410+
// TODO(alexc): it might be useful to append a suffix like `.aum` to
411+
// filenames, so we can more easily distinguish between AUMs and
412+
// arbitrary other files.
406413
var h AUMHash
407414
if err := h.UnmarshalText([]byte(file.Name())); err != nil {
408-
return fmt.Errorf("invalid aum file: %s: %w", file.Name(), err)
415+
log.Printf("ignoring unexpected non-AUM: %s: %v", file.Name(), err)
416+
continue
409417
}
410418
info, err := c.get(h)
411419
if err != nil {

tka/tailchonk_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"os"
99
"path/filepath"
10+
"slices"
1011
"sync"
1112
"testing"
1213
"time"
@@ -83,6 +84,49 @@ func TestTailchonkFS_CommitTime(t *testing.T) {
8384
}
8485
}
8586

87+
// If we were interrupted while writing a temporary file, AllAUMs()
88+
// should ignore it when scanning the AUM directory.
89+
func TestTailchonkFS_IgnoreTempFile(t *testing.T) {
90+
base := t.TempDir()
91+
chonk := must.Get(ChonkDir(base))
92+
parentHash := randHash(t, 1)
93+
aum := AUM{MessageKind: AUMNoOp, PrevAUMHash: parentHash[:]}
94+
must.Do(chonk.CommitVerifiedAUMs([]AUM{aum}))
95+
96+
writeAUMFile := func(filename, contents string) {
97+
t.Helper()
98+
if err := os.MkdirAll(filepath.Join(base, filename[0:2]), os.ModePerm); err != nil {
99+
t.Fatal(err)
100+
}
101+
if err := os.WriteFile(filepath.Join(base, filename[0:2], filename), []byte(contents), 0600); err != nil {
102+
t.Fatal(err)
103+
}
104+
}
105+
106+
// Check that calling AllAUMs() returns the single committed AUM
107+
got, err := chonk.AllAUMs()
108+
if err != nil {
109+
t.Fatalf("AllAUMs() failed: %v", err)
110+
}
111+
want := []AUMHash{aum.Hash()}
112+
if !slices.Equal(got, want) {
113+
t.Fatalf("AllAUMs() is wrong: got %v, want %v", got, want)
114+
}
115+
116+
// Write some temporary files which are named like partially-committed AUMs,
117+
// then check that AllAUMs() only returns the single committed AUM.
118+
writeAUMFile("AUM1234.tmp", "incomplete AUM\n")
119+
writeAUMFile("AUM1234.tmp_123", "second incomplete AUM\n")
120+
121+
got, err = chonk.AllAUMs()
122+
if err != nil {
123+
t.Fatalf("AllAUMs() failed: %v", err)
124+
}
125+
if !slices.Equal(got, want) {
126+
t.Fatalf("AllAUMs() is wrong: got %v, want %v", got, want)
127+
}
128+
}
129+
86130
func TestMarkActiveChain(t *testing.T) {
87131
type aumTemplate struct {
88132
AUM AUM

0 commit comments

Comments
 (0)