diff --git a/pkg/util/customcheckpointmanager/customcheckpointmanager.go b/pkg/util/customcheckpointmanager/customcheckpointmanager.go index 93d57bee2f..c2128f6506 100644 --- a/pkg/util/customcheckpointmanager/customcheckpointmanager.go +++ b/pkg/util/customcheckpointmanager/customcheckpointmanager.go @@ -296,15 +296,26 @@ func (cm *customCheckpointManager) CreateCheckpoint(checkpointName string, check func (cm *customCheckpointManager) isCheckpointUpToDate() (bool, error) { currentCheckpointFilePath := filepath.Join(cm.currStateDir, cm.checkpointName) - // Current checkpoint is not up to date as it does not exist - if !general.IsPathExists(currentCheckpointFilePath) { + // Current checkpoint is not up to date if it does not exist or if is not a regular file. + currFileExists, err := general.IsRegularFileExists(currentCheckpointFilePath, true) + if err != nil { + return false, fmt.Errorf("[%v] failed to check if current file exists: %w", cm.pluginName, err) + } + + if !currFileExists { return false, nil } previousCheckpointFilePath := filepath.Join(cm.prevStateDir, cm.checkpointName) - // When there is no previous checkpoint, we consider the current checkpoint is up to date - if !general.IsPathExists(previousCheckpointFilePath) { + // When there is no previous checkpoint, we consider the current checkpoint is up to date. + // Previous checkpoint does not exist if it is not a regular file. + prevFileExists, err := general.IsRegularFileExists(previousCheckpointFilePath, true) + if err != nil { + return false, fmt.Errorf("[%v] failed to check if previous file exists: %w", cm.pluginName, err) + } + + if !prevFileExists { return true, nil } diff --git a/pkg/util/general/file.go b/pkg/util/general/file.go index 350615849d..8b788b80fe 100644 --- a/pkg/util/general/file.go +++ b/pkg/util/general/file.go @@ -152,6 +152,45 @@ func IsPathExists(path string) bool { return true } +// IsRegularFileExists is to check whether a regular file specified by the path exists. +// If there is an entry that is not a regular file, make the entry hidden if makeHidden equals true +func IsRegularFileExists(path string, makeHidden bool) (bool, error) { + pathExists := IsPathExists(path) + if pathExists { + // Path exists, check if the file is a regular file + fileInfo, err := os.Stat(path) + if err != nil { + return false, fmt.Errorf("failed to get file info: %w", err) + } + + isRegular := fileInfo.Mode().IsRegular() + if !isRegular && makeHidden { + err = makeEntryHidden(path) + if err != nil { + return false, fmt.Errorf("failed to remove directory %s: %w", path, err) + } + } + + return isRegular, nil + } + + return false, nil +} + +// makeEntryHidden makes a file entry hidden by adding a prefix dot +func makeEntryHidden(path string) error { + dir := filepath.Dir(path) + base := filepath.Base(path) + + // premature return if the entry is already hidden + if len(base) > 0 && base[0] == '.' { + return nil + } + + hiddenPath := filepath.Join(dir, "."+base) + return os.Rename(path, hiddenPath) +} + // ReadFileIntoLines read contents from the given file, and parse them into string slice; // each string indicates a line in the file func ReadFileIntoLines(filepath string) ([]string, error) { diff --git a/pkg/util/general/file_test.go b/pkg/util/general/file_test.go index 7dfae5c699..6a6716fbb1 100644 --- a/pkg/util/general/file_test.go +++ b/pkg/util/general/file_test.go @@ -747,3 +747,149 @@ func TestIsFileUpToDate(t *testing.T) { }) } } + +func TestIsRegularFileExists(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + + type testCase struct { + name string + setup func(baseDir string) string + makeHidden bool + wantExists bool + expectOriginalExists bool + expectHiddenExists bool + } + + makeHiddenPath := func(p string) string { + dir := filepath.Dir(p) + base := filepath.Base(p) + return filepath.Join(dir, "."+base) + } + + tests := []testCase{ + { + name: "non-existent path without hiding", + setup: func(baseDir string) string { return filepath.Join(baseDir, "not_exists.txt") }, + makeHidden: false, + wantExists: false, + expectOriginalExists: false, + expectHiddenExists: false, + }, + { + name: "non-existent path with hiding", + setup: func(baseDir string) string { return filepath.Join(baseDir, "not_exists_2.txt") }, + makeHidden: true, + wantExists: false, + expectOriginalExists: false, + expectHiddenExists: false, + }, + { + name: "regular file exists, no hiding", + setup: func(baseDir string) string { + p := filepath.Join(baseDir, "regular.txt") + assert.NoError(t, os.WriteFile(p, []byte("hello"), 0o644)) + return p + }, + makeHidden: false, + wantExists: true, + expectOriginalExists: true, + expectHiddenExists: false, + }, + { + name: "regular file exists, hiding requested", + setup: func(baseDir string) string { + p := filepath.Join(baseDir, "regular2.txt") + assert.NoError(t, os.WriteFile(p, []byte("hello2"), 0o644)) + return p + }, + makeHidden: true, + wantExists: true, + expectOriginalExists: true, + expectHiddenExists: false, + }, + { + name: "directory exists, no hiding", + setup: func(baseDir string) string { + p := filepath.Join(baseDir, "adir") + assert.NoError(t, os.MkdirAll(p, 0o755)) + return p + }, + makeHidden: false, + wantExists: false, + expectOriginalExists: true, + expectHiddenExists: false, + }, + { + name: "directory exists, hiding requested", + setup: func(baseDir string) string { + p := filepath.Join(baseDir, "bdir") + assert.NoError(t, os.MkdirAll(p, 0o755)) + return p + }, + makeHidden: true, + wantExists: false, + expectOriginalExists: false, + expectHiddenExists: true, + }, + { + name: "symlink to regular file, hiding requested", + setup: func(baseDir string) string { + target := filepath.Join(baseDir, "target_regular.txt") + assert.NoError(t, os.WriteFile(target, []byte("data"), 0o644)) + link := filepath.Join(baseDir, "link_to_regular") + assert.NoError(t, os.Symlink(target, link)) + return link + }, + makeHidden: true, + wantExists: true, + expectOriginalExists: true, + expectHiddenExists: false, + }, + { + name: "symlink to directory, hiding requested", + setup: func(baseDir string) string { + target := filepath.Join(baseDir, "target_dir") + assert.NoError(t, os.MkdirAll(target, 0o755)) + link := filepath.Join(baseDir, "link_to_dir") + assert.NoError(t, os.Symlink(target, link)) + return link + }, + makeHidden: true, + wantExists: false, + expectOriginalExists: false, + expectHiddenExists: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + p := tt.setup(tmpDir) + + exists, err := IsRegularFileExists(p, tt.makeHidden) + assert.NoError(t, err) + assert.Equal(t, tt.wantExists, exists) + + // verify rename behavior + hidden := makeHiddenPath(p) + _, origErr := os.Stat(p) + _, hiddenErr := os.Stat(hidden) + + if tt.expectOriginalExists { + assert.NoError(t, origErr) + } else { + assert.True(t, os.IsNotExist(origErr)) + } + + if tt.expectHiddenExists { + assert.NoError(t, hiddenErr) + } else { + assert.True(t, os.IsNotExist(hiddenErr)) + } + }) + } +}