On a headless Linux or BSD system (no DISPLAY or WAYLAND_DISPLAY) nothing empties the freedesktop trash, so trashing never freed space. There, d now deletes permanently after an explicit confirmation, checking owner and type against the scan and removing through an os.Root on the parent so a swapped symlink cannot redirect it. o shows the folder in the map instead of launching a file manager. Desktop sessions, macOS and Windows are unchanged.
82 lines
2.0 KiB
Go
82 lines
2.0 KiB
Go
package platform
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
// TrashName is the user-facing name of the trash.
|
|
const TrashName = "Trash"
|
|
|
|
// Reveal selects the item in Finder.
|
|
func Reveal(path string) error {
|
|
if err := checkAbs(path); err != nil {
|
|
return err
|
|
}
|
|
return startDetached("open", "-R", path)
|
|
}
|
|
|
|
// Trash moves path into ~/.Trash (same volume) or the volume's
|
|
// .Trashes/<uid> directory. It never copies and never deletes.
|
|
func Trash(path string, want Expect) error {
|
|
if err := checkAbs(path); err != nil {
|
|
return err
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dirs := []string{filepath.Join(home, ".Trash")}
|
|
if vol := volumeOf(path); vol != "" {
|
|
dirs = append(dirs, filepath.Join(vol, ".Trashes", fmt.Sprint(os.Getuid())))
|
|
}
|
|
base := filepath.Base(path)
|
|
for vi, dir := range dirs {
|
|
if err := os.MkdirAll(filepath.Dir(dir), 0o700); err != nil {
|
|
continue
|
|
}
|
|
// A volume trash is on a shared filesystem: another user could plant
|
|
// a symlink or an open directory there, so insist on a private one.
|
|
if err := ensurePrivateDir(dir, vi > 0); err != nil {
|
|
continue
|
|
}
|
|
for i := 1; i < 10000; i++ {
|
|
name := base
|
|
if i > 1 {
|
|
name = fmt.Sprintf("%s %d", base, i)
|
|
}
|
|
dst := filepath.Join(dir, name)
|
|
if _, err := os.Lstat(dst); err == nil {
|
|
continue
|
|
}
|
|
err := renameChecked(path, dst, want)
|
|
if errors.Is(err, syscall.EXDEV) {
|
|
break // try the next trash location
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
return errors.New("trash: item is on a volume without a usable trash")
|
|
}
|
|
|
|
func volumeOf(path string) string {
|
|
const prefix = "/Volumes/"
|
|
if len(path) > len(prefix) && path[:len(prefix)] == prefix {
|
|
rest := path[len(prefix):]
|
|
for i := 0; i < len(rest); i++ {
|
|
if rest[i] == '/' {
|
|
return prefix + rest[:i]
|
|
}
|
|
}
|
|
return path
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Headless is always false: macOS has Finder and the Trash even without
|
|
// $DISPLAY (which it does not use).
|
|
func Headless() bool { return false }
|