package api import ( "errors" "fmt" "io" "os" "path/filepath" "github.com/mathiasbq/hyperguild/ingestion/internal/extract" ) // errNotANote is returned for a raw/ entry that is not a regular file — in // particular a symlink, whose target may be anywhere (hyperguild#153). var errNotANote = errors.New("not a regular note file in raw/ (symlinks are refused)") // errRawNotDir means brain/raw is not a real directory (e.g. a symlink), so // containment inside it would mean nothing (hyperguild#153 item 4). var errRawNotDir = errors.New("brain/raw is not a real directory") // OpenRawRoot opens brain/raw as an os.Root, refusing a raw/ that is itself a // symlink. Reads through the root cannot leave it, even if an entry is // swapped for a symlink between a check and the read. It returns the root // and raw/'s path. A missing raw/ returns an error wrapping os.ErrNotExist. func OpenRawRoot(brainDir string) (*os.Root, string, error) { p := filepath.Join(brainDir, "raw") fi, err := os.Lstat(p) if err != nil { return nil, "", err } if fi.Mode()&os.ModeSymlink != 0 || !fi.IsDir() { return nil, "", errRawNotDir } r, err := os.OpenRoot(p) if err != nil { return nil, "", fmt.Errorf("open raw root: %w", err) } return r, p, nil } // readRawNote reads one raw/ entry by basename through the root, refusing // anything that is not a regular file. // // Accepted residual: a HARD link to an outside file is a regular file and is // read. Syncthing syncs contents, not links, so a peer cannot create one; // fs.protected_hardlinks=1 and the pod's secrets live on a different // filesystem from the brain volume, so a local caller cannot link them in. // Refusing Nlink > 1 is the next step if either assumption changes. func readRawNote(r *os.Root, base string) ([]byte, os.FileInfo, error) { fi, err := r.Lstat(base) if err != nil { return nil, nil, err } if !fi.Mode().IsRegular() { return nil, nil, errNotANote } f, _, err := extract.OpenRegular(r, base) if err != nil { return nil, nil, err } defer func() { _ = f.Close() }() b, err := io.ReadAll(f) if err != nil { return nil, nil, err } return b, fi, nil }