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 }