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 }