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