1 /*
2  * Copyright (C) 2019 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.server.om;
18 
19 import static android.content.Context.IDMAP_SERVICE;
20 
21 import static com.android.server.om.OverlayManagerService.TAG;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.os.FabricatedOverlayInfo;
26 import android.os.FabricatedOverlayInternal;
27 import android.os.IBinder;
28 import android.os.IIdmap2;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.SystemClock;
32 import android.os.SystemService;
33 import android.text.TextUtils;
34 import android.util.Slog;
35 
36 import com.android.server.FgThread;
37 
38 import java.io.File;
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.concurrent.TimeoutException;
42 import java.util.concurrent.atomic.AtomicInteger;
43 
44 /**
45  * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10
46  * seconds without a transaction.
47  **/
48 class IdmapDaemon {
49     // The amount of time in milliseconds to wait after a transaction to the idmap service is made
50     // before stopping the service.
51     private static final int SERVICE_TIMEOUT_MS = 10000;
52 
53     // The amount of time in milliseconds to wait when attempting to connect to idmap service.
54     private static final int SERVICE_CONNECT_TIMEOUT_MS = 5000;
55     private static final int SERVICE_CONNECT_INTERVAL_SLEEP_MS = 5;
56 
57     private static final String IDMAP_DAEMON = "idmap2d";
58 
59     private static IdmapDaemon sInstance;
60     private volatile IIdmap2 mService;
61     private final AtomicInteger mOpenedCount = new AtomicInteger();
62     private final Object mIdmapToken = new Object();
63 
64     /**
65      * An {@link AutoCloseable} connection to the idmap service. When the connection is closed or
66      * finalized, the idmap service will be stopped after a period of time unless another connection
67      * to the service is open.
68      **/
69     private class Connection implements AutoCloseable {
70         private boolean mOpened = true;
71 
Connection()72         private Connection() {
73             synchronized (mIdmapToken) {
74                 mOpenedCount.incrementAndGet();
75             }
76         }
77 
78         @Override
close()79         public void close() {
80             synchronized (mIdmapToken) {
81                 if (!mOpened) {
82                     return;
83                 }
84 
85                 mOpened = false;
86                 if (mOpenedCount.decrementAndGet() != 0) {
87                     // Only post the callback to stop the service if the service does not have an
88                     // open connection.
89                     return;
90                 }
91 
92                 FgThread.getHandler().postDelayed(() -> {
93                     synchronized (mIdmapToken) {
94                         // Only stop the service if the service does not have an open connection.
95                         if (mService == null || mOpenedCount.get() != 0) {
96                             return;
97                         }
98 
99                         stopIdmapService();
100                         mService = null;
101                     }
102                 }, mIdmapToken, SERVICE_TIMEOUT_MS);
103             }
104         }
105     }
106 
getInstance()107     static IdmapDaemon getInstance() {
108         if (sInstance == null) {
109             sInstance = new IdmapDaemon();
110         }
111         return sInstance;
112     }
113 
createIdmap(@onNull String targetPath, @NonNull String overlayPath, @Nullable String overlayName, int policies, boolean enforce, int userId)114     String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
115             @Nullable String overlayName, int policies, boolean enforce, int userId)
116             throws TimeoutException, RemoteException {
117         try (Connection c = connect()) {
118             return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
119                     policies, enforce, userId);
120         }
121     }
122 
removeIdmap(String overlayPath, int userId)123     boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException {
124         try (Connection c = connect()) {
125             return mService.removeIdmap(overlayPath, userId);
126         }
127     }
128 
verifyIdmap(@onNull String targetPath, @NonNull String overlayPath, @Nullable String overlayName, int policies, boolean enforce, int userId)129     boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
130             @Nullable String overlayName, int policies, boolean enforce, int userId)
131             throws Exception {
132         try (Connection c = connect()) {
133             return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
134                     policies, enforce, userId);
135         }
136     }
137 
idmapExists(String overlayPath, int userId)138     boolean idmapExists(String overlayPath, int userId) {
139         try (Connection c = connect()) {
140             return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
141         } catch (Exception e) {
142             Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
143             return false;
144         }
145     }
146 
createFabricatedOverlay(@onNull FabricatedOverlayInternal overlay)147     FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
148         try (Connection c = connect()) {
149             return mService.createFabricatedOverlay(overlay);
150         } catch (Exception e) {
151             Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
152             return null;
153         }
154     }
155 
deleteFabricatedOverlay(@onNull String path)156     boolean deleteFabricatedOverlay(@NonNull String path) {
157         try (Connection c = connect()) {
158             return mService.deleteFabricatedOverlay(path);
159         } catch (Exception e) {
160             Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
161             return false;
162         }
163     }
164 
getFabricatedOverlayInfos()165     synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
166         final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>();
167         try (Connection c = connect()) {
168             mService.acquireFabricatedOverlayIterator();
169             List<FabricatedOverlayInfo> infos;
170             while (!(infos = mService.nextFabricatedOverlayInfos()).isEmpty()) {
171                 allInfos.addAll(infos);
172             }
173             return allInfos;
174         } catch (Exception e) {
175             Slog.wtf(TAG, "failed to get all fabricated overlays", e);
176         } finally {
177             try {
178                 mService.releaseFabricatedOverlayIterator();
179             } catch (RemoteException e) {
180                 // ignore
181             }
182         }
183         return allInfos;
184     }
185 
dumpIdmap(@onNull String overlayPath)186     String dumpIdmap(@NonNull String overlayPath) {
187         try (Connection c = connect()) {
188             String dump = mService.dumpIdmap(overlayPath);
189             return TextUtils.nullIfEmpty(dump);
190         } catch (Exception e) {
191             Slog.wtf(TAG, "failed to dump idmap", e);
192             return null;
193         }
194     }
195 
getIdmapService()196     private IBinder getIdmapService() throws TimeoutException, RemoteException {
197         SystemService.start(IDMAP_DAEMON);
198 
199         final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS;
200         while (SystemClock.elapsedRealtime() <= endMillis) {
201             final IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
202             if (binder != null) {
203                 binder.linkToDeath(
204                         () -> Slog.w(TAG, String.format("service '%s' died", IDMAP_SERVICE)), 0);
205                 return binder;
206             }
207 
208             try {
209                 Thread.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS);
210             } catch (InterruptedException ignored) {
211             }
212         }
213 
214         throw new TimeoutException(
215             String.format("Failed to connect to '%s' in %d milliseconds", IDMAP_SERVICE,
216                     SERVICE_CONNECT_TIMEOUT_MS));
217     }
218 
stopIdmapService()219     private static void stopIdmapService() {
220         try {
221             SystemService.stop(IDMAP_DAEMON);
222         } catch (RuntimeException e) {
223             // If the idmap daemon cannot be disabled for some reason, it is okay
224             // since we already finished invoking idmap.
225             Slog.w(TAG, "Failed to disable idmap2 daemon", e);
226         }
227     }
228 
connect()229     private Connection connect() throws TimeoutException, RemoteException {
230         synchronized (mIdmapToken) {
231             FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken);
232             if (mService != null) {
233                 // Not enough time has passed to stop the idmap service. Reuse the existing
234                 // interface.
235                 return new Connection();
236             }
237 
238             mService = IIdmap2.Stub.asInterface(getIdmapService());
239             return new Connection();
240         }
241     }
242 }
243