code.gitea.io/gitea@v1.19.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  	"io"
     9  	"net/url"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/util"
    17  )
    18  
    19  var _ ObjectStorage = &LocalStorage{}
    20  
    21  // LocalStorageType is the type descriptor for local storage
    22  const LocalStorageType Type = "local"
    23  
    24  // LocalStorageConfig represents the configuration for a local storage
    25  type LocalStorageConfig struct {
    26  	Path          string `ini:"PATH"`
    27  	TemporaryPath string `ini:"TEMPORARY_PATH"`
    28  }
    29  
    30  // LocalStorage represents a local files storage
    31  type LocalStorage struct {
    32  	ctx    context.Context
    33  	dir    string
    34  	tmpdir string
    35  }
    36  
    37  // NewLocalStorage returns a local files
    38  func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error) {
    39  	configInterface, err := toConfig(LocalStorageConfig{}, cfg)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	config := configInterface.(LocalStorageConfig)
    44  
    45  	log.Info("Creating new Local Storage at %s", config.Path)
    46  	if err := os.MkdirAll(config.Path, os.ModePerm); err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	if config.TemporaryPath == "" {
    51  		config.TemporaryPath = config.Path + "/tmp"
    52  	}
    53  
    54  	return &LocalStorage{
    55  		ctx:    ctx,
    56  		dir:    config.Path,
    57  		tmpdir: config.TemporaryPath,
    58  	}, nil
    59  }
    60  
    61  func (l *LocalStorage) buildLocalPath(p string) string {
    62  	return filepath.Join(l.dir, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:])
    63  }
    64  
    65  // Open a file
    66  func (l *LocalStorage) Open(path string) (Object, error) {
    67  	return os.Open(l.buildLocalPath(path))
    68  }
    69  
    70  // Save a file
    71  func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) {
    72  	p := l.buildLocalPath(path)
    73  	if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
    74  		return 0, err
    75  	}
    76  
    77  	// Create a temporary file to save to
    78  	if err := os.MkdirAll(l.tmpdir, os.ModePerm); err != nil {
    79  		return 0, err
    80  	}
    81  	tmp, err := os.CreateTemp(l.tmpdir, "upload-*")
    82  	if err != nil {
    83  		return 0, err
    84  	}
    85  	tmpRemoved := false
    86  	defer func() {
    87  		if !tmpRemoved {
    88  			_ = util.Remove(tmp.Name())
    89  		}
    90  	}()
    91  
    92  	n, err := io.Copy(tmp, r)
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  
    97  	if err := tmp.Close(); err != nil {
    98  		return 0, err
    99  	}
   100  
   101  	if err := util.Rename(tmp.Name(), p); err != nil {
   102  		return 0, err
   103  	}
   104  	// 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)
   105  	// but we don't want to make these files executable - so ensure that we mask out the executable bits
   106  	if err := util.ApplyUmask(p, os.ModePerm&0o666); err != nil {
   107  		return 0, err
   108  	}
   109  
   110  	tmpRemoved = true
   111  
   112  	return n, nil
   113  }
   114  
   115  // Stat returns the info of the file
   116  func (l *LocalStorage) Stat(path string) (os.FileInfo, error) {
   117  	return os.Stat(l.buildLocalPath(path))
   118  }
   119  
   120  // Delete delete a file
   121  func (l *LocalStorage) Delete(path string) error {
   122  	return util.Remove(l.buildLocalPath(path))
   123  }
   124  
   125  // URL gets the redirect URL to a file
   126  func (l *LocalStorage) URL(path, name string) (*url.URL, error) {
   127  	return nil, ErrURLNotSupported
   128  }
   129  
   130  // IterateObjects iterates across the objects in the local storage
   131  func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) error {
   132  	return filepath.WalkDir(l.dir, func(path string, d os.DirEntry, err error) error {
   133  		if err != nil {
   134  			return err
   135  		}
   136  		select {
   137  		case <-l.ctx.Done():
   138  			return l.ctx.Err()
   139  		default:
   140  		}
   141  		if path == l.dir {
   142  			return nil
   143  		}
   144  		if d.IsDir() {
   145  			return nil
   146  		}
   147  		relPath, err := filepath.Rel(l.dir, path)
   148  		if err != nil {
   149  			return err
   150  		}
   151  		obj, err := os.Open(path)
   152  		if err != nil {
   153  			return err
   154  		}
   155  		defer obj.Close()
   156  		return fn(relPath, obj)
   157  	})
   158  }
   159  
   160  func init() {
   161  	RegisterStorageType(LocalStorageType, NewLocalStorage)
   162  }