1 /*
2  * Copyright (C) 2020 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 /* Utils for target file.*/
18 package com.android.tradefed.util;
19 
20 import com.android.ddmlib.Log;
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.List;
27 
28 public class TargetFileUtils {
29     // 3 permission groups: owner, group, all users
30     private static final int PERMISSION_GROUPS = 3;
31 
32     public enum FilePermission {
33         EXECUTE(1),
34         READ(4),
35         WRITE(2);
36 
37         private int mPermissionNum;
38 
FilePermission(int permissionNum)39         FilePermission(int permissionNum) {
40             mPermissionNum = permissionNum;
41         }
42 
getPermissionNum()43         public int getPermissionNum() {
44             return mPermissionNum;
45         }
46     }
47 
48     /**
49      * Determines if the permission bits grant the specify permission to any group.
50      *
51      * @param permission The specify permissions.
52      * @param permissionBits The octal permissions string (e.g. 741).
53      * @return True if any owner/group/global has the specify permission.
54      */
hasPermission(FilePermission permission, String permissionBits)55     public static boolean hasPermission(FilePermission permission, String permissionBits) {
56         for (int i = 0; i < PERMISSION_GROUPS; i++) {
57             if (hasPermission(permissionBits, i, permission)) {
58                 return true;
59             }
60         }
61         return false;
62     }
63 
64     /**
65      * Read the file permission bits of a path.
66      *
67      * @param filepath Path to a file or directory.
68      * @param device The test device.
69      * @return  Octal permission bits for the path.
70      */
getPermission(String filepath, ITestDevice device)71     public static String getPermission(String filepath, ITestDevice device)
72             throws DeviceNotAvailableException {
73         CommandResult commandResult = device.executeShellV2Command("stat -c %a " + filepath);
74         if (!CommandStatus.SUCCESS.equals(commandResult.getStatus())) {
75             CLog.logAndDisplay(Log.LogLevel.ERROR, "Get permission error:\nstdout%s\nstderr",
76                     commandResult.getStdout(), commandResult.getStderr());
77             return "";
78         }
79         return commandResult.getStdout().trim();
80     }
81 
82     /**
83      * Determines if the permission bits grant a permission to a group.
84      *
85      * @param permissionBits The octal permissions string (e.g. 741).
86      * @param groupIndex The index of the group into the permissions string.
87      *                         (e.g. 0 is owner group). If set to -1, then all groups are
88      *                         checked.
89      * @param permission The value of the permission.
90      * @return  True if the group(s) has read permission.
91      */
hasPermission( String permissionBits, int groupIndex, FilePermission permission)92     private static boolean hasPermission(
93             String permissionBits, int groupIndex, FilePermission permission) {
94         if (groupIndex >= PERMISSION_GROUPS) {
95             throw new RuntimeException(String.format("Invalid group: %s", groupIndex));
96         }
97         if (permissionBits.length() != PERMISSION_GROUPS) {
98             throw new RuntimeException(
99                     String.format("Invalid permission bits: %s", permissionBits.length() + ""));
100         }
101 
102         // Define the start/end group index
103         int start = groupIndex;
104         int end = groupIndex + 1;
105         if (groupIndex < 0) {
106             start = 0;
107             end = PERMISSION_GROUPS;
108         }
109 
110         for (int i = start; i < end; i++) {
111             try {
112                 int perm = Integer.valueOf(permissionBits.charAt(i) + "");
113                 if (perm > 7) {
114                     throw new RuntimeException(String.format("Invalid permission bit: %d", perm));
115                 }
116                 if ((perm & permission.getPermissionNum()) == 0) {
117                     // Return false if any group lacks the permission
118                     return false;
119                 }
120             } catch (NumberFormatException e) {
121                 throw new RuntimeException(String.format(
122                         "Permission bits \"%s\" format error, should be three digital number "
123                                 + "(e.q. 741).",
124                         permissionBits));
125             }
126         }
127         // Return true if no group lacks the permission
128         return true;
129     }
130 
131     /**
132      * Helper method which executes a adb shell find command and returns the results as an {@link
133      * ArrayList<String>}.
134      *
135      * @param path The path to search on device.
136      * @param namePattern The file name pattern.
137      * @param options A {@link List} of {@link String} for other options pass to find.
138      * @param device The test device.
139      * @return The result in {@link ArrayList<String>}.
140      * @throws DeviceNotAvailableException if connection with device is lost and cannot be
141      *     recovered.
142      */
findFile(String path, String namePattern, List<String> options, ITestDevice device)143     public static ArrayList<String> findFile(String path, String namePattern, List<String> options,
144             ITestDevice device) throws DeviceNotAvailableException {
145         ArrayList<String> findedFiles = new ArrayList<>();
146         String command = String.format("find %s -name \"%s\"", path, namePattern);
147         if (options != null) {
148             command += " " + String.join(" ", options);
149         }
150         CLog.d("command: %s", command);
151         CommandResult result = device.executeShellV2Command(command);
152         if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
153             CLog.e("Find command: '%s' failed, returned:\nstdout:%s\nstderr:%s", command,
154                     result.getStdout(), result.getStderr());
155             return findedFiles;
156         }
157         findedFiles = new ArrayList<>(Arrays.asList(result.getStdout().split("\n")));
158         findedFiles.removeIf(s -> s.contentEquals(""));
159         return findedFiles;
160     }
161 
162     /**
163      * Check if the permission for a given path is readonly.
164      *
165      * @param filepath Path to a file or directory.
166      * @param device The test device.
167      * @return true if the path is readonly, false otherwise.
168      */
isReadOnly(String filepath, ITestDevice device)169     public static boolean isReadOnly(String filepath, ITestDevice device)
170             throws DeviceNotAvailableException {
171         String permissionBits = getPermission(filepath, device);
172         return (hasPermission(FilePermission.READ, permissionBits)
173                 && !hasPermission(FilePermission.WRITE, permissionBits)
174                 && !hasPermission(FilePermission.EXECUTE, permissionBits));
175     }
176 
177     /**
178      * Check if the permission for a given path is readwrite.
179      *
180      * @param filepath Path to a file or directory.
181      * @param device The test device.
182      * @return true if the path is readwrite, false otherwise.
183      */
isReadWriteOnly(String filepath, ITestDevice device)184     public static boolean isReadWriteOnly(String filepath, ITestDevice device)
185             throws DeviceNotAvailableException {
186         String permissionBits = getPermission(filepath, device);
187         return (hasPermission(FilePermission.READ, permissionBits)
188                 && hasPermission(FilePermission.WRITE, permissionBits)
189                 && !hasPermission(FilePermission.EXECUTE, permissionBits));
190     }
191 }
192