code.gitea.io/gitea@v1.22.3/modules/storage/local.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package storage
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"code.gitea.io/gitea/modules/log"
    15  	"code.gitea.io/gitea/modules/setting"
    16  	"code.gitea.io/gitea/modules/util"
    17  )
    18  
    19  var _ ObjectStorage = &LocalStorage{}
    20  
    21  // LocalStorage represents a local files storage
    22  type LocalStorage struct {
    23  	ctx    context.Context
    24  	dir    string
    25  	tmpdir string
    26  }
    27  
    28  // NewLocalStorage returns a local files
    29  func NewLocalStorage(ctx context.Context, config *setting.Storage) (ObjectStorage, error) {
    30  	if !filepath.IsAbs(config.Path) {
    31  		return nil, fmt.Errorf("LocalStorageConfig.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path)
    32  	}
    33  	log.Info("Creating new Local Storage at %s", config.Path)
    34  	if err := os.MkdirAll(config.Path, os.ModePerm); err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	if config.TemporaryPath == "" {
    39  		config.TemporaryPath = filepath.Join(config.Path, "tmp")
    40  	}
    41  	if !filepath.IsAbs(config.TemporaryPath) {
    42  		return nil, fmt.Errorf("LocalStorageConfig.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath)
    43  	}
    44  
    45  	return &LocalStorage{
    46  		ctx:    ctx,
    47  		dir:    config.Path,
    48  		tmpdir: config.TemporaryPath,
    49  	}, nil
    50  }
    51  
    52  func (l *LocalStorage) buildLocalPath(p string) string {
    53  	return util.FilePathJoinAbs(l.dir, p)
    54  }
    55  
    56  // Open a file
    57  func (l *LocalStorage) Open(path string) (Object, error) {
    58  	return os.Open(l.buildLocalPath(path))
    59  }
    60  
    61  // Save a file
    62  func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) {
    63  	p := l.buildLocalPath(path)
    64  	if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
    65  		return 0, err
    66  	}
    67  
    68  	// Create a temporary file to save to
    69  	if err := os.MkdirAll(l.tmpdir, os.ModePerm); err != nil {
    70  		return 0, err
    71  	}
    72  	tmp, err := os.CreateTemp(l.tmpdir, "upload-*")
    73  	if err != nil {
    74  		return 0, err
    75  	}
    76  	tmpRemoved := false
    77  	defer func() {
    78  		if !tmpRemoved {
    79  			_ = util.Remove(tmp.Name())
    80  		}
    81  	}()
    82  
    83  	n, err := io.Copy(tmp, r)
    84  	if err != nil {
    85  		return 0, err
    86  	}
    87  
    88  	if err := tmp.Close(); err != nil {
    89  		return 0, err
    90  	}
    91  
    92  	if err := util.Rename(tmp.Name(), p); err != nil {
    93  		return 0, err
    94  	}
    95  	// Golang's tmp file (os.CreateTemp) always have 0o600 mode, so we need to change the file to follow the umask (as what Create/MkDir does)
    96  	// but we don't want to make these files executable - so ensure that we mask out the executable bits
    97  	if err := util.ApplyUmask(p, os.ModePerm&0o666); err != nil {
    98  		return 0, err
    99  	}
   100  
   101  	tmpRemoved = true
   102  
   103  	return n, nil
   104  }
   105  
   106  // Stat returns the info of the file
   107  func (l *LocalStorage) Stat(path string) (os.FileInfo, error) {
   108  	return os.Stat(l.buildLocalPath(path))
   109  }
   110  
   111  // Delete delete a file
   112  func (l *LocalStorage) Delete(path string) error {
   113  	return util.Remove(l.buildLocalPath(path))
   114  }
   115  
   116  // URL gets the redirect URL to a file
   117  func (l *LocalStorage) URL(path, name string) (*url.URL, error) {
   118  	return nil, ErrURLNotSupported
   119  }
   120  
   121  // IterateObjects iterates across the objects in the local storage
   122  func (l *LocalStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error {
   123  	dir := l.buildLocalPath(dirName)
   124  	return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
   125  		if err != nil {
   126  			return err
   127  		}
   128  		select {
   129  		case <-l.ctx.Done():
   130  			return l.ctx.Err()
   131  		default:
   132  		}
   133  		if path == l.dir {
   134  			return nil
   135  		}
   136  		if d.IsDir() {
   137  			return nil
   138  		}
   139  		relPath, err := filepath.Rel(l.dir, path)
   140  		if err != nil {
   141  			return err
   142  		}
   143  		obj, err := os.Open(path)
   144  		if err != nil {
   145  			return err
   146  		}
   147  		defer obj.Close()
   148  		return fn(relPath, obj)
   149  	})
   150  }
   151  
   152  func init() {
   153  	RegisterStorageType(setting.LocalStorageType, NewLocalStorage)
   154  }