package attach // =============================================================== temporary.go === /* import ( "encoding/base32" "errors" "io/fs" "os" "path/filepath" ) // TODO consider configurability const DotTmp = ".tmp" const TmpEntropy = 40 func (root *Root) TemporaryName() (string, error) { buf := make([]byte, TmpEntropy) _, err := rand.Read(buf) if err != nil { return "", err } return filepath.Join(DotTmp, base32.StdEncoding.EncodeToString(buf)), nil } func (root *Root) WriteTemporaryFile(data []byte, perm fs.FileMode) (string, func(), error) { root.mu.Lock() defer root.mu.Unlock() return root.writeTemporaryFile(data, perm) } // requires a held write lock func (root *Root) writeTemporaryFile(data []byte, perm fs.FileMode) (string, func(), error) { tmpname, err := root.TemporaryName() if err != nil { return "", nil, err } f, err := os.OpenFile(root.Path(tmpname), os.O_EXCL|os.O_CREATE|os.O_WRONLY, perm) if errors.Is(err, fs.ErrNotExist) { os.MkdirAll(filepath.Dir(root.Path(tmpname)), 0755) f, err = os.OpenFile(root.Path(tmpname), os.O_EXCL|os.O_CREATE|os.O_WRONLY, perm) } if err != nil { return "", nil, err } _, err = f.Write(data) if err != nil { f.Close() return "", nil, err } err = f.Sync() if err != nil { f.Close() return "", nil, err } err = f.Close() if err != nil { return "", nil, err } return tmpname, func() { os.Remove(root.Path(tmpname)) os.Remove(filepath.Dir(root.Path(tmpname))) }, nil } */ // =============================================================== write.go === /* import ( "errors" "fmt" "io" "io/fs" "os" "path/filepath" "strings" ) func (root *Root) WriteFile(name string, data []byte, perm fs.FileMode) error { root.mu.Lock() defer root.mu.Unlock() tmpname, cleanup, err := root.writeTemporaryFile(data, perm) if err != nil { return err } defer cleanup() return root.rename(tmpname, name) } //func (root *Root) WriteExclusiveFile(name string, data []byte, perm fs.FileMode) error { // root.mu.Lock() // defer root.mu.Unlock() // // tmpname, cleanup, err := root.writeTemporaryFile(data, perm) // if err != nil { // return err // } // defer cleanup() // // f, err := os.OpenFile(root.Path(name), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0444) // if err != nil { // return err // } // err = f.Close() // if err != nil { // return err // } // // return root.rename(tmpname, name) //} // NOTE: Rename discards data if newname already exists. See Root.RenameWithoutOverriding. func (root Root) Rename(oldname, newname string) error { root.mu.Lock() defer root.mu.Unlock() return root.rename(oldname, newname) } // requires a held write lock func (root *Root) rename(oldname, newname string) error { err := os.Rename(root.Path(oldname), root.Path(newname)) if errors.Is(err, fs.ErrNotExist) { os.MkdirAll(filepath.Dir(root.Path(newname)), 0755) err = os.Rename(root.Path(oldname), root.Path(newname)) } return err } func (root Root) OpenFileForWriting(name string) (io.WriteCloser, error) { return os.OpenFile(root.Path(name), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0444) } //func (root Root) RenameWithoutOverriding(oldname, newname string) error { // os.MkdirAll(filepath.Dir(root.Path(newname)), 0755) // // f, err := root.OpenFileForWriting(newname) // if err != nil { // return err // } // err = f.Close() // if err != nil { // return err // } // // err = root.Rename(oldname, newname) // if err != nil { // os.Remove(oldname) // *should* be an empty file // } // return err //} // RenameNonDestructivelyCloseTo may alter newname. func (root *Root) RenameNonDestructivelyCloseTo(oldname, newname string) (string, error) { root.mu.Lock() defer root.mu.Unlock() newdir, newfile := filepath.Split(newname) for k := 1; k < 1024; k++ { uniquenewfile := newfile if k > 1 { ext := filepath.Ext(newfile) woext := strings.TrimSuffix(newfile, ext) uniquenewfile = woext + fmt.Sprintf(".%d", k) + ext } uniquenewname := filepath.Join(newdir, uniquenewfile) if _, err := root.lstat(uniquenewname); errors.Is(err, fs.ErrNotExist) { return uniquenewname, root.renameNonDestructively(oldname, uniquenewname) } } return "", fmt.Errorf("too many similarily named files") } // requires a held write lock func (root *Root) renameNonDestructively(oldname, newname string) error { f, err := os.OpenFile(root.Path(newname), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0000) if errors.Is(err, fs.ErrNotExist) { os.MkdirAll(filepath.Dir(root.Path(newname)), 0755) f, err = os.OpenFile(root.Path(newname), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0000) } if err != nil { return err } err = f.Close() if err != nil { return err } return root.rename(oldname, newname) } func (root *Root) Copy(oldname, newname string, perm fs.FileMode) error { root.mu.Lock() defer root.mu.Unlock() data, err := root.readFile(oldname) if err != nil { return err } tmpname, cleanup, err := root.writeTemporaryFile(data, perm) if err != nil { return err } defer cleanup() return root.rename(tmpname, newname) } */