// Package tempdir allocates disposable storage outside a source worktree. package tempdir import ( "fmt" "os" "path/filepath" "strings" ) // Outside uses system temporary storage and falls back to the repository's // parent when TMPDIR resolves inside the worktree. Cleanup belongs to the caller. func Outside(repositoryDir, pattern string) (string, error) { repository, err := filepath.Abs(repositoryDir) if err != nil { return "", err } repository, err = filepath.EvalSymlinks(repository) if err != nil { return "", err } directory, err := os.MkdirTemp("", pattern) if err != nil { return "", err } created := directory directory, err = filepath.Abs(created) if err != nil { os.RemoveAll(created) return "", err } directory, err = filepath.EvalSymlinks(directory) if err != nil { os.RemoveAll(directory) return "", err } outside, err := isOutside(repository, directory) if err != nil { os.RemoveAll(directory) return "", err } if outside { return directory, nil } if err := os.RemoveAll(directory); err != nil { return "", fmt.Errorf("remove temporary storage inside repository %q: %w", repository, err) } directory, err = os.MkdirTemp(filepath.Dir(repository), pattern) if err != nil { return "", fmt.Errorf("create temporary storage outside repository %q: %w", repository, err) } created = directory directory, err = filepath.Abs(created) if err != nil { os.RemoveAll(created) return "", err } directory, err = filepath.EvalSymlinks(directory) if err != nil { os.RemoveAll(directory) return "", err } outside, err = isOutside(repository, directory) if err != nil { os.RemoveAll(directory) return "", err } if !outside { os.RemoveAll(directory) return "", fmt.Errorf("temporary storage %q is inside repository %q", directory, repository) } return directory, nil } func isOutside(repository, directory string) (bool, error) { if !strings.EqualFold(filepath.VolumeName(repository), filepath.VolumeName(directory)) { return true, nil } relative, err := filepath.Rel(repository, directory) if err != nil { return false, err } return relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)), nil }