internal/plugin/manager/plugindisabler.go (80 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 // // https://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. package manager import ( "context" "fmt" "os" "github.com/GoogleCloudPlatform/galog" acmpb "github.com/GoogleCloudPlatform/google-guest-agent/internal/acp/proto/google_guest_agent/acp" "github.com/GoogleCloudPlatform/google-guest-agent/internal/ps" "github.com/GoogleCloudPlatform/google-guest-agent/internal/resource" ) // stopStep implements the plugin stop. type stopStep struct { // Cleanup is set to true to notify plugins to remove any state stored on // disk. Stop request can be sent as part of plugin restart which does not // require cleanup whereas plugin remove does require. cleanup bool } // Name returns the name of the step. func (ss *stopStep) Name() string { return "StopPluginStep" } // Status returns the plugin state for current step. func (ss *stopStep) Status() acmpb.CurrentPluginStates_DaemonPluginState_StatusValue { return acmpb.CurrentPluginStates_DaemonPluginState_STOPPING } // Status returns the plugin state for current step. func (ss *stopStep) ErrorStatus() acmpb.CurrentPluginStates_DaemonPluginState_StatusValue { // This step is not expected to fail as agent would kill the plugin if stop // fails. return acmpb.CurrentPluginStates_DaemonPluginState_STATE_VALUE_UNSPECIFIED } func (ss *stopStep) stopPlugin(ctx context.Context, p *Plugin) error { pluginPid := p.pid() proc, err := ps.FindPid(pluginPid) if err != nil { return fmt.Errorf("%q plugin process(%d) not found: %w", p.FullName(), pluginPid, err) } // If plugin is not running, we can skip the stop RPC. // Ensures PID is not reused by a different process and then attempts to // stop and kill the plugin process. if proc.Exe != p.EntryPath { galog.Infof("Plugin PID (%d) is being reused by a different process running from (%q) different from expected binary(%q), skipping stop RPC", pluginPid, proc.Exe, p.EntryPath) return nil } if _, err := p.Stop(ctx, ss.cleanup); err != nil { galog.Warnf("Stop %s plugin failed with error: %v", p.FullName(), err) } // Make sure plugin process exited by attempting to kill. if err := ps.KillProcess(pluginPid, ps.KillModeWait); err != nil { return fmt.Errorf("kill %s plugin process (%d) completed with error: %v", p.FullName(), pluginPid, err) } sendEvent(ctx, p, acmpb.PluginEventMessage_PLUGIN_STOPPED, "Successfully stopped the plugin.") return nil } func (ss *stopStep) Run(ctx context.Context, p *Plugin) error { if err := ss.stopPlugin(ctx, p); err != nil { // Its unlikely for kill to fail as process is running as root and is a best // effort. Just log the error for debugging in-case it happens. galog.Infof("Kill %s plugin process completed with: %v", p.FullName(), err) } if p.client != nil { if err := p.client.Close(); err != nil { galog.Warnf("Close %s plugin client failed with error: %v", p.FullName(), err) } } p.setState(acmpb.CurrentPluginStates_DaemonPluginState_STOPPED) // Reset the plugin client and process PID. p.client = nil p.setPid(0) // Cleanup is set to true only on plugin removal. if ss.cleanup { if err := cleanup(ctx, p); err != nil { // Not a critical step in plugin removal, just log a message. galog.Debugf("Unable to cleanup plugin state: %v", err) } } return nil } // Cleanup removes all known paths associated with this plugin. func cleanup(ctx context.Context, p *Plugin) error { galog.Infof("Cleaning up %q plugin state", p.FullName()) // Remove resource constraint first before attempting any file removal. // On windows [JobObjects] are used for setting resource limits that can // prevent manager from cleanup/removing files. if err := resource.RemoveConstraint(ctx, p.FullName()); err != nil { return fmt.Errorf("resource constraint removal failed: %w", err) } // Files paths of core plugins are managed by package manager do not remove. if p.PluginType != PluginTypeCore { if err := os.RemoveAll(p.InstallPath); err != nil { return fmt.Errorf("%s plugin install path (%s) removal failed with error: %w", p.FullName(), p.InstallPath, err) } } if p.Protocol == udsProtocol { if err := os.RemoveAll(p.Address); err != nil { return fmt.Errorf("%s plugin socket file (%s) removal failed with error: %w", p.FullName(), p.Address, err) } } stateFile := p.stateFile() if err := os.RemoveAll(stateFile); err != nil { return fmt.Errorf("%s plugin state (%s) removal failed with error: %w", p.FullName(), stateFile, err) } p.Address = "" return nil }