1 /* 2 * Copyright (C) 2018 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.car.systemupdater; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.util.Log; 22 23 import com.android.internal.util.Preconditions; 24 25 import java.io.BufferedReader; 26 import java.io.File; 27 import java.io.IOException; 28 import java.io.InputStreamReader; 29 import java.util.Arrays; 30 import java.util.Enumeration; 31 import java.util.Locale; 32 import java.util.zip.ZipEntry; 33 import java.util.zip.ZipFile; 34 35 /** Parse an A/B update zip file. */ 36 class UpdateParser { 37 38 private static final String TAG = "UpdateLayoutFragment"; 39 private static final String PAYLOAD_BIN_FILE = "payload.bin"; 40 private static final String PAYLOAD_PROPERTIES = "payload_properties.txt"; 41 private static final String FILE_URL_PREFIX = "file://"; 42 private static final int ZIP_FILE_HEADER = 30; 43 UpdateParser()44 private UpdateParser() { 45 } 46 47 /** 48 * Parse a zip file containing a system update and return a non null ParsedUpdate. 49 */ 50 @Nullable parse(@onNull File file)51 static ParsedUpdate parse(@NonNull File file) throws IOException { 52 Preconditions.checkNotNull(file); 53 54 long payloadOffset = 0; 55 long payloadSize = 0; 56 boolean payloadFound = false; 57 String[] props = null; 58 59 try (ZipFile zipFile = new ZipFile(file)) { 60 Enumeration<? extends ZipEntry> entries = zipFile.entries(); 61 while (entries.hasMoreElements()) { 62 ZipEntry entry = entries.nextElement(); 63 long fileSize = entry.getCompressedSize(); 64 if (!payloadFound) { 65 payloadOffset += ZIP_FILE_HEADER + entry.getName().length(); 66 if (entry.getExtra() != null) { 67 payloadOffset += entry.getExtra().length; 68 } 69 } 70 71 if (entry.isDirectory()) { 72 continue; 73 } else if (entry.getName().equals(PAYLOAD_BIN_FILE)) { 74 payloadSize = fileSize; 75 payloadFound = true; 76 } else if (entry.getName().equals(PAYLOAD_PROPERTIES)) { 77 try (BufferedReader buffer = new BufferedReader( 78 new InputStreamReader(zipFile.getInputStream(entry)))) { 79 props = buffer.lines().toArray(String[]::new); 80 } 81 } 82 if (!payloadFound) { 83 payloadOffset += fileSize; 84 } 85 86 if (Log.isLoggable(TAG, Log.DEBUG)) { 87 Log.d(TAG, String.format("Entry %s", entry.getName())); 88 } 89 } 90 } 91 return new ParsedUpdate(file, payloadOffset, payloadSize, props); 92 } 93 94 /** Information parsed from an update file. */ 95 static class ParsedUpdate { 96 final String mUrl; 97 final long mOffset; 98 final long mSize; 99 final String[] mProps; 100 ParsedUpdate(File file, long offset, long size, String[] props)101 ParsedUpdate(File file, long offset, long size, String[] props) { 102 mUrl = FILE_URL_PREFIX + file.getAbsolutePath(); 103 mOffset = offset; 104 mSize = size; 105 mProps = props; 106 } 107 108 /** Verify the update information is correct. */ isValid()109 boolean isValid() { 110 return mOffset >= 0 && mSize > 0 && mProps != null; 111 } 112 113 @Override toString()114 public String toString() { 115 return String.format(Locale.getDefault(), 116 "ParsedUpdate: URL=%s, offset=%d, size=%s, props=%s", 117 mUrl, mOffset, mSize, Arrays.toString(mProps)); 118 } 119 } 120 } 121