//go:build linux package hostfiles import ( "crypto/sha256" "encoding/hex" "errors" "os" "path/filepath" "syscall" "testing" "time" "github.com/carlosframework/platform/internal/instancefs" ) // presentBounded runs Present and fails the test if it does not return: a // read that opened a planted FIFO without O_NONBLOCK would block for ever. func presentBounded(t *testing.T, dir, envPath string, m Manifest, envAbsent bool) (bool, error) { t.Helper() type res struct { ok bool err error } done := make(chan res, 1) go func() { ok, err := Present(dir, envPath, m, envAbsent); done <- res{ok, err} }() select { case r := <-done: return r.ok, r.err case <-time.After(20 * time.Second): t.Fatal("Present did not return: it blocked on what the tenant planted") return false, nil } } func presentEntry(body []byte) Entry { sum := sha256.Sum256(body) return Entry{Object: "o", SHA256: hex.EncodeToString(sum[:]), Size: int64(len(body)), Seq: 1} } // Present reads the files directory and the env file, both in the // tenant's instance directory, through main's safe open (instancefs): a // symlink at a name or beneath the root, a FIFO, or a file larger than // its entry is not present and is never read through or waited on; a // symlink at the root itself, or in the path to it, is an error. func TestPresentReadsTenantGroundThroughTheSafeOpen(t *testing.T) { body := []byte("delta") env := []byte("CARLOS_ADMIN_TOKEN=t\n") m := Manifest{V: ManifestVersion, Entries: map[string]Entry{"b/c/d.bin": presentEntry(body)}, Platform: map[string]Entry{EnvItem: presentEntry(env)}} // setup lays out a good install: inst/files/b/c/d.bin and inst/shop.example.env. setup := func(t *testing.T) (files, envPath string) { t.Helper() inst := filepath.Join(t.TempDir(), "inst") files = filepath.Join(inst, "files") if err := os.MkdirAll(filepath.Join(files, "b", "c"), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(files, "b", "c", "d.bin"), body, 0o600); err != nil { t.Fatal(err) } envPath = filepath.Join(inst, "shop.example.env") if err := os.WriteFile(envPath, env, 0o600); err != nil { t.Fatal(err) } if ok, err := Present(files, envPath, m, false); err != nil || !ok { t.Fatalf("a good install: %v %v", ok, err) } return files, envPath } // linkTo replaces path with a symlink to a copy of the same bytes or // directory elsewhere, so a read that followed it would be satisfied. linkTo := func(t *testing.T, path string) { t.Helper() moved := filepath.Join(t.TempDir(), "moved") if err := os.Rename(path, moved); err != nil { t.Fatal(err) } if err := os.Symlink(moved, path); err != nil { t.Fatal(err) } } fifo := func(t *testing.T, path string) { t.Helper() if err := os.Remove(path); err != nil { t.Fatal(err) } if err := syscall.Mkfifo(path, 0o600); err != nil { t.Fatal(err) } } grow := func(t *testing.T, path string, keep []byte) { t.Helper() if err := os.WriteFile(path, append(append([]byte(nil), keep...), "more"...), 0o600); err != nil { t.Fatal(err) } } for _, tc := range []struct { name string plant func(t *testing.T, files, envPath string) wantErr error // nil: not present, no error }{ {"a symlink at a file", func(t *testing.T, files, _ string) { linkTo(t, filepath.Join(files, "b", "c", "d.bin")) }, nil}, {"a symlinked directory beneath the root", func(t *testing.T, files, _ string) { linkTo(t, filepath.Join(files, "b")) }, nil}, {"a FIFO at a file", func(t *testing.T, files, _ string) { fifo(t, filepath.Join(files, "b", "c", "d.bin")) }, nil}, {"a file larger than its entry", func(t *testing.T, files, _ string) { grow(t, filepath.Join(files, "b", "c", "d.bin"), body) }, nil}, {"a symlink at the files root", func(t *testing.T, files, _ string) { linkTo(t, files) }, instancefs.ErrSymlink}, {"a symlink at the env file", func(t *testing.T, _, envPath string) { linkTo(t, envPath) }, nil}, {"a FIFO at the env file", func(t *testing.T, _, envPath string) { fifo(t, envPath) }, nil}, {"an env file larger than its entry", func(t *testing.T, _, envPath string) { grow(t, envPath, env) }, nil}, {"a symlinked instance directory", func(t *testing.T, _, envPath string) { linkTo(t, filepath.Dir(envPath)) }, instancefs.ErrSymlink}, } { t.Run(tc.name, func(t *testing.T) { files, envPath := setup(t) tc.plant(t, files, envPath) ok, err := presentBounded(t, files, envPath, m, false) if ok || !errors.Is(err, tc.wantErr) || (tc.wantErr == nil && err != nil) { t.Fatalf("Present = %v, %v; want false, %v", ok, err, tc.wantErr) } }) } } // With the caller's evidence of no env file, an env file is "none" only // when its directory opened with no symlink anywhere and the name is not // in it: a FIFO or a link there is something, not none, and is never // opened blocking; a linked directory is an error; a missing one is not // evidence. func TestPresentReadsNoEnvFileOnlyFromADirectoryThatOpened(t *testing.T) { none := Manifest{V: ManifestVersion, Entries: map[string]Entry{}, Platform: map[string]Entry{}} for _, tc := range []struct { name string plant func(t *testing.T, envPath string) want bool wantErr error }{ {"none there", func(*testing.T, string) {}, true, nil}, {"a FIFO", func(t *testing.T, envPath string) { if err := syscall.Mkfifo(envPath, 0o600); err != nil { t.Fatal(err) } }, false, nil}, {"a symlink to nothing", func(t *testing.T, envPath string) { if err := os.Symlink(filepath.Join(t.TempDir(), "nothing"), envPath); err != nil { t.Fatal(err) } }, false, nil}, {"a symlinked directory", func(t *testing.T, envPath string) { dir := filepath.Dir(envPath) moved := filepath.Join(t.TempDir(), "moved") if err := os.Rename(dir, moved); err != nil { t.Fatal(err) } if err := os.Symlink(moved, dir); err != nil { t.Fatal(err) } }, false, instancefs.ErrSymlink}, {"a missing directory", func(t *testing.T, envPath string) { if err := os.Remove(filepath.Dir(envPath)); err != nil { t.Fatal(err) } }, false, nil}, } { t.Run(tc.name, func(t *testing.T) { dir := filepath.Join(t.TempDir(), "inst") if err := os.Mkdir(dir, 0o700); err != nil { t.Fatal(err) } envPath := filepath.Join(dir, "shop.example.env") tc.plant(t, envPath) ok, err := presentBounded(t, t.TempDir(), envPath, none, true) if ok != tc.want || !errors.Is(err, tc.wantErr) || (tc.wantErr == nil && err != nil) { t.Fatalf("Present = %v, %v; want %v, %v", ok, err, tc.want, tc.wantErr) } }) } } // A file far larger than its entry is answered from its size, never read // whole: a tenant's sparse terabyte costs nothing to make. func TestPresentNeverReadsPastAnEntrysSize(t *testing.T) { body := []byte("delta") m := Manifest{V: ManifestVersion, Entries: map[string]Entry{"big": presentEntry(body)}, Platform: map[string]Entry{}} dir := t.TempDir() f, err := os.Create(filepath.Join(dir, "big")) if err != nil { t.Fatal(err) } if _, err := f.Write(body); err != nil { t.Fatal(err) } if err := f.Truncate(1 << 40); err != nil { f.Close() t.Skipf("no sparse terabyte here: %v", err) } f.Close() if ok, err := presentBounded(t, dir, filepath.Join(t.TempDir(), "shop.example.env"), m, true); ok || err != nil { t.Fatalf("Present = %v, %v; want false", ok, err) } } // An empty entry's size and digest are what a FIFO with no writer reads // as (zero bytes, then EOF), so only the regular-file check tells the two // apart: a FIFO at an empty file's name is not present. func TestPresentNeverTakesAFIFOForAnEmptyFile(t *testing.T) { m := Manifest{V: ManifestVersion, Entries: map[string]Entry{"empty": presentEntry(nil)}, Platform: map[string]Entry{}} dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, "empty"), nil, 0o600); err != nil { t.Fatal(err) } env := filepath.Join(t.TempDir(), "shop.example.env") if ok, err := Present(dir, env, m, true); err != nil || !ok { t.Fatalf("an empty file: %v %v", ok, err) } if err := os.Remove(filepath.Join(dir, "empty")); err != nil { t.Fatal(err) } if err := syscall.Mkfifo(filepath.Join(dir, "empty"), 0o600); err != nil { t.Fatal(err) } if ok, err := presentBounded(t, dir, env, m, true); ok || err != nil { t.Fatalf("a FIFO at an empty file's name: %v, %v; want false", ok, err) } }