// CreateFile - create a file inside disk root path, replies with custome disk.File which provides atomic writes func (disk Disk) CreateFile(filename string) (*atomic.File, *probe.Error) { disk.lock.Lock() defer disk.lock.Unlock() if filename == "" { return nil, probe.NewError(InvalidArgument{}) } f, err := atomic.FileCreate(filepath.Join(disk.path, filename)) if err != nil { return nil, probe.NewError(err) } return f, nil }
// writeFile writes data to a file named by filename. // If the file does not exist, writeFile creates it; // otherwise writeFile truncates it before writing. func writeFile(filename string, data []byte) *probe.Error { atomicFile, e := atomic.FileCreate(filename) if e != nil { return probe.NewError(e) } _, e = atomicFile.Write(data) if e != nil { return probe.NewError(e) } e = atomicFile.Close() if e != nil { return probe.NewError(e) } return nil }
// CreateObject - PUT object func (fs API) CreateObject(bucket, object, expectedMD5Sum string, size int64, data io.Reader, signature *Signature) (ObjectMetadata, *probe.Error) { fs.lock.Lock() defer fs.lock.Unlock() // check bucket name valid if !IsValidBucket(bucket) { return ObjectMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket}) } // check bucket exists if _, err := os.Stat(filepath.Join(fs.path, bucket)); os.IsNotExist(err) { return ObjectMetadata{}, probe.NewError(BucketNotFound{Bucket: bucket}) } // verify object path legal if !IsValidObjectName(object) { return ObjectMetadata{}, probe.NewError(ObjectNameInvalid{Bucket: bucket, Object: object}) } // get object path objectPath := filepath.Join(fs.path, bucket, object) if strings.TrimSpace(expectedMD5Sum) != "" { expectedMD5SumBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(expectedMD5Sum)) if err != nil { // pro-actively close the connection return ObjectMetadata{}, probe.NewError(InvalidDigest{Md5: expectedMD5Sum}) } expectedMD5Sum = hex.EncodeToString(expectedMD5SumBytes) } // write object file, err := atomic.FileCreate(objectPath) if err != nil { return ObjectMetadata{}, probe.NewError(err) } h := md5.New() sh := sha256.New() mw := io.MultiWriter(file, h, sh) if size > 0 { _, err = io.CopyN(mw, data, size) if err != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(err) } } else { _, err = io.Copy(mw, data) if err != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(err) } } md5Sum := hex.EncodeToString(h.Sum(nil)) // Verify if the written object is equal to what is expected, only if it is requested as such if strings.TrimSpace(expectedMD5Sum) != "" { if err := isMD5SumEqual(strings.TrimSpace(expectedMD5Sum), md5Sum); err != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(BadDigest{Md5: expectedMD5Sum, Bucket: bucket, Object: object}) } } sha256Sum := hex.EncodeToString(sh.Sum(nil)) if signature != nil { ok, perr := signature.DoesSignatureMatch(sha256Sum) if perr != nil { file.CloseAndPurge() return ObjectMetadata{}, perr.Trace() } if !ok { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(SignatureDoesNotMatch{}) } } file.File.Sync() file.Close() st, err := os.Stat(objectPath) if err != nil { return ObjectMetadata{}, probe.NewError(err) } newObject := ObjectMetadata{ Bucket: bucket, Object: object, Created: st.ModTime(), Size: st.Size(), ContentType: "application/octet-stream", Md5: md5Sum, } return newObject, nil }
// CompleteMultipartUpload - complete a multipart upload and persist the data func (fs API) CompleteMultipartUpload(bucket, object, uploadID string, data io.Reader, signature *Signature) (ObjectMetadata, *probe.Error) { fs.lock.Lock() defer fs.lock.Unlock() // check bucket name valid if !IsValidBucket(bucket) { return ObjectMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket}) } // verify object path legal if !IsValidObjectName(object) { return ObjectMetadata{}, probe.NewError(ObjectNameInvalid{Bucket: bucket, Object: object}) } if !fs.isValidUploadID(object, uploadID) { return ObjectMetadata{}, probe.NewError(InvalidUploadID{UploadID: uploadID}) } bucketPath := filepath.Join(fs.path, bucket) if _, err := os.Stat(bucketPath); err != nil { // check bucket exists if os.IsNotExist(err) { return ObjectMetadata{}, probe.NewError(BucketNotFound{Bucket: bucket}) } return ObjectMetadata{}, probe.NewError(InternalError{}) } objectPath := filepath.Join(bucketPath, object) file, err := atomic.FileCreate(objectPath) if err != nil { return ObjectMetadata{}, probe.NewError(err) } h := md5.New() mw := io.MultiWriter(file, h) partBytes, err := ioutil.ReadAll(data) if err != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(err) } if signature != nil { sh := sha256.New() sh.Write(partBytes) ok, perr := signature.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil))) if perr != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(err) } if !ok { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(SignatureDoesNotMatch{}) } } parts := &CompleteMultipartUpload{} if err := xml.Unmarshal(partBytes, parts); err != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(MalformedXML{}) } if !sort.IsSorted(completedParts(parts.Part)) { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(InvalidPartOrder{}) } if err := fs.concatParts(parts, objectPath, mw); err != nil { file.CloseAndPurge() return ObjectMetadata{}, err.Trace() } delete(fs.multiparts.ActiveSession, object) for _, part := range parts.Part { err = os.Remove(objectPath + fmt.Sprintf("$%d", part.PartNumber)) if err != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(err) } } if err := os.Remove(objectPath + "$multiparts"); err != nil { file.CloseAndPurge() return ObjectMetadata{}, probe.NewError(err) } if err := SaveMultipartsSession(fs.multiparts); err != nil { file.CloseAndPurge() return ObjectMetadata{}, err.Trace() } file.File.Sync() file.Close() st, err := os.Stat(objectPath) if err != nil { return ObjectMetadata{}, probe.NewError(err) } newObject := ObjectMetadata{ Bucket: bucket, Object: object, Created: st.ModTime(), Size: st.Size(), ContentType: "application/octet-stream", Md5: hex.EncodeToString(h.Sum(nil)), } return newObject, nil }
// CreateObjectPart - create a part in a multipart session func (fs API) CreateObjectPart(bucket, object, uploadID, expectedMD5Sum string, partID int, size int64, data io.Reader, signature *Signature) (string, *probe.Error) { fs.lock.Lock() defer fs.lock.Unlock() if partID <= 0 { return "", probe.NewError(errors.New("invalid part id, cannot be zero or less than zero")) } // check bucket name valid if !IsValidBucket(bucket) { return "", probe.NewError(BucketNameInvalid{Bucket: bucket}) } // verify object path legal if !IsValidObjectName(object) { return "", probe.NewError(ObjectNameInvalid{Bucket: bucket, Object: object}) } if !fs.isValidUploadID(object, uploadID) { return "", probe.NewError(InvalidUploadID{UploadID: uploadID}) } if strings.TrimSpace(expectedMD5Sum) != "" { expectedMD5SumBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(expectedMD5Sum)) if err != nil { // pro-actively close the connection return "", probe.NewError(InvalidDigest{Md5: expectedMD5Sum}) } expectedMD5Sum = hex.EncodeToString(expectedMD5SumBytes) } bucketPath := filepath.Join(fs.path, bucket) if _, err := os.Stat(bucketPath); err != nil { // check bucket exists if os.IsNotExist(err) { return "", probe.NewError(BucketNotFound{Bucket: bucket}) } if err != nil { return "", probe.NewError(InternalError{}) } } objectPath := filepath.Join(bucketPath, object) partPath := objectPath + fmt.Sprintf("$%d", partID) partFile, err := atomic.FileCreate(partPath) if err != nil { return "", probe.NewError(err) } h := md5.New() sh := sha256.New() mw := io.MultiWriter(partFile, h, sh) _, err = io.CopyN(mw, data, size) if err != nil { partFile.CloseAndPurge() return "", probe.NewError(err) } md5sum := hex.EncodeToString(h.Sum(nil)) // Verify if the written object is equal to what is expected, only if it is requested as such if strings.TrimSpace(expectedMD5Sum) != "" { if err := isMD5SumEqual(strings.TrimSpace(expectedMD5Sum), md5sum); err != nil { partFile.CloseAndPurge() return "", probe.NewError(BadDigest{Md5: expectedMD5Sum, Bucket: bucket, Object: object}) } } if signature != nil { ok, perr := signature.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil))) if perr != nil { partFile.CloseAndPurge() return "", perr.Trace() } if !ok { partFile.CloseAndPurge() return "", probe.NewError(SignatureDoesNotMatch{}) } } partFile.File.Sync() partFile.Close() fi, err := os.Stat(partPath) if err != nil { return "", probe.NewError(err) } partMetadata := PartMetadata{} partMetadata.ETag = md5sum partMetadata.PartNumber = partID partMetadata.Size = fi.Size() partMetadata.LastModified = fi.ModTime() multiPartfile, err := os.OpenFile(objectPath+"$multiparts", os.O_RDWR|os.O_APPEND, 0600) if err != nil { return "", probe.NewError(err) } defer multiPartfile.Close() var deserializedMultipartSession MultipartSession decoder := json.NewDecoder(multiPartfile) err = decoder.Decode(&deserializedMultipartSession) if err != nil { return "", probe.NewError(err) } deserializedMultipartSession.Parts = append(deserializedMultipartSession.Parts, &partMetadata) deserializedMultipartSession.TotalParts++ fs.multiparts.ActiveSession[object] = &deserializedMultipartSession sort.Sort(partNumber(deserializedMultipartSession.Parts)) encoder := json.NewEncoder(multiPartfile) err = encoder.Encode(&deserializedMultipartSession) if err != nil { return "", probe.NewError(err) } return partMetadata.ETag, nil }