/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.systemupdater; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; import com.android.internal.util.Preconditions; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.Enumeration; import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** Parse an A/B update zip file. */ class UpdateParser { private static final String TAG = "UpdateLayoutFragment"; private static final String PAYLOAD_BIN_FILE = "payload.bin"; private static final String PAYLOAD_PROPERTIES = "payload_properties.txt"; private static final String FILE_URL_PREFIX = "file://"; private static final int ZIP_FILE_HEADER = 30; private UpdateParser() { } /** * Parse a zip file containing a system update and return a non null ParsedUpdate. */ @Nullable static ParsedUpdate parse(@NonNull File file) throws IOException { Preconditions.checkNotNull(file); long payloadOffset = 0; long payloadSize = 0; boolean payloadFound = false; String[] props = null; try (ZipFile zipFile = new ZipFile(file)) { Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); long fileSize = entry.getCompressedSize(); if (!payloadFound) { payloadOffset += ZIP_FILE_HEADER + entry.getName().length(); if (entry.getExtra() != null) { payloadOffset += entry.getExtra().length; } } if (entry.isDirectory()) { continue; } else if (entry.getName().equals(PAYLOAD_BIN_FILE)) { payloadSize = fileSize; payloadFound = true; } else if (entry.getName().equals(PAYLOAD_PROPERTIES)) { try (BufferedReader buffer = new BufferedReader( new InputStreamReader(zipFile.getInputStream(entry)))) { props = buffer.lines().toArray(String[]::new); } } if (!payloadFound) { payloadOffset += fileSize; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, String.format("Entry %s", entry.getName())); } } } return new ParsedUpdate(file, payloadOffset, payloadSize, props); } /** Information parsed from an update file. */ static class ParsedUpdate { final String mUrl; final long mOffset; final long mSize; final String[] mProps; ParsedUpdate(File file, long offset, long size, String[] props) { mUrl = FILE_URL_PREFIX + file.getAbsolutePath(); mOffset = offset; mSize = size; mProps = props; } /** Verify the update information is correct. */ boolean isValid() { return mOffset >= 0 && mSize > 0 && mProps != null; } @Override public String toString() { return String.format(Locale.getDefault(), "ParsedUpdate: URL=%s, offset=%d, size=%s, props=%s", mUrl, mOffset, mSize, Arrays.toString(mProps)); } } }