blob: 5d20b0497454b7e71c0c8b9bca061929c6f666d5 [file] [edit]
// Copyright 2020-2025 Buf Technologies, Inc.
//
// 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 tmp provides temporary files and directories.
//
// Usage of this package requires eng approval - ask before using.
package tmp
import (
"context"
"errors"
"io"
"os"
"path/filepath"
"github.com/bufbuild/buf/private/pkg/uuidutil"
)
// File is a temporary file or directory.
//
// It must be closed when done.
type File interface {
io.Closer
Path() string
}
// NewFile returns a new temporary file with the data copied from the Reader.
//
// It must be closed when done. This deletes this file.
// This file will be automatically closed on context cancellation.
//
// Usage of this function requires eng approval - ask before using.
func NewFile(ctx context.Context, reader io.Reader) (File, error) {
id, err := uuidutil.New()
if err != nil {
return nil, err
}
file, err := os.CreateTemp("", id.String())
if err != nil {
return nil, err
}
path := file.Name()
// just in case
absPath, err := filepath.Abs(filepath.Clean(path))
if err != nil {
return nil, err
}
closer := func() error { return os.Remove(absPath) }
go func() {
<-ctx.Done()
_ = closer()
}()
_, err = io.Copy(file, reader)
err = errors.Join(err, file.Close())
if err != nil {
err = errors.Join(err, closer())
return nil, err
}
return newFile(closerFunc(closer), absPath), nil
}
// NewDir returns a new temporary directory.
//
// It must be closed when done. This deletes this directory and all its contents.
// This directory will be automatically closed on context cancellation.
//
// Usage of this function requires eng approval - ask before using.
func NewDir(ctx context.Context, options ...DirOption) (File, error) {
dirOptions := newDirOptions()
for _, option := range options {
option(dirOptions)
}
id, err := uuidutil.New()
if err != nil {
return nil, err
}
path, err := os.MkdirTemp(dirOptions.basePath, id.String())
if err != nil {
return nil, err
}
// just in case
absPath, err := filepath.Abs(filepath.Clean(path))
if err != nil {
return nil, err
}
closer := func() error { return os.RemoveAll(absPath) }
go func() {
<-ctx.Done()
_ = closer()
}()
return newFile(closerFunc(closer), absPath), nil
}
// DirOption is an option for NewDir.
type DirOption func(*dirOptions)
// DirWithBasePath returns a new DirOption that sets the base path to create
// the temporary directory in.
//
// The default is to use os.TempDir().
func DirWithBasePath(basePath string) DirOption {
return func(dirOptions *dirOptions) {
dirOptions.basePath = basePath
}
}
type file struct {
io.Closer
path string
}
func newFile(closer io.Closer, path string) *file {
return &file{
Closer: closer,
path: path,
}
}
func (f *file) Path() string {
return f.path
}
type closerFunc func() error
func (c closerFunc) Close() error {
return c()
}
type dirOptions struct {
basePath string
}
func newDirOptions() *dirOptions {
return &dirOptions{}
}