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  }