//go:build windows // +build windows // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file 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 statemanager import ( "io/ioutil" "os" "path/filepath" "github.com/aws/amazon-ecs-agent/agent/statemanager/dependencies" "github.com/aws/amazon-ecs-agent/agent/utils/oswrapper" "github.com/cihub/seelog" "golang.org/x/sys/windows/registry" ) const ( ecsDataFileRootKey = registry.LOCAL_MACHINE ecsDataFileKeyPath = `SOFTWARE\Amazon\ECS Agent\State File` ecsDataFileValueName = "path" // overrideValueNameEnv is the name of an environment variable that can be used to override the // name of the registry value queried for the state file path. This is useful in integration- // and functional-tests, but should not be set for any non-test use-case. overrideValueNameEnv = "ZZZ_I_KNOW_SETTING_TEST_VALUES_IN_PRODUCTION_IS_NOT_SUPPORTED" ) /* On Windows, the basic approach for attempting to ensure that the state file is written out correctly relies on the Windows Registry and documented behavior of the Win32 API. On each save, the agent creates a new file where it writes out the json object. Once the file is written, it gets flushed to disk using the Win32 FlushFileBuffers API. After the file is flushed to disk, a registry key is updated to indicate the new file name. Finally, the old file retrieved from the registry key is deleted. On each load, the agent reads a well-known registry key to find the name of the file to load. */ type windowsDependencies struct { registry dependencies.WindowsRegistry } func newPlatformDependencies() platformDependencies { return windowsDependencies{ registry: dependencies.StdRegistry{}, } } var readAll = ioutil.ReadAll var isNotExist = os.IsNotExist var open = func(name string) (oswrapper.File, error) { return os.Open(name) } func (manager *basicStateManager) readFile() ([]byte, error) { manager.savingLock.Lock() defer manager.savingLock.Unlock() path, err := manager.getPath() if err != nil { if err == registry.ErrNotExist { // Happens on the first run; not a real error return nil, nil } return nil, err } file, err := open(filepath.Clean(path)) if err != nil { if isNotExist(err) { // Happens every first run; not a real error return nil, nil } return nil, err } defer file.Close() return readAll(file) } func (manager *basicStateManager) getPath() (string, error) { deps := manager.platformDependencies.(windowsDependencies) key, err := deps.registry.OpenKey(ecsDataFileRootKey, ecsDataFileKeyPath, registry.READ) if err != nil { return "", err } defer key.Close() val, _, err := key.GetStringValue(valueName()) if err != nil { return "", err } return val, nil } func valueName() string { valueName := ecsDataFileValueName if os.Getenv(overrideValueNameEnv) != "" { valueName = os.Getenv(overrideValueNameEnv) } return valueName } var remove = os.Remove var tempFile = func(dir, pattern string) (oswrapper.File, error) { return ioutil.TempFile(dir, pattern) } func (manager *basicStateManager) writeFile(data []byte) error { oldFile, err := manager.getPath() if err != nil { if err != registry.ErrNotExist { return err } oldFile = "" } dataFile, err := tempFile(manager.statePath, ecsDataFile) if err != nil { seelog.Errorf("Error saving state; could not create file to save state: %v", err) return err } defer dataFile.Close() _, err = dataFile.Write(data) if err != nil { seelog.Errorf("Error saving state; could not write to file to save state: %s %v ", dataFile.Name(), err) return err } // this calls FlushFileBuffers, see https://golang.org/src/syscall/syscall_windows.go#L523 // TODO Investigate performance differences between FlushFileBuffers and unbuffered IO. err = dataFile.Sync() if err != nil { seelog.Errorf("Error saving state; could not sync file to save state: %s %v", dataFile.Name(), err) return err } err = dataFile.Close() if err != nil { seelog.Errorf("Error saving state; could not close file to save state: %s %v", dataFile.Name(), err) return err } err = manager.savePath(dataFile.Name()) if err != nil { seelog.Errorf("Failed to save the data file path: %v", err) return err } if oldFile == "" { return nil } err = remove(oldFile) if err != nil { seelog.Errorf("Error removing old file %s; err %v", oldFile, err) return err } return nil } func (manager *basicStateManager) savePath(path string) error { deps := manager.platformDependencies.(windowsDependencies) key, existed, err := deps.registry.CreateKey(ecsDataFileRootKey, ecsDataFileKeyPath, registry.SET_VALUE|registry.CREATE_SUB_KEY) if err != nil { seelog.Errorf("Failed to create registry key %s %v", ecsDataFileKeyPath, err) return err } defer key.Close() if !existed { seelog.Infof(`Created new registry key: HKEY_LOCAL_MACHINE\%s`, ecsDataFileKeyPath) } return key.SetStringValue(valueName(), path) }