package util import ( "errors" "os" "path/filepath" "syscall" ) // Writable reports whether path is Writable on Windows. // // Semantics: // - for an existing regular file, it tries to open it for writing; // - for an existing directory, it tries to create and remove a temp file inside it; // - for other file types, it returns false with no error. // // This is intentionally an operational check, not a mode-bit check, because // on Windows effective writability is determined by ACLs and file attributes, // not by POSIX-like permission bits from os.FileMode. func Writable(path string) (bool, error) { info, err := os.Stat(path) if err != nil { return false, err } if info.IsDir() { return writableDir(path) } if !info.Mode().IsRegular() { return false, nil } return writableFile(path) } // writableFile checks whether an existing regular file can be opened for writing. func writableFile(path string) (bool, error) { f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0) if err == nil { _ = f.Close() return true, nil } if isPermissionLikeError(err) { return false, nil } return false, err } // writableDir checks whether a directory allows creating a child file. // That is usually the most useful definition of "directory is writable". func writableDir(path string) (bool, error) { pattern := filepath.Join(path, ".writable-check-*") f, err := os.CreateTemp(path, filepath.Base(pattern)) if err == nil { name := f.Name() _ = f.Close() _ = os.Remove(name) return true, nil } if isPermissionLikeError(err) { return false, nil } return false, err } // isPermissionLikeError normalizes the common Windows "access denied" style // failures to a simple false result instead of surfacing them as hard errors. func isPermissionLikeError(err error) bool { if err == nil { return false } if errors.Is(err, os.ErrPermission) { return true } var errno syscall.Errno if errors.As(err, &errno) { // ERROR_ACCESS_DENIED if errno == syscall.ERROR_ACCESS_DENIED { return true } } var pathErr *os.PathError if errors.As(err, &pathErr) && errors.Is(pathErr.Err, os.ErrPermission) { return true } return false }