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.Collections; 41 import java.util.List; 42 import java.util.concurrent.TimeoutException; 43 import java.util.concurrent.atomic.AtomicInteger; 44 45 /** 46 * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds 47 * without a transaction. 48 **/ 49 class IdmapDaemon { 50 // The amount of time in milliseconds to wait after a transaction to the idmap service is made 51 // before stopping the service. 52 private static final int SERVICE_TIMEOUT_MS = 10000; 53 54 // The device may enter CPU sleep while waiting for the service startup, and in that mode 55 // the uptime doesn't increment. Thus, we need to have two timeouts: a smaller one for the 56 // uptime and a longer one for the wall time in case when the device never advances the uptime, 57 // so the watchdog won't get triggered. 58 59 // The amount of uptime in milliseconds to wait when attempting to connect to idmap service. 60 private static final int SERVICE_CONNECT_UPTIME_TIMEOUT_MS = 5000; 61 // The amount of wall time in milliseconds to wait. 62 private static final int SERVICE_CONNECT_WALLTIME_TIMEOUT_MS = 30000; 63 private static final int SERVICE_CONNECT_INTERVAL_SLEEP_MS = 5; 64 65 private static final String IDMAP_DAEMON = "idmap2d"; 66 67 private static IdmapDaemon sInstance; 68 private volatile IIdmap2 mService; 69 private final AtomicInteger mOpenedCount = new AtomicInteger(); 70 private final Object mIdmapToken = new Object(); 71 72 /** 73 * An {@link AutoCloseable} connection to the idmap service. When the connection is closed or 74 * finalized, the idmap service will be stopped after a period of time unless another connection 75 * to the service is open. 76 **/ 77 private class Connection implements AutoCloseable { 78 @Nullable 79 private final IIdmap2 mIdmap2; 80 private boolean mOpened = true; 81 Connection(IIdmap2 idmap2)82 private Connection(IIdmap2 idmap2) { 83 synchronized (mIdmapToken) { 84 mOpenedCount.incrementAndGet(); 85 mIdmap2 = idmap2; 86 } 87 } 88 89 @Override close()90 public void close() { 91 synchronized (mIdmapToken) { 92 if (!mOpened) { 93 return; 94 } 95 96 mOpened = false; 97 if (mOpenedCount.decrementAndGet() != 0) { 98 // Only post the callback to stop the service if the service does not have an 99 // open connection. 100 return; 101 } 102 103 FgThread.getHandler().postDelayed(() -> { 104 synchronized (mIdmapToken) { 105 // Only stop the service if the service does not have an open connection. 106 if (mService == null || mOpenedCount.get() != 0) { 107 return; 108 } 109 110 stopIdmapService(); 111 mService = null; 112 } 113 }, mIdmapToken, SERVICE_TIMEOUT_MS); 114 } 115 } 116 117 @Nullable getIdmap2()118 public IIdmap2 getIdmap2() { 119 return mIdmap2; 120 } 121 } 122 getInstance()123 static IdmapDaemon getInstance() { 124 if (sInstance == null) { 125 sInstance = new IdmapDaemon(); 126 } 127 return sInstance; 128 } 129 createIdmap(@onNull String targetPath, @NonNull String overlayPath, @Nullable String overlayName, int policies, boolean enforce, int userId)130 String createIdmap(@NonNull String targetPath, @NonNull String overlayPath, 131 @Nullable String overlayName, int policies, boolean enforce, int userId) 132 throws TimeoutException, RemoteException { 133 try (Connection c = connect()) { 134 final IIdmap2 idmap2 = c.getIdmap2(); 135 if (idmap2 == null) { 136 Slog.w(TAG, "idmap2d service is not ready for createIdmap(\"" + targetPath 137 + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " 138 + enforce + ", " + userId + ")"); 139 return null; 140 } 141 142 return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), 143 policies, enforce, userId); 144 } 145 } 146 removeIdmap(String overlayPath, int userId)147 boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException { 148 try (Connection c = connect()) { 149 final IIdmap2 idmap2 = c.getIdmap2(); 150 if (idmap2 == null) { 151 Slog.w(TAG, "idmap2d service is not ready for removeIdmap(\"" + overlayPath 152 + "\", " + userId + ")"); 153 return false; 154 } 155 156 return idmap2.removeIdmap(overlayPath, userId); 157 } 158 } 159 verifyIdmap(@onNull String targetPath, @NonNull String overlayPath, @Nullable String overlayName, int policies, boolean enforce, int userId)160 boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath, 161 @Nullable String overlayName, int policies, boolean enforce, int userId) 162 throws Exception { 163 try (Connection c = connect()) { 164 final IIdmap2 idmap2 = c.getIdmap2(); 165 if (idmap2 == null) { 166 Slog.w(TAG, "idmap2d service is not ready for verifyIdmap(\"" + targetPath 167 + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " 168 + enforce + ", " + userId + ")"); 169 return false; 170 } 171 172 return idmap2.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), 173 policies, enforce, userId); 174 } 175 } 176 idmapExists(String overlayPath, int userId)177 boolean idmapExists(String overlayPath, int userId) { 178 try (Connection c = connect()) { 179 final IIdmap2 idmap2 = c.getIdmap2(); 180 if (idmap2 == null) { 181 Slog.w(TAG, "idmap2d service is not ready for idmapExists(\"" + overlayPath 182 + "\", " + userId + ")"); 183 return false; 184 } 185 186 return new File(idmap2.getIdmapPath(overlayPath, userId)).isFile(); 187 } catch (Exception e) { 188 Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e); 189 return false; 190 } 191 } 192 createFabricatedOverlay(@onNull FabricatedOverlayInternal overlay)193 FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) { 194 try (Connection c = connect()) { 195 final IIdmap2 idmap2 = c.getIdmap2(); 196 if (idmap2 == null) { 197 Slog.w(TAG, "idmap2d service is not ready for createFabricatedOverlay()"); 198 return null; 199 } 200 201 return idmap2.createFabricatedOverlay(overlay); 202 } catch (Exception e) { 203 Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e); 204 return null; 205 } 206 } 207 deleteFabricatedOverlay(@onNull String path)208 boolean deleteFabricatedOverlay(@NonNull String path) { 209 try (Connection c = connect()) { 210 final IIdmap2 idmap2 = c.getIdmap2(); 211 if (idmap2 == null) { 212 Slog.w(TAG, "idmap2d service is not ready for deleteFabricatedOverlay(\"" + path 213 + "\")"); 214 return false; 215 } 216 217 return idmap2.deleteFabricatedOverlay(path); 218 } catch (Exception e) { 219 Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e); 220 return false; 221 } 222 } 223 getFabricatedOverlayInfos()224 synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() { 225 final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>(); 226 Connection c = null; 227 int iteratorId = -1; 228 try { 229 c = connect(); 230 final IIdmap2 service = c.getIdmap2(); 231 if (service == null) { 232 Slog.w(TAG, "idmap2d service is not ready for getFabricatedOverlayInfos()"); 233 return Collections.emptyList(); 234 } 235 236 iteratorId = service.acquireFabricatedOverlayIterator(); 237 List<FabricatedOverlayInfo> infos; 238 while (!(infos = service.nextFabricatedOverlayInfos(iteratorId)).isEmpty()) { 239 allInfos.addAll(infos); 240 } 241 return allInfos; 242 } catch (Exception e) { 243 Slog.wtf(TAG, "failed to get all fabricated overlays", e); 244 } finally { 245 try { 246 if (c.getIdmap2() != null && iteratorId != -1) { 247 c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId); 248 } 249 } catch (RemoteException e) { 250 // ignore 251 } 252 c.close(); 253 } 254 return allInfos; 255 } 256 dumpIdmap(@onNull String overlayPath)257 String dumpIdmap(@NonNull String overlayPath) { 258 try (Connection c = connect()) { 259 final IIdmap2 service = c.getIdmap2(); 260 if (service == null) { 261 final String dumpText = "idmap2d service is not ready for dumpIdmap()"; 262 Slog.w(TAG, dumpText); 263 return dumpText; 264 } 265 String dump = service.dumpIdmap(overlayPath); 266 return TextUtils.nullIfEmpty(dump); 267 } catch (Exception e) { 268 Slog.wtf(TAG, "failed to dump idmap", e); 269 return null; 270 } 271 } 272 273 @Nullable getIdmapService()274 private IBinder getIdmapService() throws TimeoutException, RemoteException { 275 try { 276 SystemService.start(IDMAP_DAEMON); 277 } catch (RuntimeException e) { 278 Slog.wtf(TAG, "Failed to enable idmap2 daemon", e); 279 if (e.getMessage().contains("failed to set system property")) { 280 return null; 281 } 282 } 283 284 long uptimeMillis = SystemClock.uptimeMillis(); 285 final long endUptimeMillis = uptimeMillis + SERVICE_CONNECT_UPTIME_TIMEOUT_MS; 286 long walltimeMillis = SystemClock.elapsedRealtime(); 287 final long endWalltimeMillis = walltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS; 288 289 do { 290 final IBinder binder = ServiceManager.getService(IDMAP_SERVICE); 291 if (binder != null) { 292 binder.linkToDeath( 293 () -> Slog.w(TAG, 294 TextUtils.formatSimple("service '%s' died", IDMAP_SERVICE)), 0); 295 return binder; 296 } 297 SystemClock.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS); 298 } while ((uptimeMillis = SystemClock.uptimeMillis()) <= endUptimeMillis 299 && (walltimeMillis = SystemClock.elapsedRealtime()) <= endWalltimeMillis); 300 301 throw new TimeoutException( 302 TextUtils.formatSimple("Failed to connect to '%s' in %d/%d ms (spent %d/%d ms)", 303 IDMAP_SERVICE, SERVICE_CONNECT_UPTIME_TIMEOUT_MS, 304 SERVICE_CONNECT_WALLTIME_TIMEOUT_MS, 305 uptimeMillis - endUptimeMillis + SERVICE_CONNECT_UPTIME_TIMEOUT_MS, 306 walltimeMillis - endWalltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS)); 307 } 308 stopIdmapService()309 private static void stopIdmapService() { 310 try { 311 SystemService.stop(IDMAP_DAEMON); 312 } catch (RuntimeException e) { 313 // If the idmap daemon cannot be disabled for some reason, it is okay 314 // since we already finished invoking idmap. 315 Slog.w(TAG, "Failed to disable idmap2 daemon", e); 316 } 317 } 318 319 @NonNull connect()320 private Connection connect() throws TimeoutException, RemoteException { 321 synchronized (mIdmapToken) { 322 FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken); 323 if (mService != null) { 324 // Not enough time has passed to stop the idmap service. Reuse the existing 325 // interface. 326 return new Connection(mService); 327 } 328 329 IBinder binder = getIdmapService(); 330 if (binder == null) { 331 return new Connection(null); 332 } 333 334 mService = IIdmap2.Stub.asInterface(binder); 335 return new Connection(mService); 336 } 337 } 338 } 339