github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/filestorage/filestorage.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package filestorage
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils/v3"
    16  
    17  	"github.com/juju/juju/environs/storage"
    18  )
    19  
    20  // fileStorageReader implements StorageReader backed
    21  // by the local filesystem.
    22  type fileStorageReader struct {
    23  	path string
    24  }
    25  
    26  // NewFileStorageReader returns a new storage reader for
    27  // a directory inside the local file system.
    28  func NewFileStorageReader(path string) (reader storage.StorageReader, err error) {
    29  	var p string
    30  	if p, err = utils.NormalizePath(path); err != nil {
    31  		return nil, err
    32  	}
    33  	if p, err = filepath.Abs(p); err != nil {
    34  		return nil, err
    35  	}
    36  	fi, err := os.Stat(p)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	if !fi.Mode().IsDir() {
    41  		return nil, fmt.Errorf("specified source path is not a directory: %s", path)
    42  	}
    43  	return &fileStorageReader{p}, nil
    44  }
    45  
    46  func (f *fileStorageReader) fullPath(name string) string {
    47  	return filepath.Join(f.path, name)
    48  }
    49  
    50  // Get implements storage.StorageReader.Get.
    51  func (f *fileStorageReader) Get(name string) (io.ReadCloser, error) {
    52  	if isInternalPath(name) {
    53  		return nil, &os.PathError{
    54  			Op:   "Get",
    55  			Path: name,
    56  			Err:  os.ErrNotExist,
    57  		}
    58  	}
    59  	filename := f.fullPath(name)
    60  	fi, err := os.Stat(filename)
    61  	if err != nil {
    62  		if os.IsNotExist(err) {
    63  			err = errors.NewNotFound(err, "")
    64  		}
    65  		return nil, err
    66  	} else if fi.IsDir() {
    67  		return nil, errors.NotFoundf("no such file with name %q", name)
    68  	}
    69  	file, err := os.Open(filename)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return file, nil
    74  }
    75  
    76  // isInternalPath returns true if a path should be hidden from user visibility
    77  // filestorage uses ".tmp/" as a staging directory for uploads, so we don't
    78  // want it to be visible
    79  func isInternalPath(path string) bool {
    80  	// This blocks both ".tmp", ".tmp/foo" but also ".tmpdir", better to be
    81  	// overly restrictive to start with
    82  	return strings.HasPrefix(path, ".tmp")
    83  }
    84  
    85  // List implements storage.StorageReader.List.
    86  func (f *fileStorageReader) List(prefix string) ([]string, error) {
    87  	var names []string
    88  	if isInternalPath(prefix) {
    89  		return names, nil
    90  	}
    91  	prefix = filepath.Join(f.path, prefix)
    92  	dir := filepath.Dir(prefix)
    93  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    94  		if err != nil {
    95  			return err
    96  		}
    97  		if !info.IsDir() && strings.HasPrefix(path, prefix) {
    98  			names = append(names, path[len(f.path)+1:])
    99  		}
   100  		return nil
   101  	})
   102  	if err != nil && !os.IsNotExist(err) {
   103  		return nil, err
   104  	}
   105  	sort.Strings(names)
   106  	return names, nil
   107  }
   108  
   109  // URL implements storage.StorageReader.URL.
   110  func (f *fileStorageReader) URL(name string) (string, error) {
   111  	return utils.MakeFileURL(filepath.Join(f.path, name)), nil
   112  }
   113  
   114  // DefaultConsistencyStrategy implements storage.StorageReader.ConsistencyStrategy.
   115  func (f *fileStorageReader) DefaultConsistencyStrategy() utils.AttemptStrategy {
   116  	// TODO(katco): 2016-08-09: lp:1611427
   117  	return utils.AttemptStrategy{}
   118  }
   119  
   120  // ShouldRetry is specified in the StorageReader interface.
   121  func (f *fileStorageReader) ShouldRetry(err error) bool {
   122  	return false
   123  }
   124  
   125  type fileStorageWriter struct {
   126  	fileStorageReader
   127  }
   128  
   129  // NewFileStorageWriter returns a new read/write storag for
   130  // a directory inside the local file system.
   131  func NewFileStorageWriter(path string) (storage.Storage, error) {
   132  	reader, err := NewFileStorageReader(path)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	return &fileStorageWriter{*reader.(*fileStorageReader)}, nil
   137  }
   138  
   139  func (f *fileStorageWriter) Put(name string, r io.Reader, length int64) error {
   140  	if isInternalPath(name) {
   141  		return &os.PathError{
   142  			Op:   "Put",
   143  			Path: name,
   144  			Err:  os.ErrPermission,
   145  		}
   146  	}
   147  	fullpath := f.fullPath(name)
   148  	dir := filepath.Dir(fullpath)
   149  	if err := os.MkdirAll(dir, 0755); err != nil {
   150  		return err
   151  	}
   152  	tmpdir := filepath.Join(f.path, ".tmp")
   153  	if err := os.MkdirAll(tmpdir, 0755); err != nil {
   154  		return err
   155  	}
   156  	defer os.Remove(tmpdir)
   157  	// Write to a temporary file first, and then move (atomically).
   158  	file, err := os.CreateTemp(tmpdir, "juju-filestorage-")
   159  	if err != nil {
   160  		return err
   161  	}
   162  	defer func() { _ = file.Close() }()
   163  
   164  	_, err = io.CopyN(file, r, length)
   165  	if err != nil {
   166  		os.Remove(file.Name())
   167  		return err
   168  	}
   169  	return utils.ReplaceFile(file.Name(), fullpath)
   170  }
   171  
   172  func (f *fileStorageWriter) Remove(name string) error {
   173  	fullpath := f.fullPath(name)
   174  	err := os.Remove(fullpath)
   175  	if os.IsNotExist(err) {
   176  		err = nil
   177  	}
   178  	return err
   179  }
   180  
   181  func (f *fileStorageWriter) RemoveAll() error {
   182  	return storage.RemoveAll(f)
   183  }