package mount import ( "fmt" "io" "github.com/seaweedfs/seaweedfs/weed/filer" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/operation" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/util" util_http "github.com/seaweedfs/seaweedfs/weed/util/http" ) func (wfs *WFS) saveDataAsChunk(fullPath util.FullPath) filer.SaveDataAsChunkFunctionType { // Backstop: FUSE entry points sanitize names before they reach // inodeToPath, but async flush paths (e.g. writebackCache, handles whose // RememberPath was set from an older code path) may still carry bytes // that predate sanitization. Proto3 string fields require valid UTF-8, // so scrub the full path once here before every AssignVolume call. assignPath := fullPath.Sanitized() return func(reader io.Reader, filename string, offset int64, tsNs int64, _ uint64) (chunk *filer_pb.FileChunk, err error) { uploader, err := operation.NewUploader() if err != nil { return } // WantMd5 gives mount writes a real filer.ETag (server echoes Content-MD5 back). uploadOption := &operation.UploadOption{ Filename: filename, Cipher: wfs.option.Cipher, IsInputCompressed: false, MimeType: "", PairMap: nil, WantMd5: true, } if wfs.option.VolumeServerAccess == "filerProxy" { // getCurrentFiler() can change on failover, so read it per attempt. uploadOption.GenUploadUrl = func(host, fileId string) string { return util_http.ProxyChunkUrl(string(wfs.getCurrentFiler()), fileId) } } fileId, uploadResult, err, data := uploader.UploadWithRetry( wfs, &filer_pb.AssignVolumeRequest{ Count: 1, Replication: wfs.option.Replication, Collection: wfs.option.Collection, TtlSec: wfs.option.TtlSec, DiskType: string(wfs.option.DiskType), DataCenter: wfs.option.DataCenter, Path: assignPath, }, uploadOption, reader, ) if err != nil { glog.V(0).Infof("upload data %v: %v", filename, err) return nil, fmt.Errorf("upload data: %w", err) } if uploadResult.Error != "" { glog.V(0).Infof("upload failure %v: %v", filename, err) return nil, fmt.Errorf("upload result: %v", uploadResult.Error) } // When peer sharing is enabled we need EVERY chunk in the // local cache so we can actually serve it back to peers on // FetchChunk — otherwise the directory would advertise us as // a holder and the fetcher would get NOT_FOUND from our // chunk cache. When peer sharing is off we preserve the // original behavior of caching only the first chunk (small // files) to avoid blowing the cache on large uploads. Both // paths gate on chunkCache != nil: -cacheCapacityMB=0 disables // the cache entirely, in which case SetChunk would panic. shouldCache := wfs.chunkCache != nil && (offset == 0 || wfs.peerAnnouncer != nil) if shouldCache { wfs.chunkCache.SetChunk(fileId, data) } // Announce every uploaded chunk so the tier-2 directory fills // in as the file is written. Without this, the per-fetch // announce path only bootstraps after someone else has already // pulled a chunk via peer — which can't happen if nobody has // told the directory who holds the chunk. Skip the announce // when we couldn't cache (no point advertising bytes we can't // actually serve back). if wfs.peerAnnouncer != nil && shouldCache { wfs.peerAnnouncer.EnqueueAnnounce(fileId) } chunk = uploadResult.ToPbFileChunk(fileId, offset, tsNs) return chunk, nil } }