diff options
| -rw-r--r-- | trash.go | 105 |
1 files changed, 68 insertions, 37 deletions
@@ -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) } |
