github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/cephfs/upload.go (about) 1 // Copyright 2018-2021 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 //go:build ceph 20 // +build ceph 21 22 package cephfs 23 24 import ( 25 "bytes" 26 "context" 27 "encoding/json" 28 "io" 29 "os" 30 "path/filepath" 31 32 cephfs2 "github.com/ceph/go-ceph/cephfs" 33 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 34 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 35 "github.com/cs3org/reva/v2/pkg/appctx" 36 ctx2 "github.com/cs3org/reva/v2/pkg/ctx" 37 "github.com/cs3org/reva/v2/pkg/errtypes" 38 "github.com/cs3org/reva/v2/pkg/storage" 39 "github.com/cs3org/reva/v2/pkg/utils" 40 "github.com/google/uuid" 41 "github.com/pkg/errors" 42 tusd "github.com/tus/tusd/v2/pkg/handler" 43 ) 44 45 func (fs *cephfs) Upload(ctx context.Context, req storage.UploadRequest, uff storage.UploadFinishedFunc) (*provider.ResourceInfo, error) { 46 user := fs.makeUser(ctx) 47 upload, err := fs.GetUpload(ctx, req.Ref.GetPath()) 48 if err != nil { 49 metadata := map[string]string{"sizedeferred": "true"} 50 uploadIDs, err := fs.InitiateUpload(ctx, req.Ref, 0, metadata) 51 if err != nil { 52 return &provider.ResourceInfo{}, err 53 } 54 if upload, err = fs.GetUpload(ctx, uploadIDs["simple"]); err != nil { 55 return &provider.ResourceInfo{}, errors.Wrap(err, "cephfs: error retrieving upload") 56 } 57 } 58 59 uploadInfo := upload.(*fileUpload) 60 61 p := uploadInfo.info.Storage["InternalDestination"] 62 ok, err := IsChunked(p) 63 if err != nil { 64 return &provider.ResourceInfo{}, errors.Wrap(err, "cephfs: error checking path") 65 } 66 if ok { 67 var assembledFile string 68 p, assembledFile, err = NewChunkHandler(ctx, fs).WriteChunk(p, req.Body) 69 if err != nil { 70 return &provider.ResourceInfo{}, err 71 } 72 if p == "" { 73 if err = uploadInfo.Terminate(ctx); err != nil { 74 return &provider.ResourceInfo{}, errors.Wrap(err, "cephfs: error removing auxiliary files") 75 } 76 return &provider.ResourceInfo{}, errtypes.PartialContent(req.Ref.String()) 77 } 78 uploadInfo.info.Storage["InternalDestination"] = p 79 80 user.op(func(cv *cacheVal) { 81 req.Body, err = cv.mount.Open(assembledFile, os.O_RDONLY, 0) 82 }) 83 if err != nil { 84 return &provider.ResourceInfo{}, errors.Wrap(err, "cephfs: error opening assembled file") 85 } 86 defer req.Body.Close() 87 defer user.op(func(cv *cacheVal) { 88 _ = cv.mount.Unlink(assembledFile) 89 }) 90 } 91 ri := &provider.ResourceInfo{ 92 // fill with at least fileid, mtime and etag 93 Id: &provider.ResourceId{ 94 StorageId: uploadInfo.info.MetaData["providerID"], 95 SpaceId: uploadInfo.info.Storage["SpaceRoot"], 96 OpaqueId: uploadInfo.info.Storage["NodeId"], 97 }, 98 Etag: uploadInfo.info.MetaData["etag"], 99 } 100 101 if mtime, err := utils.MTimeToTS(uploadInfo.info.MetaData["mtime"]); err == nil { 102 ri.Mtime = &mtime 103 } 104 105 if _, err := uploadInfo.WriteChunk(ctx, 0, req.Body); err != nil { 106 return &provider.ResourceInfo{}, errors.Wrap(err, "cephfs: error writing to binary file") 107 } 108 109 return ri, uploadInfo.FinishUpload(ctx) 110 } 111 112 func (fs *cephfs) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { 113 user := fs.makeUser(ctx) 114 np, err := user.resolveRef(ref) 115 if err != nil { 116 return nil, errors.Wrap(err, "cephfs: error resolving reference") 117 } 118 119 info := tusd.FileInfo{ 120 MetaData: tusd.MetaData{ 121 "filename": filepath.Base(np), 122 "dir": filepath.Dir(np), 123 }, 124 Size: uploadLength, 125 } 126 127 if metadata != nil { 128 info.MetaData["providerID"] = metadata["providerID"] 129 if metadata["mtime"] != "" { 130 info.MetaData["mtime"] = metadata["mtime"] 131 } 132 if _, ok := metadata["sizedeferred"]; ok { 133 info.SizeIsDeferred = true 134 } 135 } 136 137 upload, err := fs.NewUpload(ctx, info) 138 if err != nil { 139 return nil, err 140 } 141 142 info, _ = upload.GetInfo(ctx) 143 144 return map[string]string{ 145 "simple": info.ID, 146 "tus": info.ID, 147 }, nil 148 } 149 150 // UseIn tells the tus upload middleware which extensions it supports. 151 func (fs *cephfs) UseIn(composer *tusd.StoreComposer) { 152 composer.UseCore(fs) 153 composer.UseTerminater(fs) 154 } 155 156 func (fs *cephfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tusd.Upload, err error) { 157 log := appctx.GetLogger(ctx) 158 log.Debug().Interface("info", info).Msg("cephfs: NewUpload") 159 160 user := fs.makeUser(ctx) 161 162 fn := info.MetaData["filename"] 163 if fn == "" { 164 return nil, errors.New("cephfs: missing filename in metadata") 165 } 166 info.MetaData["filename"] = filepath.Clean(info.MetaData["filename"]) 167 168 dir := info.MetaData["dir"] 169 if dir == "" { 170 return nil, errors.New("cephfs: missing dir in metadata") 171 } 172 info.MetaData["dir"] = filepath.Clean(info.MetaData["dir"]) 173 174 np := filepath.Join(info.MetaData["dir"], info.MetaData["filename"]) 175 176 info.ID = uuid.New().String() 177 178 binPath := fs.getUploadPath(info.ID) 179 180 info.Storage = map[string]string{ 181 "Type": "Cephfs", 182 "BinPath": binPath, 183 "InternalDestination": np, 184 185 "Idp": user.Id.Idp, 186 "UserId": user.Id.OpaqueId, 187 "UserName": user.Username, 188 "UserType": utils.UserTypeToString(user.Id.Type), 189 190 "LogLevel": log.GetLevel().String(), 191 } 192 193 // Create binary file with no content 194 user.op(func(cv *cacheVal) { 195 var f *cephfs2.File 196 defer closeFile(f) 197 f, err = cv.mount.Open(binPath, os.O_CREATE|os.O_WRONLY, filePermDefault) 198 if err != nil { 199 return 200 } 201 }) 202 //TODO: if we get two same upload ids, the second one can't upload at all 203 if err != nil { 204 return 205 } 206 207 upload = &fileUpload{ 208 info: info, 209 binPath: binPath, 210 infoPath: binPath + ".info", 211 fs: fs, 212 ctx: ctx, 213 } 214 215 if !info.SizeIsDeferred && info.Size == 0 { 216 log.Debug().Interface("info", info).Msg("cephfs: finishing upload for empty file") 217 // no need to create info file and finish directly 218 err = upload.FinishUpload(ctx) 219 220 return 221 } 222 223 // writeInfo creates the file by itself if necessary 224 err = upload.(*fileUpload).writeInfo() 225 226 return 227 } 228 229 func (fs *cephfs) getUploadPath(uploadID string) string { 230 return filepath.Join(fs.conf.UploadFolder, uploadID) 231 } 232 233 // GetUpload returns the Upload for the given upload id 234 func (fs *cephfs) GetUpload(ctx context.Context, id string) (fup tusd.Upload, err error) { 235 binPath := fs.getUploadPath(id) 236 info := tusd.FileInfo{} 237 if err != nil { 238 return nil, errtypes.NotFound("bin path for upload " + id + " not found") 239 } 240 infoPath := binPath + ".info" 241 242 var data bytes.Buffer 243 f, err := fs.adminConn.adminMount.Open(infoPath, os.O_RDONLY, 0) 244 if err != nil { 245 return 246 } 247 _, err = io.Copy(&data, f) 248 if err != nil { 249 return 250 } 251 if err = json.Unmarshal(data.Bytes(), &info); err != nil { 252 return 253 } 254 255 u := &userpb.User{ 256 Id: &userpb.UserId{ 257 Idp: info.Storage["Idp"], 258 OpaqueId: info.Storage["UserId"], 259 }, 260 Username: info.Storage["UserName"], 261 } 262 ctx = ctx2.ContextSetUser(ctx, u) 263 user := fs.makeUser(ctx) 264 265 var stat Statx 266 user.op(func(cv *cacheVal) { 267 stat, err = cv.mount.Statx(binPath, cephfs2.StatxSize, 0) 268 }) 269 if err != nil { 270 return 271 } 272 info.Offset = int64(stat.Size) 273 274 return &fileUpload{ 275 info: info, 276 binPath: binPath, 277 infoPath: infoPath, 278 fs: fs, 279 ctx: ctx, 280 }, nil 281 } 282 283 type fileUpload struct { 284 // info stores the current information about the upload 285 info tusd.FileInfo 286 // infoPath is the path to the .info file 287 infoPath string 288 // binPath is the path to the binary file (which has no extension) 289 binPath string 290 // only fs knows how to handle metadata and versions 291 fs *cephfs 292 // a context with a user 293 ctx context.Context 294 } 295 296 // GetInfo returns the FileInfo 297 func (upload *fileUpload) GetInfo(ctx context.Context) (tusd.FileInfo, error) { 298 return upload.info, nil 299 } 300 301 // GetReader returns an io.Reader for the upload 302 func (upload *fileUpload) GetReader(ctx context.Context) (file io.ReadCloser, err error) { 303 user := upload.fs.makeUser(upload.ctx) 304 user.op(func(cv *cacheVal) { 305 file, err = cv.mount.Open(upload.binPath, os.O_RDONLY, 0) 306 }) 307 return 308 } 309 310 // WriteChunk writes the stream from the reader to the given offset of the upload 311 func (upload *fileUpload) WriteChunk(ctx context.Context, offset int64, src io.Reader) (n int64, err error) { 312 var file io.WriteCloser 313 user := upload.fs.makeUser(upload.ctx) 314 user.op(func(cv *cacheVal) { 315 file, err = cv.mount.Open(upload.binPath, os.O_WRONLY|os.O_APPEND, 0) 316 }) 317 if err != nil { 318 return 0, err 319 } 320 defer file.Close() 321 322 n, err = io.Copy(file, src) 323 324 // If the HTTP PATCH request gets interrupted in the middle (e.g. because 325 // the user wants to pause the upload), Go's net/http returns an io.ErrUnexpectedEOF. 326 // However, for OwnCloudStore it's not important whether the stream has ended 327 // on purpose or accidentally. 328 if err != nil { 329 if err != io.ErrUnexpectedEOF { 330 return n, err 331 } 332 } 333 334 upload.info.Offset += n 335 err = upload.writeInfo() 336 337 return n, err 338 } 339 340 // writeInfo updates the entire information. Everything will be overwritten. 341 func (upload *fileUpload) writeInfo() error { 342 data, err := json.Marshal(upload.info) 343 344 if err != nil { 345 return err 346 } 347 user := upload.fs.makeUser(upload.ctx) 348 user.op(func(cv *cacheVal) { 349 var file io.WriteCloser 350 if file, err = cv.mount.Open(upload.infoPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, filePermDefault); err != nil { 351 return 352 } 353 defer file.Close() 354 355 _, err = io.Copy(file, bytes.NewReader(data)) 356 }) 357 358 return err 359 } 360 361 // FinishUpload finishes an upload and moves the file to the internal destination 362 func (upload *fileUpload) FinishUpload(ctx context.Context) (err error) { 363 364 np := upload.info.Storage["InternalDestination"] 365 366 // TODO check etag with If-Match header 367 // if destination exists 368 // if _, err := os.Stat(np); err == nil { 369 // the local storage does not store metadata 370 // the fileid is based on the path, so no we do not need to copy it to the new file 371 // the local storage does not track revisions 372 // } 373 374 // if destination exists 375 // if _, err := os.Stat(np); err == nil { 376 // create revision 377 // if err := upload.fs.archiveRevision(upload.ctx, np); err != nil { 378 // return err 379 // } 380 // } 381 382 user := upload.fs.makeUser(upload.ctx) 383 log := appctx.GetLogger(ctx) 384 385 user.op(func(cv *cacheVal) { 386 err = cv.mount.Rename(upload.binPath, np) 387 }) 388 if err != nil { 389 return errors.Wrap(err, upload.binPath) 390 } 391 392 // only delete the upload if it was successfully written to the fs 393 user.op(func(cv *cacheVal) { 394 err = cv.mount.Unlink(upload.infoPath) 395 }) 396 if err != nil { 397 if err.Error() != errNotFound { 398 log.Err(err).Interface("info", upload.info).Msg("cephfs: could not delete upload metadata") 399 } 400 } 401 402 // TODO: set mtime if specified in metadata 403 404 return 405 } 406 407 // To implement the termination extension as specified in https://tus.io/protocols/resumable-upload.html#termination 408 // - the storage needs to implement AsTerminatableUpload 409 // - the upload needs to implement Terminate 410 411 // AsTerminatableUpload returns a a TerminatableUpload 412 func (fs *cephfs) AsTerminatableUpload(upload tusd.Upload) tusd.TerminatableUpload { 413 return upload.(*fileUpload) 414 } 415 416 // Terminate terminates the upload 417 func (upload *fileUpload) Terminate(ctx context.Context) (err error) { 418 user := upload.fs.makeUser(upload.ctx) 419 420 user.op(func(cv *cacheVal) { 421 if err = cv.mount.Unlink(upload.infoPath); err != nil { 422 return 423 } 424 err = cv.mount.Unlink(upload.binPath) 425 }) 426 427 return 428 }