1package storage 2 3// Copyright 2017 Microsoft Corporation 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17import ( 18 "encoding/xml" 19 "errors" 20 "fmt" 21 "io" 22 "net/http" 23 "net/url" 24 "time" 25) 26 27// GetPageRangesResponse contains the response fields from 28// Get Page Ranges call. 29// 30// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx 31type GetPageRangesResponse struct { 32 XMLName xml.Name `xml:"PageList"` 33 PageList []PageRange `xml:"PageRange"` 34} 35 36// PageRange contains information about a page of a page blob from 37// Get Pages Range call. 38// 39// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx 40type PageRange struct { 41 Start int64 `xml:"Start"` 42 End int64 `xml:"End"` 43} 44 45var ( 46 errBlobCopyAborted = errors.New("storage: blob copy is aborted") 47 errBlobCopyIDMismatch = errors.New("storage: blob copy id is a mismatch") 48) 49 50// PutPageOptions includes the options for a put page operation 51type PutPageOptions struct { 52 Timeout uint 53 LeaseID string `header:"x-ms-lease-id"` 54 IfSequenceNumberLessThanOrEqualTo *int `header:"x-ms-if-sequence-number-le"` 55 IfSequenceNumberLessThan *int `header:"x-ms-if-sequence-number-lt"` 56 IfSequenceNumberEqualTo *int `header:"x-ms-if-sequence-number-eq"` 57 IfModifiedSince *time.Time `header:"If-Modified-Since"` 58 IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"` 59 IfMatch string `header:"If-Match"` 60 IfNoneMatch string `header:"If-None-Match"` 61 RequestID string `header:"x-ms-client-request-id"` 62} 63 64// WriteRange writes a range of pages to a page blob. 65// Ranges must be aligned with 512-byte boundaries and chunk must be of size 66// multiplies by 512. 67// 68// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Page 69func (b *Blob) WriteRange(blobRange BlobRange, bytes io.Reader, options *PutPageOptions) error { 70 if bytes == nil { 71 return errors.New("bytes cannot be nil") 72 } 73 return b.modifyRange(blobRange, bytes, options) 74} 75 76// ClearRange clears the given range in a page blob. 77// Ranges must be aligned with 512-byte boundaries and chunk must be of size 78// multiplies by 512. 79// 80// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Page 81func (b *Blob) ClearRange(blobRange BlobRange, options *PutPageOptions) error { 82 return b.modifyRange(blobRange, nil, options) 83} 84 85func (b *Blob) modifyRange(blobRange BlobRange, bytes io.Reader, options *PutPageOptions) error { 86 if blobRange.End < blobRange.Start { 87 return errors.New("the value for rangeEnd must be greater than or equal to rangeStart") 88 } 89 if blobRange.Start%512 != 0 { 90 return errors.New("the value for rangeStart must be a multiple of 512") 91 } 92 if blobRange.End%512 != 511 { 93 return errors.New("the value for rangeEnd must be a multiple of 512 - 1") 94 } 95 96 params := url.Values{"comp": {"page"}} 97 98 // default to clear 99 write := "clear" 100 var cl uint64 101 102 // if bytes is not nil then this is an update operation 103 if bytes != nil { 104 write = "update" 105 cl = (blobRange.End - blobRange.Start) + 1 106 } 107 108 headers := b.Container.bsc.client.getStandardHeaders() 109 headers["x-ms-blob-type"] = string(BlobTypePage) 110 headers["x-ms-page-write"] = write 111 headers["x-ms-range"] = blobRange.String() 112 headers["Content-Length"] = fmt.Sprintf("%v", cl) 113 114 if options != nil { 115 params = addTimeout(params, options.Timeout) 116 headers = mergeHeaders(headers, headersFromStruct(*options)) 117 } 118 uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params) 119 120 resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, bytes, b.Container.bsc.auth) 121 if err != nil { 122 return err 123 } 124 defer drainRespBody(resp) 125 return checkRespCode(resp, []int{http.StatusCreated}) 126} 127 128// GetPageRangesOptions includes the options for a get page ranges operation 129type GetPageRangesOptions struct { 130 Timeout uint 131 Snapshot *time.Time 132 PreviousSnapshot *time.Time 133 Range *BlobRange 134 LeaseID string `header:"x-ms-lease-id"` 135 RequestID string `header:"x-ms-client-request-id"` 136} 137 138// GetPageRanges returns the list of valid page ranges for a page blob. 139// 140// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Page-Ranges 141func (b *Blob) GetPageRanges(options *GetPageRangesOptions) (GetPageRangesResponse, error) { 142 params := url.Values{"comp": {"pagelist"}} 143 headers := b.Container.bsc.client.getStandardHeaders() 144 145 if options != nil { 146 params = addTimeout(params, options.Timeout) 147 params = addSnapshot(params, options.Snapshot) 148 if options.PreviousSnapshot != nil { 149 params.Add("prevsnapshot", timeRFC3339Formatted(*options.PreviousSnapshot)) 150 } 151 if options.Range != nil { 152 headers["Range"] = options.Range.String() 153 } 154 headers = mergeHeaders(headers, headersFromStruct(*options)) 155 } 156 uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params) 157 158 var out GetPageRangesResponse 159 resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth) 160 if err != nil { 161 return out, err 162 } 163 defer drainRespBody(resp) 164 165 if err = checkRespCode(resp, []int{http.StatusOK}); err != nil { 166 return out, err 167 } 168 err = xmlUnmarshal(resp.Body, &out) 169 return out, err 170} 171 172// PutPageBlob initializes an empty page blob with specified name and maximum 173// size in bytes (size must be aligned to a 512-byte boundary). A page blob must 174// be created using this method before writing pages. 175// 176// See CreateBlockBlobFromReader for more info on creating blobs. 177// 178// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Blob 179func (b *Blob) PutPageBlob(options *PutBlobOptions) error { 180 if b.Properties.ContentLength%512 != 0 { 181 return errors.New("Content length must be aligned to a 512-byte boundary") 182 } 183 184 params := url.Values{} 185 headers := b.Container.bsc.client.getStandardHeaders() 186 headers["x-ms-blob-type"] = string(BlobTypePage) 187 headers["x-ms-blob-content-length"] = fmt.Sprintf("%v", b.Properties.ContentLength) 188 headers["x-ms-blob-sequence-number"] = fmt.Sprintf("%v", b.Properties.SequenceNumber) 189 headers = mergeHeaders(headers, headersFromStruct(b.Properties)) 190 headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata) 191 192 if options != nil { 193 params = addTimeout(params, options.Timeout) 194 headers = mergeHeaders(headers, headersFromStruct(*options)) 195 } 196 uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params) 197 198 resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth) 199 if err != nil { 200 return err 201 } 202 return b.respondCreation(resp, BlobTypePage) 203} 204