aboutsummaryrefslogtreecommitdiffstats
path: root/trash.go
diff options
context:
space:
mode:
Diffstat (limited to 'trash.go')
-rw-r--r--trash.go105
1 files changed, 68 insertions, 37 deletions
diff --git a/trash.go b/trash.go
index cf92cfe..126d3fe 100644
--- a/trash.go
+++ b/trash.go
@@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"io"
- "io/fs"
"net/url"
"os"
"path/filepath"
@@ -12,32 +11,28 @@ import (
"time"
)
-func mv(src, dst string) error {
- if err := os.Rename(src, dst); err == nil {
- return nil
+func cp(srcRoot string, dstRoot string) (err error) {
+ defer func() {
+ if err != nil {
+ os.RemoveAll(dstRoot)
+ }
+ }()
+
+ type roDir struct {
+ path string
+ mode os.FileMode
}
- src = filepath.Clean(src)
- if err := filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
+ var roDirs []roDir
+
+ srcRoot = filepath.Clean(srcRoot)
+ if err := filepath.WalkDir(srcRoot, func(src string, d os.DirEntry, err error) error {
if err != nil {
return err
}
- relPath := strings.TrimPrefix(path, src)
- dstPath := filepath.Join(dst, relPath)
+ dst := filepath.Join(dstRoot, strings.TrimPrefix(src, srcRoot))
switch d.Type() {
- case fs.ModeDir:
- stat, err := d.Info()
- if err != nil {
- return err
- }
- return os.Mkdir(dstPath, stat.Mode().Perm())
- case fs.ModeSymlink:
- linkTarget, err := os.Readlink(path)
- if err != nil {
- return err
- }
- return os.Symlink(linkTarget, dstPath)
- case 0:
- srcF, err := os.Open(path)
+ case 0: // regular file
+ srcF, err := os.Open(src)
if err != nil {
return err
}
@@ -46,7 +41,7 @@ func mv(src, dst string) error {
if err != nil {
return err
}
- dstF, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, stat.Mode().Perm())
+ dstF, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, stat.Mode().Perm())
if err != nil {
return err
}
@@ -58,26 +53,66 @@ func mv(src, dst string) error {
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())
}
}); err != nil {
- os.RemoveAll(dst)
+ 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 {
+ return err
+ }
+ }
+ return nil
+}
+
+func mv(src, dst string) error {
+ if err := os.Rename(src, dst); err == nil {
+ return nil
+ }
+ if err := cp(src, dst); err != nil {
return err
}
return os.RemoveAll(src)
}
-func trashFile(fileName, trash, now string) error {
+func trashFile(fileName, trash, now string) (err error) {
absPath, err := filepath.Abs(fileName)
if err != nil {
return fmt.Errorf("%s: find absolute path: %s", fileName, err)
}
- info, err := os.CreateTemp(trash+"/info", filepath.Base(fileName)+"."+now+".*.trashinfo")
+ 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)
}
- escapedPath := strings.Split(absPath, "/")
+ defer func() {
+ info.Close()
+ if err != nil {
+ os.Remove(info.Name())
+ }
+ }()
+ escapedPath := strings.Split(absPath, string(filepath.Separator))
for i, pathSegment := range escapedPath {
escapedPath[i] = url.QueryEscape(pathSegment)
}
@@ -85,19 +120,15 @@ func trashFile(fileName, trash, now string) error {
Path=%s
DeletionDate=%s
`,
- strings.Join(escapedPath, "/"),
+ filepath.Join(escapedPath...),
now)
if err != nil {
- info.Close()
- os.Remove(info.Name())
return fmt.Errorf("%s: write trashinfo: %s", fileName, err)
}
if err := info.Close(); err != nil {
- os.Remove(info.Name())
return fmt.Errorf("%s: write trashinfo: %s", fileName, err)
}
- if err := mv(fileName, trash+"/files/"+strings.TrimSuffix(filepath.Base(info.Name()), ".trashinfo")); err != nil {
- os.Remove(info.Name())
+ if err := mv(fileName, filepath.Join(trash, "files", strings.TrimSuffix(filepath.Base(info.Name()), ".trashinfo"))); err != nil {
return fmt.Errorf("%s: %s", fileName, err)
}
return nil
@@ -116,15 +147,15 @@ func main() {
os.Exit(1)
}
- trash := os.Getenv("HOME") + "/.local/share/Trash"
+ trash := filepath.Join(os.Getenv("HOME"), ".local", "share", "Trash")
if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" {
- trash = xdgDataHome + "/Trash"
+ trash = filepath.Join(xdgDataHome, "Trash")
}
- if err := os.MkdirAll(trash+"/files", 0755); err != nil {
+ if err := os.MkdirAll(filepath.Join(trash, "files"), 0755); err != nil {
fmt.Fprintln(os.Stderr, "trash:", err)
os.Exit(1)
}
- if err := os.MkdirAll(trash+"/info", 0755); err != nil {
+ if err := os.MkdirAll(filepath.Join(trash, "info"), 0755); err != nil {
fmt.Fprintln(os.Stderr, "trash:", err)
os.Exit(1)
}