From 08c7ef9b7f8b4de3b509dec8b36b94bac6e009bb Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 2 Sep 2026 19:09:29 +0200 Subject: [PATCH] Keep the real data-path error, reject an empty data path, and report a crypto/rand failure instead of seeding zero --- data.go | 20 ++++++++++++-------- fejkdata.go | 25 ++++++++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/data.go b/data.go index 3d59bb6..fd2b3f1 100644 --- a/data.go +++ b/data.go @@ -10,13 +10,14 @@ import ( ) // dataSource is one tree to load: an fs.FS and the directory in it to start from. -// label prefixes file names in errors; path, when set, is a directory on disk that -// must exist. +// label prefixes file names in errors; onDisk marks path as a directory that must +// exist. type dataSource struct { - fsys fs.FS - label string - path string - root string + fsys fs.FS + label string + onDisk bool + path string + root string } func (s dataSource) name(p string) string { @@ -36,10 +37,13 @@ func (s dataSource) name(p string) string { func loadData(sources []dataSource) (map[string]node, error) { root := map[string]node{} for _, src := range sources { - if src.path != "" { + if src.onDisk { + if src.path == "" { + return nil, fmt.Errorf("a data path is empty") + } info, err := os.Stat(src.path) if err != nil { - return nil, fmt.Errorf("%s: no such directory", src.path) + return nil, err } if !info.IsDir() { return nil, fmt.Errorf("%s is not a directory", src.path) diff --git a/fejkdata.go b/fejkdata.go index 0af610a..599a73d 100644 --- a/fejkdata.go +++ b/fejkdata.go @@ -75,7 +75,7 @@ func WithSeed(seed uint64) Option { // layer several; the last wins a name clash. func WithDataPath(dir string) Option { return func(c *config) { - c.sources = append(c.sources, dataSource{fsys: os.DirFS(dir), label: dir, path: dir}) + c.sources = append(c.sources, dataSource{fsys: os.DirFS(dir), label: dir, onDisk: true, path: dir}) } } @@ -112,7 +112,11 @@ func New(opts ...Option) (*Generator, error) { if err != nil { return nil, fmt.Errorf("fejkdata: %w", err) } - return &Generator{rand: newRand(c.seed, c.seeded), categories: cats}, nil + rng, err := newRand(c.seed, c.seeded) + if err != nil { + return nil, fmt.Errorf("fejkdata: %w", err) + } + return &Generator{rand: rng, categories: cats}, nil } // List returns the sorted dotted paths Fake can render: every category, the dotted @@ -203,12 +207,19 @@ func join(prefix, name string) string { return prefix + "." + name } -func newRand(seed uint64, seeded bool) *session { - r := rand.New(rand.NewPCG(seed, seed^0x9e3779b97f4a7c15)) - if !seeded { +// randomBytes seeds an unseeded generator; a failure is an error, never a fixed seed. +var randomBytes = crand.Read + +func newRand(seed uint64, seeded bool) (*session, error) { + var r *rand.Rand + if seeded { + r = rand.New(rand.NewPCG(seed, seed^0x9e3779b97f4a7c15)) + } else { var b [16]byte - _, _ = crand.Read(b[:]) + if _, err := randomBytes(b[:]); err != nil { + return nil, fmt.Errorf("seeding from crypto/rand: %w", err) + } r = rand.New(rand.NewPCG(binary.LittleEndian.Uint64(b[:8]), binary.LittleEndian.Uint64(b[8:]))) } - return &session{Rand: r, counters: map[string]uint64{}} + return &session{Rand: r, counters: map[string]uint64{}}, nil }