1 /* 2 * Copyright (C) 2021 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.blockdevicewriter; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.ITestDevice; 24 import com.android.tradefed.util.CommandResult; 25 import com.android.tradefed.util.CommandStatus; 26 27 import java.util.ArrayList; 28 29 /** 30 * Wrapper for block_device_writer command. 31 * 32 * <p>To use this class, please push block_device_writer binary to /data/local/tmp. 33 * 1. In Android.bp, add: 34 * <pre> 35 * data_device_bins_both: ["block_device_writer"], 36 * </pre> 37 * 2. In AndroidText.xml, add: 38 * <pre> 39 * <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> 40 * <option name="append-bitness" value="true" /> 41 * <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> 42 * </target_preparer> 43 * </pre> 44 */ 45 public final class BlockDeviceWriter { 46 private static final String EXECUTABLE = "/data/local/tmp/block_device_writer"; 47 48 /** 49 * Modifies a byte of the file directly against the backing block storage. 50 * 51 * The effect can only be observed when the page cache is read from disk again. See 52 * {@link #dropCaches} for details. 53 */ damageFileAgainstBlockDevice(ITestDevice device, String path, long offsetOfTargetingByte)54 public static void damageFileAgainstBlockDevice(ITestDevice device, String path, 55 long offsetOfTargetingByte) 56 throws DeviceNotAvailableException { 57 assertThat(path).startsWith("/data/"); 58 ITestDevice.MountPointInfo mountPoint = device.getMountPointInfo("/data"); 59 ArrayList<String> args = new ArrayList<>(); 60 args.add(EXECUTABLE); 61 if ("f2fs".equals(mountPoint.type)) { 62 args.add("--use-f2fs-pinning"); 63 } 64 args.add(mountPoint.filesystem); 65 args.add(path); 66 args.add(Long.toString(offsetOfTargetingByte)); 67 CommandResult result = device.executeShellV2Command(String.join(" ", args)); 68 assertWithMessage( 69 String.format("stdout=%s\nstderr=%s", result.getStdout(), result.getStderr())) 70 .that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS); 71 } 72 73 /** 74 * Drops file caches so that the result of {@link #damageFileAgainstBlockDevice} can be 75 * observed. If a process has an open FD or memory map of the damaged file, cache eviction won't 76 * happen and the damage cannot be observed. 77 */ dropCaches(ITestDevice device)78 public static void dropCaches(ITestDevice device) throws DeviceNotAvailableException { 79 CommandResult result = device.executeShellV2Command( 80 "sync && echo 1 > /proc/sys/vm/drop_caches"); 81 assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS); 82 } 83 assertFileNotOpen(ITestDevice device, String path)84 public static void assertFileNotOpen(ITestDevice device, String path) 85 throws DeviceNotAvailableException { 86 CommandResult result = device.executeShellV2Command("lsof " + path); 87 assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS); 88 assertThat(result.getStdout()).isEmpty(); 89 } 90 91 /** 92 * Checks if the give offset of a file can be read. 93 * This method will return false if the file has fs-verity enabled and is damaged at the offset. 94 */ canReadByte(ITestDevice device, String filePath, long offset)95 public static boolean canReadByte(ITestDevice device, String filePath, long offset) 96 throws DeviceNotAvailableException { 97 CommandResult result = device.executeShellV2Command( 98 "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset)); 99 return result.getStatus() == CommandStatus.SUCCESS; 100 } 101 } 102