mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-02-02 05:41:10 -08:00
552 lines
16 KiB
Go
552 lines
16 KiB
Go
//go:build windows
|
|
|
|
package environment
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
|
|
"github.com/Azure/go-ansiterm/winterm"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func (env *ShellEnvironment) Root() bool {
|
|
defer env.Trace(time.Now(), "Root")
|
|
var sid *windows.SID
|
|
|
|
// Although this looks scary, it is directly copied from the
|
|
// official windows documentation. The Go API for this is a
|
|
// direct wrap around the official C++ API.
|
|
// See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
|
|
err := windows.AllocateAndInitializeSid(
|
|
&windows.SECURITY_NT_AUTHORITY,
|
|
2,
|
|
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
|
windows.DOMAIN_ALIAS_RID_ADMINS,
|
|
0, 0, 0, 0, 0, 0,
|
|
&sid)
|
|
if err != nil {
|
|
env.Log(Error, "Root", err.Error())
|
|
return false
|
|
}
|
|
defer func() {
|
|
_ = windows.FreeSid(sid)
|
|
}()
|
|
|
|
// This appears to cast a null pointer so I'm not sure why this
|
|
// works, but this guy says it does and it Works for Me™:
|
|
// https://github.com/golang/go/issues/28804#issuecomment-438838144
|
|
token := windows.Token(0)
|
|
|
|
member, err := token.IsMember(sid)
|
|
if err != nil {
|
|
env.Log(Error, "Root", err.Error())
|
|
return false
|
|
}
|
|
|
|
return member
|
|
}
|
|
|
|
func (env *ShellEnvironment) Home() string {
|
|
home := os.Getenv("HOME")
|
|
defer func() {
|
|
env.Log(Debug, "Home", home)
|
|
}()
|
|
if len(home) > 0 {
|
|
return home
|
|
}
|
|
// fallback to older implemenations on Windows
|
|
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
|
if home == "" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
return home
|
|
}
|
|
|
|
func (env *ShellEnvironment) QueryWindowTitles(processName, windowTitleRegex string) (string, error) {
|
|
defer env.Trace(time.Now(), "WindowTitle", windowTitleRegex)
|
|
title, err := queryWindowTitles(processName, windowTitleRegex)
|
|
if err != nil {
|
|
env.Log(Error, "QueryWindowTitles", err.Error())
|
|
}
|
|
return title, err
|
|
}
|
|
|
|
func (env *ShellEnvironment) IsWsl() bool {
|
|
defer env.Trace(time.Now(), "IsWsl")
|
|
return false
|
|
}
|
|
|
|
func (env *ShellEnvironment) IsWsl2() bool {
|
|
defer env.Trace(time.Now(), "IsWsl2")
|
|
return false
|
|
}
|
|
|
|
func (env *ShellEnvironment) TerminalWidth() (int, error) {
|
|
defer env.Trace(time.Now(), "TerminalWidth")
|
|
if env.CmdFlags.TerminalWidth != 0 {
|
|
return env.CmdFlags.TerminalWidth, nil
|
|
}
|
|
handle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
|
if err != nil {
|
|
env.Log(Error, "TerminalWidth", err.Error())
|
|
return 0, err
|
|
}
|
|
info, err := winterm.GetConsoleScreenBufferInfo(uintptr(handle))
|
|
if err != nil {
|
|
env.Log(Error, "TerminalWidth", err.Error())
|
|
return 0, err
|
|
}
|
|
// return int(float64(info.Size.X) * 0.57), nil
|
|
return int(info.Size.X), nil
|
|
}
|
|
|
|
func (env *ShellEnvironment) Platform() string {
|
|
return WindowsPlatform
|
|
}
|
|
|
|
func (env *ShellEnvironment) CachePath() string {
|
|
defer env.Trace(time.Now(), "CachePath")
|
|
// get LOCALAPPDATA if present
|
|
if cachePath := returnOrBuildCachePath(env.Getenv("LOCALAPPDATA")); len(cachePath) != 0 {
|
|
return cachePath
|
|
}
|
|
return env.Home()
|
|
}
|
|
|
|
func (env *ShellEnvironment) LookWinAppPath(file string) (string, error) {
|
|
winAppPath := filepath.Join(env.Getenv("LOCALAPPDATA"), `\Microsoft\WindowsApps\`)
|
|
command := file + ".exe"
|
|
isWinStoreApp := func() bool {
|
|
return env.HasFilesInDir(winAppPath, command)
|
|
}
|
|
if isWinStoreApp() {
|
|
commandFile := filepath.Join(winAppPath, command)
|
|
return readWinAppLink(commandFile)
|
|
}
|
|
return "", errors.New("no Windows Store App")
|
|
}
|
|
|
|
//
|
|
// Takes a registry path to a key like
|
|
// "HKLM\Software\Microsoft\Windows NT\CurrentVersion\EditionID"
|
|
//
|
|
// The last part of the path is the key to retrieve.
|
|
//
|
|
// If the path ends in "\", the "(Default)" key in that path is retrieved.
|
|
//
|
|
// Returns a variant type if successful; nil and an error if not.
|
|
//
|
|
func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegistryValue, error) {
|
|
env.Trace(time.Now(), "WindowsRegistryKeyValue", path)
|
|
|
|
// Format:
|
|
// "HKLM\Software\Microsoft\Windows NT\CurrentVersion\EditionID"
|
|
// 1 | 2 | 3
|
|
//
|
|
// Split into:
|
|
//
|
|
// 1. Root key - extract the root HKEY string and turn this into a handle to get started
|
|
// 2. Path - open this path
|
|
// 3. Key - get this key value
|
|
//
|
|
// If 3 is "" (i.e. the path ends with "\"), then get (Default) key.
|
|
//
|
|
regPathParts := strings.SplitN(path, "\\", 2)
|
|
|
|
if len(regPathParts) < 2 {
|
|
errorLogMsg := fmt.Sprintf("Error, malformed registry path: '%s'", path)
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
|
|
regRootHKeyHandle := getHKEYHandleFromAbbrString(regPathParts[0])
|
|
if regRootHKeyHandle == 0 {
|
|
errorLogMsg := fmt.Sprintf("Error, Supplied root HKEY value not valid: '%s'", regPathParts[0])
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
|
|
// Strip key off the end.
|
|
lastSlash := strings.LastIndex(regPathParts[1], "\\")
|
|
|
|
if lastSlash < 0 {
|
|
errorLogMsg := fmt.Sprintf("Error, malformed registry path: '%s'", path)
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
|
|
regKey := regPathParts[1][lastSlash+1:]
|
|
regPath := regPathParts[1][0:lastSlash]
|
|
|
|
// Just for debug log display.
|
|
regKeyLogged := regKey
|
|
if len(regKeyLogged) == 0 {
|
|
regKeyLogged = "(Default)"
|
|
}
|
|
env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("WindowsRegistryKeyValue: root:\"%s\", path:\"%s\", key:\"%s\"", regPathParts[0], regPath, regKeyLogged))
|
|
|
|
// Second part of split is registry path after HK part - needs to be UTF16 to pass to the windows. API
|
|
regPathUTF16, err := windows.UTF16FromString(regPath)
|
|
if err != nil {
|
|
errorLogMsg := fmt.Sprintf("Error, Could not convert supplied path '%s' to UTF16, error: '%s'", regPath, err)
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
|
|
// Ok - open it..
|
|
var hKeyHandle windows.Handle
|
|
regOpenErr := windows.RegOpenKeyEx(regRootHKeyHandle, ®PathUTF16[0], 0, windows.KEY_READ, &hKeyHandle)
|
|
if regOpenErr != nil {
|
|
errorLogMsg := fmt.Sprintf("Error RegOpenKeyEx opening registry path to '%s', error: '%s'", regPath, regOpenErr)
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
// Success - from here on out, when returning make sure to close that reg key with a deferred call to close:
|
|
defer func() {
|
|
err := windows.RegCloseKey(hKeyHandle)
|
|
if err != nil {
|
|
env.Log(Error, "WindowsRegistryKeyValue", fmt.Sprintf("Error closing registry key: %s", err))
|
|
}
|
|
}()
|
|
|
|
// Again - need UTF16 of the key for the API:
|
|
regKeyUTF16, err := windows.UTF16FromString(regKey)
|
|
if err != nil {
|
|
errorLogMsg := fmt.Sprintf("Error, could not convert supplied key '%s' to UTF16, error: '%s'", regKey, err)
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
|
|
// Two stage way to get the key value - query once to get size - then allocate and query again to fill it.
|
|
var keyBufType uint32
|
|
var keyBufSize uint32
|
|
|
|
regQueryErr := windows.RegQueryValueEx(hKeyHandle, ®KeyUTF16[0], nil, &keyBufType, nil, &keyBufSize)
|
|
if regQueryErr != nil {
|
|
errorLogMsg := fmt.Sprintf("Error calling RegQueryValueEx to retrieve key data size with error '%s'", regQueryErr)
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
|
|
// Alloc and fill...
|
|
var keyBuf = make([]byte, keyBufSize)
|
|
|
|
regQueryErr = windows.RegQueryValueEx(hKeyHandle, ®KeyUTF16[0], nil, &keyBufType, &keyBuf[0], &keyBufSize)
|
|
if regQueryErr != nil {
|
|
errorLogMsg := fmt.Sprintf("Error calling RegQueryValueEx to retrieve key data with error '%s'", regQueryErr)
|
|
env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
|
|
// Format result into a string, depending on type. (future refactor - move this out into it's own function)
|
|
switch keyBufType {
|
|
case windows.REG_SZ:
|
|
var uint16p *uint16
|
|
uint16p = (*uint16)(unsafe.Pointer(&keyBuf[0])) // nasty casty
|
|
|
|
valueString := windows.UTF16PtrToString(uint16p)
|
|
env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, string: %s", valueString))
|
|
|
|
return &WindowsRegistryValue{ValueType: RegString, Str: valueString}, nil
|
|
case windows.REG_DWORD:
|
|
var uint32p *uint32
|
|
uint32p = (*uint32)(unsafe.Pointer(&keyBuf[0])) // more casting goodness
|
|
|
|
env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, DWORD, 0x%08X", *uint32p))
|
|
return &WindowsRegistryValue{ValueType: RegDword, Dword: *uint32p}, nil
|
|
case windows.REG_QWORD:
|
|
var uint64p *uint64
|
|
uint64p = (*uint64)(unsafe.Pointer(&keyBuf[0])) // more casting goodness
|
|
|
|
env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, QWORD, 0x%016X", *uint64p))
|
|
return &WindowsRegistryValue{ValueType: RegQword, Qword: *uint64p}, nil
|
|
default:
|
|
errorLogMsg := fmt.Sprintf("Error, no formatter for REG_? type:%d, data size:%d bytes", keyBufType, keyBufSize)
|
|
return nil, errors.New(errorLogMsg)
|
|
}
|
|
}
|
|
|
|
func (env *ShellEnvironment) InWSLSharedDrive() bool {
|
|
return false
|
|
}
|
|
|
|
func (env *ShellEnvironment) ConvertToWindowsPath(path string) string {
|
|
return path
|
|
}
|
|
|
|
func (env *ShellEnvironment) ConvertToLinuxPath(path string) string {
|
|
return path
|
|
}
|
|
|
|
var (
|
|
hapi = syscall.NewLazyDLL("wlanapi.dll")
|
|
hWlanOpenHandle = hapi.NewProc("WlanOpenHandle")
|
|
hWlanCloseHandle = hapi.NewProc("WlanCloseHandle")
|
|
hWlanEnumInterfaces = hapi.NewProc("WlanEnumInterfaces")
|
|
hWlanQueryInterface = hapi.NewProc("WlanQueryInterface")
|
|
)
|
|
|
|
const (
|
|
FHSS WifiType = "FHSS"
|
|
DSSS WifiType = "DSSS"
|
|
IR WifiType = "IR"
|
|
A WifiType = "802.11a"
|
|
HRDSSS WifiType = "HRDSSS"
|
|
G WifiType = "802.11g"
|
|
N WifiType = "802.11n"
|
|
AC WifiType = "802.11ac"
|
|
|
|
Infrastructure WifiType = "Infrastructure"
|
|
Independent WifiType = "Independent"
|
|
Any WifiType = "Any"
|
|
|
|
OpenSystem WifiType = "802.11 Open System"
|
|
SharedKey WifiType = "802.11 Shared Key"
|
|
WPA WifiType = "WPA"
|
|
WPAPSK WifiType = "WPA PSK"
|
|
WPANone WifiType = "WPA NONE"
|
|
WPA2 WifiType = "WPA2"
|
|
WPA2PSK WifiType = "WPA2 PSK"
|
|
Disabled WifiType = "disabled"
|
|
|
|
None WifiType = "None"
|
|
WEP40 WifiType = "WEP40"
|
|
TKIP WifiType = "TKIP"
|
|
CCMP WifiType = "CCMP"
|
|
WEP104 WifiType = "WEP104"
|
|
WEP WifiType = "WEP"
|
|
)
|
|
|
|
func (env *ShellEnvironment) WifiNetwork() (*WifiInfo, error) {
|
|
env.Trace(time.Now(), "WifiNetwork")
|
|
// Open handle
|
|
var pdwNegotiatedVersion uint32
|
|
var phClientHandle uint32
|
|
e, _, err := hWlanOpenHandle.Call(uintptr(uint32(2)), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&pdwNegotiatedVersion)), uintptr(unsafe.Pointer(&phClientHandle)))
|
|
if e != 0 {
|
|
return nil, err
|
|
}
|
|
|
|
// defer closing handle
|
|
defer func() {
|
|
_, _, _ = hWlanCloseHandle.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil)))
|
|
}()
|
|
|
|
// list interfaces
|
|
var interfaceList *WLAN_INTERFACE_INFO_LIST
|
|
e, _, err = hWlanEnumInterfaces.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&interfaceList)))
|
|
if e != 0 {
|
|
return nil, err
|
|
}
|
|
|
|
// use first interface that is connected
|
|
numberOfInterfaces := int(interfaceList.dwNumberOfItems)
|
|
infoSize := unsafe.Sizeof(interfaceList.InterfaceInfo[0])
|
|
for i := 0; i < numberOfInterfaces; i++ {
|
|
network := (*WLAN_INTERFACE_INFO)(unsafe.Pointer(uintptr(unsafe.Pointer(&interfaceList.InterfaceInfo[0])) + uintptr(i)*infoSize))
|
|
if network.isState != 1 {
|
|
continue
|
|
}
|
|
return env.parseNetworkInterface(network, phClientHandle)
|
|
}
|
|
return nil, errors.New("Not connected")
|
|
}
|
|
|
|
func (env *ShellEnvironment) parseNetworkInterface(network *WLAN_INTERFACE_INFO, clientHandle uint32) (*WifiInfo, error) {
|
|
info := WifiInfo{}
|
|
info.Interface = strings.TrimRight(string(utf16.Decode(network.strInterfaceDescription[:])), "\x00")
|
|
|
|
// Query wifi connection state
|
|
var dataSize uint16
|
|
var wlanAttr *WLAN_CONNECTION_ATTRIBUTES
|
|
e, _, err := hWlanQueryInterface.Call(uintptr(clientHandle),
|
|
uintptr(unsafe.Pointer(&network.InterfaceGuid)),
|
|
uintptr(7), // wlan_intf_opcode_current_connection
|
|
uintptr(unsafe.Pointer(nil)),
|
|
uintptr(unsafe.Pointer(&dataSize)),
|
|
uintptr(unsafe.Pointer(&wlanAttr)),
|
|
uintptr(unsafe.Pointer(nil)))
|
|
if e != 0 {
|
|
env.Log(Error, "parseNetworkInterface", "wlan_intf_opcode_current_connection error")
|
|
return &info, err
|
|
}
|
|
|
|
// SSID
|
|
ssid := wlanAttr.wlanAssociationAttributes.dot11Ssid
|
|
if ssid.uSSIDLength > 0 {
|
|
info.SSID = string(ssid.ucSSID[0:ssid.uSSIDLength])
|
|
}
|
|
|
|
// see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-phy-type
|
|
switch wlanAttr.wlanAssociationAttributes.dot11PhyType {
|
|
case 1:
|
|
info.PhysType = FHSS
|
|
case 2:
|
|
info.PhysType = DSSS
|
|
case 3:
|
|
info.PhysType = IR
|
|
case 4:
|
|
info.PhysType = A
|
|
case 5:
|
|
info.PhysType = HRDSSS
|
|
case 6:
|
|
info.PhysType = G
|
|
case 7:
|
|
info.PhysType = N
|
|
case 8:
|
|
info.PhysType = AC
|
|
default:
|
|
info.PhysType = Unknown
|
|
}
|
|
|
|
// see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-bss-type
|
|
switch wlanAttr.wlanAssociationAttributes.dot11BssType {
|
|
case 1:
|
|
info.RadioType = Infrastructure
|
|
case 2:
|
|
info.RadioType = Independent
|
|
default:
|
|
info.RadioType = Any
|
|
}
|
|
|
|
info.Signal = int(wlanAttr.wlanAssociationAttributes.wlanSignalQuality)
|
|
info.TransmitRate = int(wlanAttr.wlanAssociationAttributes.ulTxRate) / 1024
|
|
info.ReceiveRate = int(wlanAttr.wlanAssociationAttributes.ulRxRate) / 1024
|
|
|
|
// Query wifi channel
|
|
dataSize = 0
|
|
var channel *uint32
|
|
e, _, err = hWlanQueryInterface.Call(uintptr(clientHandle),
|
|
uintptr(unsafe.Pointer(&network.InterfaceGuid)),
|
|
uintptr(8), // wlan_intf_opcode_channel_number
|
|
uintptr(unsafe.Pointer(nil)),
|
|
uintptr(unsafe.Pointer(&dataSize)),
|
|
uintptr(unsafe.Pointer(&channel)),
|
|
uintptr(unsafe.Pointer(nil)))
|
|
if e != 0 {
|
|
env.Log(Error, "parseNetworkInterface", "wlan_intf_opcode_channel_number error")
|
|
return &info, err
|
|
}
|
|
info.Channel = int(*channel)
|
|
|
|
if wlanAttr.wlanSecurityAttributes.bSecurityEnabled <= 0 {
|
|
info.Authentication = Disabled
|
|
return &info, nil
|
|
}
|
|
|
|
// see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-auth-algorithm
|
|
switch wlanAttr.wlanSecurityAttributes.dot11AuthAlgorithm {
|
|
case 1:
|
|
info.Authentication = OpenSystem
|
|
case 2:
|
|
info.Authentication = SharedKey
|
|
case 3:
|
|
info.Authentication = WPA
|
|
case 4:
|
|
info.Authentication = WPAPSK
|
|
case 5:
|
|
info.Authentication = WPANone
|
|
case 6:
|
|
info.Authentication = WPA2
|
|
case 7:
|
|
info.Authentication = WPA2PSK
|
|
default:
|
|
info.Authentication = Unknown
|
|
}
|
|
|
|
// see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-cipher-algorithm
|
|
switch wlanAttr.wlanSecurityAttributes.dot11CipherAlgorithm {
|
|
case 0:
|
|
info.Cipher = None
|
|
case 0x1:
|
|
info.Cipher = WEP40
|
|
case 0x2:
|
|
info.Cipher = TKIP
|
|
case 0x4:
|
|
info.Cipher = CCMP
|
|
case 0x5:
|
|
info.Cipher = WEP104
|
|
case 0x100:
|
|
info.Cipher = WPA
|
|
case 0x101:
|
|
info.Cipher = WEP
|
|
default:
|
|
info.Cipher = Unknown
|
|
}
|
|
|
|
return &info, nil
|
|
}
|
|
|
|
type WLAN_INTERFACE_INFO_LIST struct { // nolint: revive
|
|
dwNumberOfItems uint32
|
|
dwIndex uint32 // nolint: unused
|
|
InterfaceInfo [1]WLAN_INTERFACE_INFO
|
|
}
|
|
|
|
type WLAN_INTERFACE_INFO struct { // nolint: revive
|
|
InterfaceGuid syscall.GUID // nolint: revive
|
|
strInterfaceDescription [256]uint16
|
|
isState uint32
|
|
}
|
|
|
|
type WLAN_CONNECTION_ATTRIBUTES struct { // nolint: revive
|
|
isState uint32 // nolint: unused
|
|
wlanConnectionMode uint32 // nolint: unused
|
|
strProfileName [256]uint16 // nolint: unused
|
|
wlanAssociationAttributes WLAN_ASSOCIATION_ATTRIBUTES
|
|
wlanSecurityAttributes WLAN_SECURITY_ATTRIBUTES
|
|
}
|
|
|
|
type WLAN_ASSOCIATION_ATTRIBUTES struct { // nolint: revive
|
|
dot11Ssid DOT11_SSID
|
|
dot11BssType uint32
|
|
dot11Bssid [6]uint8 // nolint: unused
|
|
dot11PhyType uint32
|
|
uDot11PhyIndex uint32 // nolint: unused
|
|
wlanSignalQuality uint32
|
|
ulRxRate uint32
|
|
ulTxRate uint32
|
|
}
|
|
|
|
type WLAN_SECURITY_ATTRIBUTES struct { // nolint: revive
|
|
bSecurityEnabled uint32
|
|
bOneXEnabled uint32 // nolint: unused
|
|
dot11AuthAlgorithm uint32
|
|
dot11CipherAlgorithm uint32
|
|
}
|
|
|
|
type DOT11_SSID struct { // nolint: revive
|
|
uSSIDLength uint32
|
|
ucSSID [32]uint8
|
|
}
|
|
|
|
func (env *ShellEnvironment) DirIsWritable(path string) bool {
|
|
defer env.Trace(time.Now(), "DirIsWritable")
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
env.Log(Error, "DirIsWritable", err.Error())
|
|
return false
|
|
}
|
|
|
|
if !info.IsDir() {
|
|
env.Log(Error, "DirIsWritable", "Path isn't a directory")
|
|
return false
|
|
}
|
|
|
|
// Check if the user bit is enabled in file permission
|
|
if info.Mode().Perm()&(1<<(uint(7))) == 0 {
|
|
env.Log(Error, "DirIsWritable", "Write permission bit is not set on this file for user")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|