/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/libkb/util_windows.go
206 строк
6 KB
chrisnojima-zoom
Version 670 - clean2 (#29122)
08 июн 2026, 19:31
Не верифицирован
08 июн 2026, 19:31
1943197
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. //go:build windows package libkb import ( "fmt" "os" "os/exec" "strings" "syscall" "time" "unicode/utf16" "unsafe" "github.com/keybase/client/go/utils" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) type GUID struct { Data1 uint32 Data2 uint16 Data3 uint16 Data4 [8]byte } // 3EB685DB-65F9-4CF6-A03A-E3EF65729F3D var ( FOLDERIDRoamingAppData = GUID{0x3EB685DB, 0x65F9, 0x4CF6, [8]byte{0xA0, 0x3A, 0xE3, 0xEF, 0x65, 0x72, 0x9F, 0x3D}} // F1B32785-6FBA-4FCF-9D55-7B8E7F157091 FOLDERIDLocalAppData = GUID{0xF1B32785, 0x6FBA, 0x4FCF, [8]byte{0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91}} FOLDERIDSystem = GUID{0x1AC14E77, 0x02E7, 0x4E5D, [8]byte{0xB7, 0x44, 0x2E, 0xB1, 0xAE, 0x51, 0x98, 0xB7}} ) var ( modShell32 = windows.NewLazySystemDLL("Shell32.dll") modOle32 = windows.NewLazySystemDLL("Ole32.dll") procSHGetKnownFolderPath = modShell32.NewProc("SHGetKnownFolderPath") procCoTaskMemFree = modOle32.NewProc("CoTaskMemFree") shChangeNotifyProc = modShell32.NewProc("SHChangeNotify") ) // LookPath searches for an executable binary named file // in the directories named by the PATH environment variable. // If file contains a slash, it is tried directly and the PATH is not consulted. func canExec(s string) error { if strings.IndexAny(s, `:\/`) == -1 { s = s + "/" } _, err := exec.LookPath(s) return err } func PosixLineEndings(arg string) string { return strings.Replace(arg, "\r", "", -1) } func coTaskMemFree(pv unsafe.Pointer) { syscall.Syscall(procCoTaskMemFree.Addr(), 1, uintptr(pv), 0, 0) return } func GetDataDir(id GUID, name, envname string) (string, error) { var pszPath unsafe.Pointer // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762188(v=vs.85).aspx // When this method returns, pszPath contains the address of a pointer to a null-terminated // Unicode string that specifies the path of the known folder. The calling process // is responsible for freeing this resource once it is no longer needed by calling // coTaskMemFree. // // It's safe for pszPath to point to memory not managed by Go: // see // https://groups.google.com/d/msg/golang-nuts/ls7Eg7Ye9pU/ye1GLs8dBwAJ // for details. r0, _, _ := procSHGetKnownFolderPath.Call(uintptr(unsafe.Pointer(&id)), uintptr(0), uintptr(0), uintptr(unsafe.Pointer(&pszPath))) if uintptr(pszPath) != 0 { defer coTaskMemFree(pszPath) } // Sometimes r0 == 0 and there still isn't a valid string returned if r0 != 0 || uintptr(pszPath) == 0 { return "", fmt.Errorf("can't get %s; HRESULT=%d, pszPath=%x", name, r0, pszPath) } var rawUnicode []uint16 for i := uintptr(0); ; i++ { u16 := *(*uint16)(unsafe.Pointer(uintptr(pszPath) + 2*i)) if u16 == 0 { break } if i == 1<<16 { return "", fmt.Errorf("%s path has more than 65535 characters", name) } rawUnicode = append(rawUnicode, u16) } folder := string(utf16.Decode(rawUnicode)) if len(folder) == 0 { // Try the environment as a backup folder = os.Getenv(envname) if len(folder) == 0 { return "", fmt.Errorf("can't get %s directory", envname) } } return folder, nil } func AppDataDir() (string, error) { return GetDataDir(FOLDERIDRoamingAppData, "FOLDERIDRoamingAppData", "APPDATA") } func LocalDataDir() (string, error) { return GetDataDir(FOLDERIDLocalAppData, "FOLDERIDLocalAppData", "LOCALAPPDATA") } func SystemDir() (string, error) { return GetDataDir(FOLDERIDSystem, "FOLDERIDSystem", "") } // SafeWriteToFile retries safeWriteToFileOnce a few times on Windows, // in case AV programs interfere with 2 writes in quick succession. func SafeWriteToFile(g SafeWriteLogger, t SafeWriter, mode os.FileMode) error { var err error for i := 0; i < 5; i++ { if err != nil { g.Debug("Retrying failed safeWriteToFileOnce - %s", err) time.Sleep(10 * time.Millisecond) } err = safeWriteToFileOnce(g, t, mode) if err == nil { break } } return err } // renameFile performs some retries on Windows, // similar to SafeWriteToFile func renameFile(g *GlobalContext, src string, dest string) error { var err error for i := 0; i < 5; i++ { if err != nil { g.Log.Debug("Retrying failed os.Rename - %s", err) time.Sleep(10 * time.Millisecond) } err = os.Rename(src, dest) if err == nil { break } } return err } // Notify the shell that the thing located at path has changed func notifyShell(path string) { pathEncoded := utf16.Encode([]rune(path)) if len(pathEncoded) > 0 { shChangeNotifyProc.Call( uintptr(0x00002000), // SHCNE_UPDATEITEM uintptr(0x0005), // SHCNF_PATHW uintptr(unsafe.Pointer(&pathEncoded[0])), 0) } } // Manipulate registry entries to reflect the mount point icon in the shell func ChangeMountIcon(oldMount string, newMount string) error { if oldMount != "" { // DeleteKey doesn't work if there are subkeys registry.DeleteKey(registry.CURRENT_USER, `SOFTWARE\Classes\Applications\Explorer.exe\Drives\`+oldMount[:1]+`\DefaultIcon`) registry.DeleteKey(registry.CURRENT_USER, `SOFTWARE\Classes\Applications\Explorer.exe\Drives\`+oldMount[:1]+`\DefaultLabel`) registry.DeleteKey(registry.CURRENT_USER, `SOFTWARE\Classes\Applications\Explorer.exe\Drives\`+oldMount[:1]) notifyShell(oldMount) } if newMount == "" { return nil } k, _, err := registry.CreateKey(registry.CURRENT_USER, `SOFTWARE\Classes\Applications\Explorer.exe\Drives\`+newMount[:1]+`\DefaultIcon`, registry.SET_VALUE|registry.CREATE_SUB_KEY|registry.WRITE) defer k.Close() if err != nil { return err } keybaseExe, err := utils.BinPath() if err != nil { return err } // Use the second icon bound into keybase.exe - hence the 1 err = k.SetStringValue("", keybaseExe+",1") if err != nil { return err } // Also give a nice label k2, _, err := registry.CreateKey(registry.CURRENT_USER, `SOFTWARE\Classes\Applications\Explorer.exe\Drives\`+newMount[:1]+`\DefaultLabel`, registry.SET_VALUE|registry.CREATE_SUB_KEY|registry.WRITE) defer k2.Close() err = k2.SetStringValue("", "Keybase") notifyShell(newMount) return err }