github.com/coreos/mantle@v0.13.0/update/operation.go (about) 1 // Copyright 2016 CoreOS, Inc. 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 package update 16 17 import ( 18 "bytes" 19 "compress/bzip2" 20 "crypto/sha256" 21 "fmt" 22 "hash" 23 "io" 24 "io/ioutil" 25 "os" 26 27 "github.com/coreos/mantle/update/metadata" 28 ) 29 30 type Operation struct { 31 hash.Hash 32 io.LimitedReader 33 34 Payload *Payload 35 Procedure *metadata.InstallProcedure 36 Operation *metadata.InstallOperation 37 } 38 39 func NewOperation(payload *Payload, proc *metadata.InstallProcedure, op *metadata.InstallOperation) *Operation { 40 sha := sha256.New() 41 return &Operation{ 42 Hash: sha, 43 LimitedReader: io.LimitedReader{ 44 R: io.TeeReader(payload, sha), 45 N: int64(op.GetDataLength()), 46 }, 47 Payload: payload, 48 Procedure: proc, 49 Operation: op, 50 } 51 } 52 53 func (op *Operation) Verify() error { 54 switch op.Operation.GetType() { 55 case metadata.InstallOperation_REPLACE: 56 fallthrough 57 case metadata.InstallOperation_REPLACE_BZ: 58 if err := op.verifyOffset(); err != nil { 59 return err 60 } 61 if len(op.Operation.SrcExtents) != 0 { 62 return fmt.Errorf("replace contains source extents") 63 } 64 if _, err := io.Copy(ioutil.Discard, op); err != nil { 65 return err 66 } 67 if err := op.verifyHash(); err != nil { 68 return err 69 } 70 case metadata.InstallOperation_MOVE: 71 return fmt.Errorf("MOVE") 72 case metadata.InstallOperation_BSDIFF: 73 return fmt.Errorf("BSDIFF") 74 } 75 76 return nil 77 } 78 79 func (op *Operation) verifyOffset() error { 80 if int64(op.Operation.GetDataOffset()) != op.Payload.Offset { 81 return fmt.Errorf("expected payload data offset %d not %d", 82 op.Operation.DataOffset, op.Payload.Offset) 83 } 84 return nil 85 } 86 87 func (op *Operation) verifyHash() error { 88 if len(op.Operation.DataSha256Hash) == 0 { 89 return fmt.Errorf("missing payload data hash") 90 } 91 92 sum := op.Sum(nil) 93 if !bytes.Equal(op.Operation.DataSha256Hash, sum) { 94 return fmt.Errorf("expected payload data hash %x not %x", 95 op.Operation.DataSha256Hash, sum) 96 } 97 98 return nil 99 } 100 101 func (op *Operation) Apply(dst, src *os.File) error { 102 switch op.Operation.GetType() { 103 case metadata.InstallOperation_REPLACE: 104 return op.replace(dst, op) 105 case metadata.InstallOperation_REPLACE_BZ: 106 return op.replace(dst, bzip2.NewReader(op)) 107 case metadata.InstallOperation_MOVE: 108 return op.move(dst, src) 109 case metadata.InstallOperation_BSDIFF: 110 return op.bsdiff(dst, src) 111 } 112 return fmt.Errorf("unknown operation type %s", op.Operation.GetType()) 113 } 114 115 func (op *Operation) replace(dst *os.File, src io.Reader) error { 116 if err := op.verifyOffset(); err != nil { 117 return err 118 } 119 if len(op.Operation.SrcExtents) != 0 { 120 return fmt.Errorf("replace contains source extents") 121 } 122 123 bs := int64(op.Payload.Manifest.GetBlockSize()) 124 maxSize := int64(op.Procedure.NewInfo.GetSize()) 125 for _, extent := range op.Operation.DstExtents { 126 offset := int64(extent.GetStartBlock()) * bs 127 length := int64(extent.GetNumBlocks()) * bs 128 129 // BUG: update_engine only writes as much of an extent as it 130 // has data for, allowing extents to be defined larger than 131 // they actually should be. So far this only appears to happen 132 // to the very last destination extent in a full update. 133 if offset+length > maxSize { 134 excess := (offset + length) - maxSize 135 plog.Warningf("extent excedes destination bounds by %d bytes!", excess) 136 length -= excess 137 } 138 139 if _, err := dst.Seek(offset, os.SEEK_SET); err != nil { 140 return err 141 } 142 if _, err := io.CopyN(dst, src, length); err != nil { 143 return err 144 } 145 } 146 147 // BUG: On rare occasions (once so far) 4 bytes will get left behind in 148 // a bzip2 compressed stream. Unclear what the bytes are and if they 149 // are there due to a difference in implementation of bzip2 itself or 150 // simply due to the algorithm being driven by reads instead of writes. 151 if op.N != 0 { 152 if op.Operation.GetType() == metadata.InstallOperation_REPLACE_BZ { 153 plog.Warningf("Go's bzip2 left %d bytes unread!", op.N) 154 if _, err := io.Copy(ioutil.Discard, op); err != nil { 155 return err 156 } 157 } else { 158 return fmt.Errorf("replace left %d trailing bytes", op.N) 159 } 160 } 161 162 return op.verifyHash() 163 } 164 165 func (op *Operation) move(dst, src *os.File) error { 166 return fmt.Errorf("MOVE") 167 } 168 169 func (op *Operation) bsdiff(dst, src *os.File) error { 170 return fmt.Errorf("BSDIFF") 171 }