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 android.car.builtin.os; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.os.Binder; 23 import android.os.IBinder; 24 import android.os.Parcel; 25 import android.os.ParcelFileDescriptor; 26 import android.os.RemoteCallbackList; 27 import android.os.RemoteException; 28 import android.os.ResultReceiver; 29 import android.os.ShellCallback; 30 31 import com.android.internal.util.FastPrintWriter; 32 33 import libcore.io.IoUtils; 34 35 import java.io.FileDescriptor; 36 import java.io.FileInputStream; 37 import java.io.FileOutputStream; 38 import java.io.IOException; 39 import java.io.PrintWriter; 40 41 /** 42 * Helper for Binder related usage 43 * 44 * @hide 45 */ 46 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 47 public final class BinderHelper { 48 49 /** Dumps given {@link RemoteCallbackList} for debugging. */ dumpRemoteCallbackList(@onNull RemoteCallbackList<?> list, @NonNull PrintWriter pw)50 public static void dumpRemoteCallbackList(@NonNull RemoteCallbackList<?> list, 51 @NonNull PrintWriter pw) { 52 list.dump(pw, /* prefix= */ ""); 53 } 54 BinderHelper()55 private BinderHelper() { 56 throw new UnsupportedOperationException("contains only static members"); 57 } 58 59 /** 60 * Listener for implementing shell command handling. Should be used with 61 * {@link #onTransactForCmd(int, Parcel, Parcel, int, ShellCommandListener)}. 62 */ 63 public interface ShellCommandListener { 64 /** 65 * Implements shell command 66 * @param in input file 67 * @param out output file 68 * @param err error output 69 * @param args args passed with the command 70 * 71 * @return linux error code for the binder call. {@code 0} means ok. 72 */ onShellCommand(@onNull FileDescriptor in, @NonNull FileDescriptor out, @NonNull FileDescriptor err, @NonNull String[] args)73 int onShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out, 74 @NonNull FileDescriptor err, @NonNull String[] args); 75 } 76 77 /** 78 * Handles {@link Binder#onTransact(int, Parcel, Parcel, int)} for shell command. 79 * 80 * <p>This is different from the default {@link Binder#onTransact(int, Parcel, Parcel, int)} 81 * in that this does not check shell UID so that test apps not having shell UID can use it. Note 82 * that underlying command still should do necessary permission checks so that only apps with 83 * right permission can run that command. 84 * 85 * @param code Binder call code 86 * @param data Input {@code Parcel} 87 * @param reply Reply {@code Parcel} 88 * @param flags Binder de-serialization flags 89 * @param cmdListener Listener to implement the command. 90 * 91 * @return {@code true} if the transaction was handled (=if it is dump or cmd call). 92 * 93 * @throws RemoteException for binder call failure 94 */ onTransactForCmd(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags, @NonNull ShellCommandListener cmdListener)95 public static boolean onTransactForCmd(int code, @NonNull Parcel data, 96 @Nullable Parcel reply, int flags, @NonNull ShellCommandListener cmdListener) 97 throws RemoteException { 98 if (code == IBinder.SHELL_COMMAND_TRANSACTION) { 99 ParcelFileDescriptor in = data.readFileDescriptor(); 100 ParcelFileDescriptor out = data.readFileDescriptor(); 101 ParcelFileDescriptor err = data.readFileDescriptor(); 102 String[] args = data.readStringArray(); 103 // not used but should read from Parcel to get the next one. 104 ShellCallback.CREATOR.createFromParcel(data); 105 ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data); 106 if (args == null) { 107 args = new String[0]; 108 } 109 110 FileDescriptor errFd; 111 if (err == null) { 112 // if no err, use out for err 113 errFd = out.getFileDescriptor(); 114 } else { 115 errFd = err.getFileDescriptor(); 116 } 117 FileInputStream inputStream = null; 118 try { 119 FileDescriptor inFd; 120 if (in == null) { 121 inputStream = new FileInputStream("/dev/null"); 122 inFd = inputStream.getFD(); 123 } else { 124 inFd = in.getFileDescriptor(); 125 } 126 if (out != null) { 127 int r = cmdListener.onShellCommand(inFd, out.getFileDescriptor(), errFd, args); 128 if (resultReceiver != null) { 129 resultReceiver.send(r, null); 130 } 131 } 132 } catch (Exception e) { 133 sendFailureToCaller(errFd, resultReceiver, 134 "Cannot handle command with error:" + e.getMessage()); 135 } finally { 136 try { 137 if (inputStream != null) { 138 inputStream.close(); 139 } 140 } catch (IOException e) { 141 sendFailureToCaller(errFd, resultReceiver, 142 "I/O error:" + e.getMessage()); 143 // Still continue and complete the command (return true} 144 } 145 IoUtils.closeQuietly(in); 146 IoUtils.closeQuietly(out); 147 IoUtils.closeQuietly(err); 148 if (reply != null) { 149 reply.writeNoException(); 150 } 151 } 152 return true; 153 } 154 return false; 155 } 156 sendFailureToCaller(FileDescriptor errFd, ResultReceiver receiver, String msg)157 private static void sendFailureToCaller(FileDescriptor errFd, ResultReceiver receiver, 158 String msg) { 159 try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(errFd))) { 160 pw.println(msg); 161 pw.flush(); 162 } 163 receiver.send(/* resultCode= */ -1, /* resultData= */ null); 164 } 165 } 166