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(
76                     Log.LogLevel.ERROR,
77                     "Get permission error:\nstdout%s\nstderr",
78                     commandResult.getStdout(),
79                     commandResult.getStderr());
80             return "";
81         }
82         return commandResult.getStdout().trim();
83     }
84 
85     /**
86      * Determines if the permission bits grant a permission to a group.
87      *
88      * @param permissionBits The octal permissions string (e.g. 741).
89      * @param groupIndex The index of the group into the permissions string. (e.g. 0 is owner
90      *     group). If set to -1, then all groups are checked.
91      * @param permission The value of the permission.
92      * @return True if the group(s) has read permission.
93      */
hasPermission( String permissionBits, int groupIndex, FilePermission permission)94     private static boolean hasPermission(
95             String permissionBits, int groupIndex, FilePermission permission) {
96         if (groupIndex >= PERMISSION_GROUPS) {
97             throw new RuntimeException(String.format("Invalid group: %s", groupIndex));
98         }
99         if (permissionBits.length() != PERMISSION_GROUPS) {
100             throw new RuntimeException(
101                     String.format("Invalid permission bits: %s", permissionBits.length() + ""));
102         }
103 
104         // Define the start/end group index
105         int start = groupIndex;
106         int end = groupIndex + 1;
107         if (groupIndex < 0) {
108             start = 0;
109             end = PERMISSION_GROUPS;
110         }
111 
112         for (int i = start; i < end; i++) {
113             try {
114                 int perm = Integer.valueOf(permissionBits.charAt(i) + "");
115                 if (perm > 7) {
116                     throw new RuntimeException(String.format("Invalid permission bit: %d", perm));
117                 }
118                 if ((perm & permission.getPermissionNum()) == 0) {
119                     // Return false if any group lacks the permission
120                     return false;
121                 }
122             } catch (NumberFormatException e) {
123                 throw new RuntimeException(
124                         String.format(
125                                 "Permission bits \"%s\" format error, should be three digital"
126                                         + " number (e.q. 741).",
127                                 permissionBits));
128             }
129         }
130         // Return true if no group lacks the permission
131         return true;
132     }
133 
134     /**
135      * Helper method which executes a adb shell find command and returns the results as an {@link
136      * ArrayList<String>}.
137      *
138      * @param path The path to search on device.
139      * @param namePattern The file name pattern.
140      * @param options A {@link List} of {@link String} for other options pass to find.
141      * @param device The test device.
142      * @return The result in {@link ArrayList<String>}.
143      * @throws DeviceNotAvailableException if connection with device is lost and cannot be
144      *     recovered.
145      */
findFile( String path, String namePattern, List<String> options, ITestDevice device)146     public static ArrayList<String> findFile(
147             String path, String namePattern, List<String> options, ITestDevice device)
148             throws DeviceNotAvailableException {
149         ArrayList<String> findedFiles = new ArrayList<>();
150         String command = String.format("find %s -name \"%s\"", path, namePattern);
151         if (options != null) {
152             command += " " + String.join(" ", options);
153         }
154         CLog.d("command: %s", command);
155         CommandResult result = device.executeShellV2Command(command);
156         if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
157             CLog.e(
158                     "Find command: '%s' failed, returned:\nstdout:%s\nstderr:%s",
159                     command, result.getStdout(), result.getStderr());
160             return findedFiles;
161         }
162         findedFiles = new ArrayList<>(Arrays.asList(result.getStdout().split("\n")));
163         findedFiles.removeIf(s -> s.contentEquals(""));
164         return findedFiles;
165     }
166 
167     /**
168      * Check if the permission for a given path is readonly.
169      *
170      * @param filepath Path to a file or directory.
171      * @param device The test device.
172      * @return true if the path is readonly, false otherwise.
173      */
isReadOnly(String filepath, ITestDevice device)174     public static boolean isReadOnly(String filepath, ITestDevice device)
175             throws DeviceNotAvailableException {
176         String permissionBits = getPermission(filepath, device);
177         return (hasPermission(FilePermission.READ, permissionBits)
178                 && !hasPermission(FilePermission.WRITE, permissionBits)
179                 && !hasPermission(FilePermission.EXECUTE, permissionBits));
180     }
181 
182     /**
183      * Check if the permission for a given path is readwrite.
184      *
185      * @param filepath Path to a file or directory.
186      * @param device The test device.
187      * @return true if the path is readwrite, false otherwise.
188      */
isReadWriteOnly(String filepath, ITestDevice device)189     public static boolean isReadWriteOnly(String filepath, ITestDevice device)
190             throws DeviceNotAvailableException {
191         String permissionBits = getPermission(filepath, device);
192         return (hasPermission(FilePermission.READ, permissionBits)
193                 && hasPermission(FilePermission.WRITE, permissionBits)
194                 && !hasPermission(FilePermission.EXECUTE, permissionBits));
195     }
196 }
197