1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dynsystem; 18 19 import static java.lang.Math.min; 20 21 import java.io.BufferedInputStream; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.nio.ByteBuffer; 25 import java.nio.ByteOrder; 26 import java.util.Arrays; 27 28 /** 29 * SparseInputStream read from upstream and detects the data format. If the upstream is a valid 30 * sparse data, it will unsparse it on the fly. Otherwise, it just passthrough as is. 31 */ 32 public class SparseInputStream extends InputStream { 33 static final int FILE_HDR_SIZE = 28; 34 static final int CHUNK_HDR_SIZE = 12; 35 36 /** 37 * This class represents a chunk in the Android sparse image. 38 * 39 * @see system/core/libsparse/sparse_format.h 40 */ 41 private class SparseChunk { 42 static final short RAW = (short) 0xCAC1; 43 static final short FILL = (short) 0xCAC2; 44 static final short DONTCARE = (short) 0xCAC3; 45 public short mChunkType; 46 public int mChunkSize; 47 public int mTotalSize; 48 public byte[] fill; toString()49 public String toString() { 50 return String.format( 51 "type: %x, chunk_size: %d, total_size: %d", mChunkType, mChunkSize, mTotalSize); 52 } 53 } 54 readFull(InputStream in, int size)55 private byte[] readFull(InputStream in, int size) throws IOException { 56 byte[] buf = new byte[size]; 57 for (int done = 0, n = 0; done < size; done += n) { 58 if ((n = in.read(buf, done, size - done)) < 0) { 59 throw new IOException("Failed to readFull"); 60 } 61 } 62 return buf; 63 } 64 readBuffer(InputStream in, int size)65 private ByteBuffer readBuffer(InputStream in, int size) throws IOException { 66 return ByteBuffer.wrap(readFull(in, size)).order(ByteOrder.LITTLE_ENDIAN); 67 } 68 readChunk(InputStream in)69 private SparseChunk readChunk(InputStream in) throws IOException { 70 SparseChunk chunk = new SparseChunk(); 71 ByteBuffer buf = readBuffer(in, CHUNK_HDR_SIZE); 72 chunk.mChunkType = buf.getShort(); 73 buf.getShort(); 74 chunk.mChunkSize = buf.getInt(); 75 chunk.mTotalSize = buf.getInt(); 76 return chunk; 77 } 78 79 private BufferedInputStream mIn; 80 private boolean mIsSparse; 81 private long mBlockSize; 82 private long mTotalBlocks; 83 private long mTotalChunks; 84 private SparseChunk mCur; 85 private long mLeft; 86 private int mCurChunks; 87 SparseInputStream(BufferedInputStream in)88 public SparseInputStream(BufferedInputStream in) throws IOException { 89 mIn = in; 90 in.mark(FILE_HDR_SIZE * 2); 91 ByteBuffer buf = readBuffer(mIn, FILE_HDR_SIZE); 92 mIsSparse = (buf.getInt() == 0xed26ff3a); 93 if (!mIsSparse) { 94 mIn.reset(); 95 return; 96 } 97 int major = buf.getShort(); 98 int minor = buf.getShort(); 99 100 if (major > 0x1 || minor > 0x0) { 101 throw new IOException("Unsupported sparse version: " + major + "." + minor); 102 } 103 104 if (buf.getShort() != FILE_HDR_SIZE) { 105 throw new IOException("Illegal file header size"); 106 } 107 if (buf.getShort() != CHUNK_HDR_SIZE) { 108 throw new IOException("Illegal chunk header size"); 109 } 110 mBlockSize = buf.getInt(); 111 if ((mBlockSize & 0x3) != 0) { 112 throw new IOException("Illegal block size, must be a multiple of 4"); 113 } 114 mTotalBlocks = buf.getInt(); 115 mTotalChunks = buf.getInt(); 116 mLeft = mCurChunks = 0; 117 } 118 119 /** 120 * Check if it needs to open a new chunk. 121 * 122 * @return true if it's EOF 123 */ prepareChunk()124 private boolean prepareChunk() throws IOException { 125 if (mCur == null || mLeft <= 0) { 126 if (++mCurChunks > mTotalChunks) return true; 127 mCur = readChunk(mIn); 128 if (mCur.mChunkType == SparseChunk.FILL) { 129 mCur.fill = readFull(mIn, 4); 130 } 131 mLeft = mCur.mChunkSize * mBlockSize; 132 } 133 return mLeft == 0; 134 } 135 136 @Override read(byte[] buf, int off, int len)137 public int read(byte[] buf, int off, int len) throws IOException { 138 if (!mIsSparse) { 139 return mIn.read(buf, off, len); 140 } 141 if (prepareChunk()) return -1; 142 int n = -1; 143 switch (mCur.mChunkType) { 144 case SparseChunk.RAW: 145 n = mIn.read(buf, off, (int) min(mLeft, len)); 146 mLeft -= n; 147 return n; 148 case SparseChunk.DONTCARE: 149 n = (int) min(mLeft, len); 150 Arrays.fill(buf, off, off + n, (byte) 0); 151 mLeft -= n; 152 return n; 153 case SparseChunk.FILL: 154 // The FILL type is rarely used, so use a simple implmentation. 155 return super.read(buf, off, len); 156 default: 157 throw new IOException("Unsupported Chunk:" + mCur.toString()); 158 } 159 } 160 161 @Override read()162 public int read() throws IOException { 163 if (!mIsSparse) { 164 return mIn.read(); 165 } 166 if (prepareChunk()) return -1; 167 int ret = -1; 168 switch (mCur.mChunkType) { 169 case SparseChunk.RAW: 170 ret = mIn.read(); 171 break; 172 case SparseChunk.DONTCARE: 173 ret = 0; 174 break; 175 case SparseChunk.FILL: 176 ret = Byte.toUnsignedInt(mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3]); 177 break; 178 default: 179 throw new IOException("Unsupported Chunk:" + mCur.toString()); 180 } 181 mLeft--; 182 return ret; 183 } 184 185 /** 186 * Get the unsparse size 187 * @return -1 if unknown 188 */ getUnsparseSize()189 public long getUnsparseSize() { 190 if (!mIsSparse) { 191 return -1; 192 } 193 return mBlockSize * mTotalBlocks; 194 } 195 } 196