internal/command/command_windows.go (39 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package command import ( "context" "fmt" "net" "github.com/GoogleCloudPlatform/galog" "github.com/GoogleCloudPlatform/google-guest-agent/internal/cfg" winio "github.com/Microsoft/go-winio" ) const ( // securityDescriptor is a Windows security descriptor in SDDL format with // local system user as owner & group and default ACLs. // Note: Guest Agent process runs as a local system user. // Refer this for well known SIDs: // https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers#well-known-sids securityDescriptor = "O:S-1-5-18G:S-1-5-18" ) // PipeName generates and returns the full pipe name as [cfg.Unstable.CommandPipePath] // prefix and `_id` being the suffix. Unlike Linux, Windows does not allow "\" // separator in pipe names so we use `_` separator. // https://learn.microsoft.com/en-us/windows/win32/ipc/pipe-names func PipeName(listener KnownListeners) string { base := cfg.Retrieve().Unstable.CommandPipePath return fmt.Sprintf("%s_%s", base, listener) } func listen(ctx context.Context, path string, filemode int, group string) (net.Listener, error) { // Winio library does not provide any method to listen on context. Failing to // specify a pipeconfig (or using the zero value) results in flaky // ACCESS_DENIED errors when re-opening the same pipe (~1/10). // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#remarks // Even with a pipeconfig, this flakes ~1/200 runs, hence the retry until the // context is expired or listen is successful. var l net.Listener var lastError error for { if ctx.Err() != nil { return nil, fmt.Errorf("context expired: %v before successful listen (last error: %v)", ctx.Err(), lastError) } config := &winio.PipeConfig{ MessageMode: false, InputBufferSize: 1024, OutputBufferSize: 1024, SecurityDescriptor: securityDescriptor, } l, lastError = winio.ListenPipe(path, config) if lastError == nil { return l, lastError } galog.V(2).Errorf("Failed to listen on pipe %s: %v. Retrying...", path, lastError) } } func dialPipe(ctx context.Context, pipe string) (net.Conn, error) { return winio.DialPipeContext(ctx, pipe) }