// Copyright 2020-2021 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 ( "io" "io/ioutil" "os" "path/filepath" "github.com/bufbuild/buf/internal/pkg/interrupt" "github.com/bufbuild/buf/internal/pkg/uuidutil" "go.uber.org/multierr" ) // File is a temporary file // // It must be closed when done. type File interface { io.Closer AbsPath() string } // NewFileWithData returns a new temporary file with the given data. // // It must be closed when done. // This file will be deleted on interrupt signals. // // Usage of this function requires eng approval - ask before using. func NewFileWithData(data []byte) (File, error) { id, err := uuidutil.New() if err != nil { return nil, err } file, err := ioutil.TempFile("", 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 } signalC, closer := interrupt.NewSignalChannel() go func() { <-signalC _ = os.Remove(absPath) }() _, err = file.Write(data) err = multierr.Append(err, file.Close()) if err != nil { err = multierr.Append(err, os.Remove(absPath)) closer() return nil, err } return newFile(absPath, closer), nil } // Dir is a temporary directory. // // It must be closed when done. type Dir interface { io.Closer AbsPath() string } // NewDir returns a new temporary directory. // // It must be closed when done. // This file will be deleted on interrupt signals. // // Usage of this function requires eng approval - ask before using. func NewDir(options ...DirOption) (Dir, error) { dirOptions := newDirOptions() for _, option := range options { option(dirOptions) } id, err := uuidutil.New() if err != nil { return nil, err } path, err := ioutil.TempDir(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 } signalC, closer := interrupt.NewSignalChannel() go func() { <-signalC _ = os.RemoveAll(absPath) }() return newDir(absPath, closer), 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 { absPath string closer func() } func newFile(absPath string, closer func()) *file { return &file{ absPath: absPath, closer: closer, } } func (f *file) AbsPath() string { return f.absPath } func (f *file) Close() error { err := os.Remove(f.absPath) f.closer() return err } type dir struct { absPath string closer func() } func newDir(absPath string, closer func()) *dir { return &dir{ absPath: absPath, closer: closer, } } func (d *dir) AbsPath() string { return d.absPath } func (d *dir) Close() error { err := os.RemoveAll(d.absPath) d.closer() return err } type dirOptions struct { basePath string } func newDirOptions() *dirOptions { return &dirOptions{} }