package vfs import ( "encoding/hex" "errors" "path" "fmt" "io" "io/fs" "strings" "time" "pkg.jfrech.com/brief/internal/crand" ) func OpenFileRDONLY[T ReadWriteSyncCloser](openFile func(name string, flag int, perm fs.FileMode) (T, error), name string) (io.ReadCloser, error) { return openFile(name, O_RDONLY, 0) } func OpenFileWRONLY[T ReadWriteSyncCloser](mkdirall func(name string, perm fs.FileMode) error, openFile func(name string, flag int, perm fs.FileMode) (T, error), name string) (WriteSyncCloser, error) { var _ = mkdirall(path.Dir(name), 0755) return openFile(name, O_WRONLY|O_CREATE|O_EXCL, 0444) } // exclusiveEmptyFile tries to OS_EXCL create an empty file. It is used to // atomically reserve a filename. func exclusiveEmptyFile(fsys FS, name string) error { f, err := OpenFileWRONLY(fsys.MkdirAll, fsys.OpenFile, name) if err != nil { return err } err = f.Sync() if err != nil { f.Close() fsys.Remove(name) return err } err = f.Close() if err != nil { fsys.Remove(name) return err } return nil } // ExclusiveEmptyFile creates an empty file named similiarly to name0. // It is used to atomically reserve a filename. // // ExclusiveEmptyFileFuzzy cannot fail unless fsys is degraded, at full // capacity or contains more than INT64_MAX entries. func ExclusiveEmptyFileFuzzy(fsys FS, name0 string) (string, error) { ext := path.Ext(name0) woext := strings.TrimSuffix(name0, ext) for n := 1; ; n++ { name := fmt.Sprintf("%s.%d%s", woext, n, ext) if n == 1 { name = name0 } err := exclusiveEmptyFile(fsys, name) if err == nil { return name, nil } else if !errors.Is(err, fs.ErrExist) { return "", err } } } // Tempfile writes content to an exclusive, hidden filename. // // Tempfile should not fail (uuid entropy is 24 bytes) unless fsys is degraded // or at full capacity. func Tempfile(fsys FS, dirname string, content []byte) (string, func(), error) { uniq := fmt.Sprintf("%016x.%s", time.Now().UnixMilli(), hex.EncodeToString(crand.ReadN(24))) name := path.Join(dirname, "." + uniq + ".tmp") cleanup := func() { fsys.Remove(name) } f, err := OpenFileWRONLY(fsys.MkdirAll, fsys.OpenFile, name) if err != nil { return "", nil, err } _, err = f.Write(content) if err != nil { f.Close() cleanup() return "", nil, err } err = f.Sync() if err != nil { f.Close() cleanup() return "", nil, err } err = f.Close() if err != nil { cleanup() return "", nil, err } return name, cleanup, nil }