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