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