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
109 changes: 59 additions & 50 deletions go/cmd/dolt/system_checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,69 +36,78 @@ func reconfigIfTempFileMoveFails(dataDir filesys.Filesys) error {
return err
}

dotDoltCreated := false
tmpDirCreated := false

doltDir := filepath.Join(absP, dbfactory.DoltDir)
stat, err := os.Stat(doltDir)
if err != nil {
err := os.MkdirAll(doltDir, os.ModePerm)
// Configure MovableTempFileProvider so that it lazily checks if os.TempDir() can be moved from to the data directory
// or not. If it cannot be, this will configure .dolt/temptf as the movable temp file directory.
//
// The intent of this being lazy is that we do not want to mess with the local filesystem unless we are asked to,
// but the concrete way we check for moveability is to create a temp file and move it into a known subdirectory
// of the .dolt subdirectory. We shouldn't create any of those things unless we need to because we are actually
// doing filesystem writes.
origprovider := tempfiles.MovableTempFileProvider
tempfiles.MovableTempFileProvider = tempfiles.NewLazyTempFileProvider(func() (tempfiles.TempFileProvider, error) {
dotDoltCreated := false
tmpDirCreated := false

doltDir := filepath.Join(absP, dbfactory.DoltDir)
stat, err := os.Stat(doltDir)
if err != nil {
return fmt.Errorf("failed to create dolt dir '%s': %s", doltDir, err.Error())
}
err := os.MkdirAll(doltDir, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("failed to create dolt dir '%s': %s", doltDir, err.Error())
}

dotDoltCreated = true
}
dotDoltCreated = true
}

doltTmpDir := filepath.Join(doltDir, env.TmpDirName)
stat, err = os.Stat(doltTmpDir)
if err != nil {
err := os.MkdirAll(doltTmpDir, os.ModePerm)
doltTmpDir := filepath.Join(doltDir, env.TmpDirName)
stat, err = os.Stat(doltTmpDir)
if err != nil {
return fmt.Errorf("failed to create temp dir '%s': %s", doltTmpDir, err.Error())
err := os.MkdirAll(doltTmpDir, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("failed to create temp dir '%s': %s", doltTmpDir, err.Error())
}
tmpDirCreated = true

} else if !stat.IsDir() {
return nil, fmt.Errorf("attempting to use '%s' as a temp directory, but there exists a file with that name", doltTmpDir)
}
tmpDirCreated = true

} else if !stat.IsDir() {
return fmt.Errorf("attempting to use '%s' as a temp directory, but there exists a file with that name", doltTmpDir)
}

tmpF, err := os.CreateTemp("", "")
if err != nil {
return err
}

name := tmpF.Name()
err = tmpF.Close()
if err != nil {
return err
}
tmpF, err := os.CreateTemp("", "")
if err != nil {
return nil, err
}

movedName := filepath.Join(doltTmpDir, "testfile")
name := tmpF.Name()
err = tmpF.Close()
if err != nil {
return nil, err
}

if os.Getenv("DOLT_FORCE_LOCAL_TEMP_FILES") == "" {
err = file.Rename(name, movedName)
} else {
err = errors.New("treating rename as failed because DOLT_FORCE_LOCAL_TEMP_FILES is set")
}
if err == nil {
// If rename was successful, then the tmp dir is fine, so no need to change it. Clean up the things we created.
_ = file.Remove(movedName)
movedName := filepath.Join(doltTmpDir, "testfile")

if tmpDirCreated {
_ = file.Remove(doltTmpDir)
if os.Getenv("DOLT_FORCE_LOCAL_TEMP_FILES") == "" {
err = file.Rename(name, movedName)
} else {
err = errors.New("treating rename as failed because DOLT_FORCE_LOCAL_TEMP_FILES is set")
}
if err == nil {
// If rename was successful, then the tmp dir is fine, so no need to change it. Clean up the things we created.
_ = file.Remove(movedName)

if dotDoltCreated {
_ = file.Remove(doltDir)
}
if tmpDirCreated {
_ = file.Remove(doltTmpDir)
}

return nil
}
_ = file.Remove(name)
if dotDoltCreated {
_ = file.Remove(doltDir)
}

// Rename failed. So we force the tmp dir to be the data dir.
tempfiles.MovableTempFileProvider = tempfiles.NewTempFileProviderAt(doltTmpDir)
return origprovider, nil
}
_ = file.Remove(name)
// Rename failed. So we force the tmp dir to be the data dir.
return tempfiles.NewTempFileProviderAt(doltTmpDir), nil
})

return nil
}
50 changes: 49 additions & 1 deletion go/store/util/tempfiles/temp_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,55 @@ func (tfp *TempFileProviderAt) Clean() {
}
}

// MovableTemFile is an object that implements TempFileProvider that is used by the nbs to create temp files that
// LazyTempFileProvider will load the TempFileProvider from |loader|
// on first access and then return temp files based on that result
// going forward. This is configured for the dolt process's data
// directory to get our process-wide MovableTempFileProvider early in
// the Dolt process's life cycle, but the required capabilities are
// not checked for until first use.
type LazyTempFileProvider struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add a comment re: what this is for

once sync.Once
loader func() (TempFileProvider, error)
provider TempFileProvider
perr error
}

func NewLazyTempFileProvider(loader func() (TempFileProvider, error)) *LazyTempFileProvider {
return &LazyTempFileProvider{
loader: loader,
}
}

func (p *LazyTempFileProvider) loadit() {
p.once.Do(func() {
p.provider, p.perr = p.loader()
})
}

func (p *LazyTempFileProvider) Clean() {
// Don't load if we haven't already been loaded.
if p.provider != nil {
p.provider.Clean()
}
}

func (p *LazyTempFileProvider) GetTempDir() string {
p.loadit()
if p.perr != nil {
return os.TempDir()
}
return p.provider.GetTempDir()
}

func (p *LazyTempFileProvider) NewFile(dir, pattern string) (*os.File, error) {
p.loadit()
if p.perr != nil {
return nil, p.perr
}
return p.provider.NewFile(dir, pattern)
}

// MovableTempFile is an object that implements TempFileProvider that is used by the nbs to create temp files that
// ultimately will be renamed. It is important to use this instance rather than using os.TempDir, or os.CreateTemp
// directly as those may have errors executing a rename against if the volume the default temporary directory lives on
// is different than the volume of the destination of the rename.
Expand Down
Loading