aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-04-14 17:14:14 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-04-14 17:14:14 -0700
commit9159eca507577b05c2b6124ecbc51d4720e64c96 (patch)
treef803d548060f15fa01be4c570841e667167bcae4
parent77a906dce45ecc334e405c70893ac43825c1f4d3 (diff)
downloadtrash-9159eca507577b05c2b6124ecbc51d4720e64c96.tar.zst
Make error handling more robust
-rw-r--r--trash.go150
1 files changed, 72 insertions, 78 deletions
diff --git a/trash.go b/trash.go
index 126d3fe..37a48ab 100644
--- a/trash.go
+++ b/trash.go
@@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"io"
+ "io/fs"
"net/url"
"os"
"path/filepath"
@@ -11,104 +12,81 @@ import (
"time"
)
-func cp(srcRoot string, dstRoot string) (err error) {
- defer func() {
+func cpEntry(srcDirEntry os.DirEntry, src, dst string) error {
+ switch srcDirEntry.Type() {
+ case 0: // regular file
+ srcF, err := os.Open(src)
if err != nil {
- os.RemoveAll(dstRoot)
+ return err
}
- }()
-
- type roDir struct {
- path string
- mode os.FileMode
- }
- var roDirs []roDir
-
- srcRoot = filepath.Clean(srcRoot)
- if err := filepath.WalkDir(srcRoot, func(src string, d os.DirEntry, err error) error {
+ defer srcF.Close()
+ stat, err := srcF.Stat()
if err != nil {
return err
}
- dst := filepath.Join(dstRoot, strings.TrimPrefix(src, srcRoot))
- switch d.Type() {
- case 0: // regular file
- srcF, err := os.Open(src)
- if err != nil {
- return err
- }
- defer srcF.Close()
- stat, err := srcF.Stat()
- if err != nil {
- return err
- }
- dstF, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, stat.Mode().Perm())
- if err != nil {
- return err
- }
- defer dstF.Close()
- if _, err := io.Copy(dstF, srcF); err != nil {
- return err
- }
- if err := dstF.Close(); err != nil {
- return err
- }
- return nil
- case os.ModeSymlink:
- linkTarget, err := os.Readlink(src)
- if err != nil {
- return err
- }
- return os.Symlink(linkTarget, dst)
- case os.ModeDir:
- stat, err := d.Info()
- if err != nil {
- return err
- }
- perm := stat.Mode().Perm()
- if perm&0300 != 0300 {
- roDirs = append(roDirs, roDir{dst, perm})
- // Make sure we can create directory contents.
- perm |= 0300
- }
- return os.MkdirAll(dst, perm)
- default:
- return fmt.Errorf("unknown file type %s", d.Type())
+ dstF, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, stat.Mode().Perm())
+ if err != nil {
+ return err
}
- }); err != nil {
- return err
- }
- // Iterate backwards to process directory contents before the parent directory.
- for i := len(roDirs) - 1; i >= 0; i-- {
- dir := roDirs[i]
- if err := os.Chmod(dir.path, dir.mode); err != nil {
+ defer dstF.Close()
+ if _, err := io.Copy(dstF, srcF); err != nil {
return err
}
+ return dstF.Close()
+ case os.ModeSymlink:
+ linkTarget, err := os.Readlink(src)
+ if err != nil {
+ return err
+ }
+ return os.Symlink(linkTarget, dst)
+ case os.ModeDir:
+ stat, err := srcDirEntry.Info()
+ if err != nil {
+ return err
+ }
+ entries, err := os.ReadDir(src)
+ if err != nil {
+ return err
+ }
+ if err := os.Mkdir(dst, 0700); err != nil {
+ return err
+ }
+ for _, entry := range entries {
+ name := entry.Name()
+ if err := cpEntry(entry, filepath.Join(src, name), filepath.Join(dst, name)); err != nil {
+ return err
+ }
+ }
+ return os.Chmod(dst, stat.Mode().Perm())
+ default:
+ return fmt.Errorf("unknown file type %s", srcDirEntry.Type())
}
- return nil
}
-func mv(src, dst string) error {
- if err := os.Rename(src, dst); err == nil {
- return nil
+func cp(src, dst string) error {
+ stat, err := os.Lstat(src)
+ if err != nil {
+ return err
}
- if err := cp(src, dst); err != nil {
+ if err := cpEntry(fs.FileInfoToDirEntry(stat), src, dst); err != nil {
+ os.RemoveAll(dst)
return err
}
- return os.RemoveAll(src)
+ return nil
}
-func trashFile(fileName, trash, now string) (err error) {
+func writeTrashInfo(fileName, trash, now string) (_ string, err error) {
absPath, err := filepath.Abs(fileName)
if err != nil {
- return fmt.Errorf("%s: find absolute path: %s", fileName, err)
+ return "", fmt.Errorf("find absolute path: %s", err)
}
info, err := os.CreateTemp(filepath.Join(trash, "info"), filepath.Base(fileName)+"."+now+".*.trashinfo")
if err != nil {
- return fmt.Errorf("%s: create trashinfo: %s", fileName, err)
+ return "", fmt.Errorf("create trashinfo: %s", err)
}
defer func() {
- info.Close()
if err != nil {
+ info.Close()
os.Remove(info.Name())
}
}()
@@ -120,15 +98,31 @@ func trashFile(fileName, trash, now string) (err error) {
Path=%s
DeletionDate=%s
`,
- filepath.Join(escapedPath...),
+ strings.Join(escapedPath, string(filepath.Separator)),
now)
if err != nil {
- return fmt.Errorf("%s: write trashinfo: %s", fileName, err)
+ return "", fmt.Errorf("write trashinfo: %s", err)
}
if err := info.Close(); err != nil {
- return fmt.Errorf("%s: write trashinfo: %s", fileName, err)
+ return "", fmt.Errorf("write trashinfo: %s", err)
+ }
+ return info.Name(), err
+}
+
+func trashFile(fileName, trash, now string) error {
+ trashInfo, err := writeTrashInfo(fileName, trash, now)
+ if err != nil {
+ return fmt.Errorf("%s: %s", fileName, err)
+ }
+ dst := filepath.Join(trash, "files", strings.TrimSuffix(filepath.Base(trashInfo), ".trashinfo"))
+ if err := os.Rename(fileName, dst); err == nil {
+ return nil
+ }
+ if err := cp(fileName, dst); err != nil {
+ os.Remove(trashInfo)
+ return fmt.Errorf("%s: %s", fileName, err)
}
- if err := mv(fileName, filepath.Join(trash, "files", strings.TrimSuffix(filepath.Base(info.Name()), ".trashinfo"))); err != nil {
+ if err := os.RemoveAll(fileName); err != nil {
return fmt.Errorf("%s: %s", fileName, err)
}
return nil