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