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