Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/admin/autorecovery/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (s *state) SaveToIntermediate() error {
if err != nil {
return err
}
path, err := data.GenerateUniqueTempPath(filepath.Join(s.storagePath, stateFilename))
path, err := util.GenerateUniqueTempPath(filepath.Join(s.storagePath, stateFilename))
if err != nil {
return err
}
Expand Down
20 changes: 1 addition & 19 deletions pkg/admin/data/atomic_dir_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"errors"
"fmt"
"math/rand/v2"
"os"
"os/exec"
"strings"
Expand Down Expand Up @@ -42,7 +41,7 @@ func (c *AtomicDirCopy) CopyToIntermediate() error {
return nil
}
var err error
c.intermediatePath, err = GenerateUniqueTempPath(c.Destination)
c.intermediatePath, err = util.GenerateUniqueTempPath(c.Destination)
if err != nil {
return err
}
Expand Down Expand Up @@ -131,20 +130,3 @@ func removeDirIfExists(path string) error {

return nil
}

// GenerateUniqueTempPath returns a filepath from given path with extra suffix
// which doesn't exist.
func GenerateUniqueTempPath(path string) (string, error) {
// 1000 tries
for i := 0; i < 1000; i++ {
//nolint:gosec
rnd := rand.IntN(100000)
newPath := fmt.Sprintf("%s.tmp.%d", path, rnd)
if exists, err := util.PathExists(newPath); err != nil {
return "", err
} else if !exists {
return newPath, nil
}
}
return "", fmt.Errorf("attempted to generate unique temporary path (%q) for 1000 tries - giving up", path)
}
30 changes: 24 additions & 6 deletions pkg/config/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"

"github.com/openshift/microshift/pkg/util"
"k8s.io/klog/v2"
)

Expand Down Expand Up @@ -45,12 +46,8 @@ func (c *Config) establishNodeName(dataDir string) (string, error) {
filePath := filepath.Join(dataDir, ".nodename")
contents, err := os.ReadFile(filePath)
if os.IsNotExist(err) {
// ensure that dataDir exists
if err := os.MkdirAll(dataDir, 0700); err != nil {
return "", fmt.Errorf("failed to create data dir: %w", err)
}
if err := os.WriteFile(filePath, []byte(name), 0400); err != nil {
return "", fmt.Errorf("failed to write nodename file %q: %v", filePath, err)
if err := c.createNodeNameFile(name, filePath, dataDir); err != nil {
return "", err
}
return name, nil
} else if err != nil {
Expand All @@ -59,6 +56,27 @@ func (c *Config) establishNodeName(dataDir string) (string, error) {
return string(contents), nil
}

func (c *Config) createNodeNameFile(nodeName, filePath, dataDir string) error {
// ensure that dataDir exists
if err := os.MkdirAll(dataDir, 0700); err != nil {
return fmt.Errorf("failed to create data dir: %w", err)
}
tmpPath, err := util.GenerateUniqueTempPath(filePath)
if err != nil {
return fmt.Errorf("failed to generate temp path for %s: %w", filePath, err)
}
if err := os.WriteFile(tmpPath, []byte(nodeName), 0400); err != nil {
return fmt.Errorf("failed to write nodename file %q: %v", filePath, err)
}
if err := os.Rename(tmpPath, filePath); err != nil {
if err := os.RemoveAll(tmpPath); err != nil {
klog.Errorf("Failed to remove intermediate path %q: %v", tmpPath, err)
}
return fmt.Errorf("failed to rename %s to %s: %w", tmpPath, filePath, err)
}
return nil
}

// Validate the NodeName to be used for this MicroShift instances
func (c *Config) validateNodeName(isDefaultNodeName bool, dataDir string) error {
currentNodeName := c.CanonicalNodeName()
Expand Down
18 changes: 18 additions & 0 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io/fs"
"math/rand/v2"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -142,3 +143,20 @@ func (l LogFilePath) Remove() error {
}
return err
}

// GenerateUniqueTempPath returns a filepath from given path with extra suffix
// which doesn't exist.
func GenerateUniqueTempPath(path string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use https://pkg.go.dev/os#CreateTemp instead of this code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could in .nodename creation, but not in the atomic_dir_copy.go. This function gives us a path that we can use either as a file or a dir. CreateTemp will return a File, which can only be a file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current function seems to be flawed because it only checks for a file existence, but does not create any. So, if the code is running in parallel, there is a chance that we'll be getting the same path back.

Copy link
Member Author

@pmtk pmtk Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't execute it in parallel (and we don't call it with the same path - it's always different). Also, if we use this function to get a temporary unique path for a directory, how does creating file helps us?
BTW. This isn't new function - it was moved from state.go and it's proven to be working for some time already

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can leave this code as-is if it's called under certain conditions, but as a generic implementation, this function is flawed.
It does not create objects on the file system, but uses object existence as a test for a validity of the generated name.
This is a race condition when the function is called from different threads for the same path - random number generation might return the same output and the function would succeed because file object is not on the file system yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created https://issues.redhat.com/browse/USHIFT-5373 to follow-up on this separately.
Let's not hold this review on this topic.

// 1000 tries
for i := 0; i < 1000; i++ {
//nolint:gosec
rnd := rand.IntN(100000)
newPath := fmt.Sprintf("%s.tmp.%d", path, rnd)
if exists, err := PathExists(newPath); err != nil {
return "", err
} else if !exists {
return newPath, nil
}
}
return "", fmt.Errorf("attempted to generate unique temporary path (%q) for 1000 tries - giving up", path)
}