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  }