github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/metadata/disk.go (about) 1 // Copyright 2018-2022 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package metadata 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "io" 26 "io/fs" 27 "os" 28 "path/filepath" 29 "time" 30 31 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 32 typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 33 "github.com/cs3org/reva/v2/pkg/errtypes" 34 ) 35 36 // Disk represents a disk metadata storage 37 type Disk struct { 38 dataDir string 39 } 40 41 // NewDiskStorage returns a new disk storage instance 42 func NewDiskStorage(dataDir string) (s Storage, err error) { 43 return &Disk{ 44 dataDir: dataDir, 45 }, nil 46 } 47 48 // Init creates the metadata space 49 func (disk *Disk) Init(_ context.Context, _ string) (err error) { 50 return os.MkdirAll(disk.dataDir, 0777) 51 } 52 53 // Backend returns the backend name of the storage 54 func (disk *Disk) Backend() string { 55 return "disk" 56 } 57 58 // Stat returns the metadata for the given path 59 func (disk *Disk) Stat(ctx context.Context, path string) (*provider.ResourceInfo, error) { 60 info, err := os.Stat(disk.targetPath(path)) 61 if err != nil { 62 var pathError *fs.PathError 63 if errors.As(err, &pathError) { 64 return nil, errtypes.NotFound("path not found: " + path) 65 } 66 return nil, err 67 } 68 entry := &provider.ResourceInfo{ 69 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 70 Path: "./" + info.Name(), 71 Name: info.Name(), 72 Mtime: &typesv1beta1.Timestamp{Seconds: uint64(info.ModTime().Unix()), Nanos: uint32(info.ModTime().Nanosecond())}, 73 } 74 if info.IsDir() { 75 entry.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER 76 } 77 entry.Etag, err = calcEtag(info.ModTime(), info.Size()) 78 if err != nil { 79 return nil, err 80 } 81 return entry, nil 82 } 83 84 // SimpleUpload stores a file on disk 85 func (disk *Disk) SimpleUpload(ctx context.Context, uploadpath string, content []byte) error { 86 _, err := disk.Upload(ctx, UploadRequest{ 87 Path: uploadpath, 88 Content: content, 89 }) 90 return err 91 } 92 93 // Upload stores a file on disk 94 func (disk *Disk) Upload(_ context.Context, req UploadRequest) (*UploadResponse, error) { 95 p := disk.targetPath(req.Path) 96 if req.IfMatchEtag != "" { 97 info, err := os.Stat(p) 98 if err != nil && !errors.Is(err, os.ErrNotExist) { 99 return nil, err 100 } else if err == nil { 101 etag, err := calcEtag(info.ModTime(), info.Size()) 102 if err != nil { 103 return nil, err 104 } 105 if etag != req.IfMatchEtag { 106 return nil, errtypes.PreconditionFailed("etag mismatch") 107 } 108 } 109 } 110 if req.IfUnmodifiedSince != (time.Time{}) { 111 info, err := os.Stat(p) 112 if err != nil && !errors.Is(err, os.ErrNotExist) { 113 return nil, err 114 } else if err == nil { 115 if info.ModTime().After(req.IfUnmodifiedSince) { 116 return nil, errtypes.PreconditionFailed(fmt.Sprintf("resource has been modified, mtime: %s > since %s", info.ModTime(), req.IfUnmodifiedSince)) 117 } 118 } 119 } 120 err := os.WriteFile(p, req.Content, 0644) 121 if err != nil { 122 return nil, err 123 } 124 125 info, err := os.Stat(disk.targetPath(req.Path)) 126 if err != nil { 127 return nil, err 128 } 129 res := &UploadResponse{} 130 res.Etag, err = calcEtag(info.ModTime(), info.Size()) 131 if err != nil { 132 return nil, err 133 } 134 return res, nil 135 } 136 137 // Download reads a file from disk 138 func (disk *Disk) Download(_ context.Context, req DownloadRequest) (*DownloadResponse, error) { 139 var err error 140 141 f, err := os.Open(disk.targetPath(req.Path)) 142 if err != nil { 143 var pathError *fs.PathError 144 if errors.As(err, &pathError) { 145 return nil, errtypes.NotFound("path not found: " + disk.targetPath(req.Path)) 146 } 147 return nil, err 148 } 149 defer f.Close() 150 151 info, err := f.Stat() 152 if err != nil { 153 return nil, err 154 } 155 156 res := DownloadResponse{} 157 res.Mtime = info.ModTime() 158 res.Etag, err = calcEtag(info.ModTime(), info.Size()) 159 if err != nil { 160 return nil, err 161 } 162 163 res.Content, err = io.ReadAll(f) 164 if err != nil { 165 return nil, err 166 } 167 return &res, nil 168 } 169 170 // SimpleDownload reads a file from disk 171 func (disk *Disk) SimpleDownload(ctx context.Context, downloadpath string) ([]byte, error) { 172 res, err := disk.Download(ctx, DownloadRequest{Path: downloadpath}) 173 return res.Content, err 174 } 175 176 // Delete deletes a path 177 func (disk *Disk) Delete(_ context.Context, path string) error { 178 return os.Remove(disk.targetPath(path)) 179 } 180 181 // ReadDir returns the resource infos in a given directory 182 func (disk *Disk) ReadDir(_ context.Context, p string) ([]string, error) { 183 infos, err := os.ReadDir(disk.targetPath(p)) 184 if err != nil { 185 if _, ok := err.(*fs.PathError); ok { 186 return []string{}, nil 187 } 188 return nil, err 189 } 190 191 entries := make([]string, 0, len(infos)) 192 for _, entry := range infos { 193 entries = append(entries, filepath.Join(p, entry.Name())) 194 } 195 return entries, nil 196 } 197 198 // ListDir returns a list of ResourceInfos for the entries in a given directory 199 func (disk *Disk) ListDir(ctx context.Context, path string) ([]*provider.ResourceInfo, error) { 200 diskEntries, err := os.ReadDir(disk.targetPath(path)) 201 if err != nil { 202 if _, ok := err.(*fs.PathError); ok { 203 return []*provider.ResourceInfo{}, nil 204 } 205 return nil, err 206 } 207 208 entries := make([]*provider.ResourceInfo, 0, len(diskEntries)) 209 for _, diskEntry := range diskEntries { 210 info, err := diskEntry.Info() 211 if err != nil { 212 continue 213 } 214 215 entry := &provider.ResourceInfo{ 216 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 217 Path: "./" + info.Name(), 218 Name: info.Name(), 219 Mtime: &typesv1beta1.Timestamp{Seconds: uint64(info.ModTime().Unix()), Nanos: uint32(info.ModTime().Nanosecond())}, 220 } 221 if info.IsDir() { 222 entry.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER 223 } 224 entries = append(entries, entry) 225 } 226 return entries, nil 227 } 228 229 // MakeDirIfNotExist will create a root node in the metadata storage. Requires an authenticated context. 230 func (disk *Disk) MakeDirIfNotExist(_ context.Context, path string) error { 231 return os.MkdirAll(disk.targetPath(path), 0777) 232 } 233 234 // CreateSymlink creates a symlink 235 func (disk *Disk) CreateSymlink(_ context.Context, oldname, newname string) error { 236 return os.Symlink(oldname, disk.targetPath(newname)) 237 } 238 239 // ResolveSymlink resolves a symlink 240 func (disk *Disk) ResolveSymlink(_ context.Context, path string) (string, error) { 241 return os.Readlink(disk.targetPath(path)) 242 } 243 244 func (disk *Disk) targetPath(p string) string { 245 return filepath.Join(disk.dataDir, filepath.Join("/", p)) 246 }