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