//go:build linux package apply import ( "fmt" "path/filepath" "syscall" "golang.org/x/sys/unix" ) // deviceOf is the device a path lives on; a variable so a test can name another file system. Below a home it is // read through a descriptor opened without following a link (issue 1024). var deviceOf = func(path string) (uint64, error) { var st unix.Stat_t if home := homeAbove(path); home != "" { fd, err := openDirUnder(home, path) if err != nil { return 0, err } defer unix.Close(fd) if err := unix.Fstat(fd, &st); err != nil { return 0, err } return uint64(st.Dev), nil } if err := unix.Stat(path, &st); err != nil { return 0, err } return uint64(st.Dev), nil } // renameUnder moves from to to, both below home, through descriptors of their parents opened without following a // link, and never over something that is there: an account that owns the folders cannot point either at another // place between the look and the move (issue 1024, novox/hq ADR 0266). func renameUnder(home, from, to string) error { fromDir, err := openDirUnder(home, filepath.Dir(from)) if err != nil { return err } defer unix.Close(fromDir) toDir, err := openDirUnder(home, filepath.Dir(to)) if err != nil { return err } defer unix.Close(toDir) if err := unix.Renameat2(fromDir, filepath.Base(from), toDir, filepath.Base(to), unix.RENAME_NOREPLACE); err != nil { return fmt.Errorf("rename %s to %s: %w", from, to, err) } return nil } // ownerIDs is the user and group of a path, without following a link. func ownerIDs(path string) (uid, gid int, ok bool) { var st syscall.Stat_t if err := syscall.Lstat(path, &st); err != nil { return 0, 0, false } return int(st.Uid), int(st.Gid), true }