diff --git a/cmd/config-manager/main.go b/cmd/config-manager/main.go index 1d30d45d6..22c6ce87a 100644 --- a/cmd/config-manager/main.go +++ b/cmd/config-manager/main.go @@ -17,6 +17,7 @@ package main import ( + "errors" "fmt" "os" "path/filepath" @@ -398,29 +399,18 @@ func updateSymlink(config string, f *Flags) (bool, error) { src = filepath.Join(f.ConfigFileSrcdir, config) } - exists, err := fileExists(f.ConfigFileDst) - if err != nil { - return false, fmt.Errorf("error checking if file '%s' exists: %v", f.ConfigFileDst, err) - } - if exists { - srcRealpath, err := filepath.EvalSymlinks(src) - if err != nil { - return false, fmt.Errorf("error evaluating realpath of '%v': %v", src, err) - } - - dstRealpath, err := filepath.EvalSymlinks(f.ConfigFileDst) - if err != nil { - return false, fmt.Errorf("error evaluating realpath of '%v': %v", f.ConfigFileDst, err) - } - - if srcRealpath == dstRealpath { + current, err := os.Readlink(f.ConfigFileDst) + if err == nil { + if current == src { return false, nil } + } else if !os.IsNotExist(err) && !errors.Is(err, syscall.EINVAL) { + return false, fmt.Errorf("error reading symlink '%s': %v", f.ConfigFileDst, err) + } - err = os.Remove(f.ConfigFileDst) - if err != nil { - return false, fmt.Errorf("error removing existing config: %v", err) - } + err = os.Remove(f.ConfigFileDst) + if err != nil && !os.IsNotExist(err) { + return false, fmt.Errorf("error removing existing config: %v", err) } err = os.Symlink(src, f.ConfigFileDst) @@ -463,17 +453,6 @@ func findPidToSignal(f *Flags) (int, error) { return -1, fmt.Errorf("no process found") } -func fileExists(filename string) (bool, error) { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false, nil - } - if err != nil { - return false, err - } - return !info.IsDir(), nil -} - func getConfigFileNameMap(f *Flags) (map[string]bool, error) { files, err := os.ReadDir(f.ConfigFileSrcdir) if err != nil { diff --git a/cmd/config-manager/main_test.go b/cmd/config-manager/main_test.go new file mode 100644 index 000000000..688da2e47 --- /dev/null +++ b/cmd/config-manager/main_test.go @@ -0,0 +1,67 @@ +/** +# Copyright (c) NVIDIA CORPORATION. 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. +# 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. +**/ + +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func newTestFlags(srcdir, dst string) *Flags { + return &Flags{ + ConfigFileSrcdir: srcdir, + ConfigFileDst: dst, + } +} + +func TestUpdateSymlinkDanglingDestination(t *testing.T) { + srcdir := t.TempDir() + dst := filepath.Join(t.TempDir(), "config.yaml") + f := newTestFlags(srcdir, dst) + + testCases := []struct { + description string + config string + wantChanged bool + }{ + { + description: "create dangling symlink", + config: "missing-config", + wantChanged: true, + }, + { + description: "dangling symlink already pointing at config is a no operation", + config: "missing-config", + wantChanged: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + changed, err := updateSymlink(tc.config, f) + require.NoError(t, err) + require.Equal(t, tc.wantChanged, changed) + }) + } + + link, err := os.Readlink(dst) + require.NoError(t, err) + require.Equal(t, filepath.Join(srcdir, "missing-config"), link) +}