Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020 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 | package com.android.server.blob; |
| 17 | |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 18 | import static android.provider.DeviceConfig.NAMESPACE_BLOBSTORE; |
| 19 | import static android.text.format.Formatter.FLAG_IEC_UNITS; |
| 20 | import static android.text.format.Formatter.formatFileSize; |
| 21 | import static android.util.TimeUtils.formatDuration; |
| 22 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 23 | import android.annotation.NonNull; |
| 24 | import android.annotation.Nullable; |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 25 | import android.content.Context; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 26 | import android.os.Environment; |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 27 | import android.provider.DeviceConfig; |
| 28 | import android.provider.DeviceConfig.Properties; |
| 29 | import android.util.DataUnit; |
Sudheer Shanka | e53e1ed | 2020-02-03 17:15:24 -0800 | [diff] [blame] | 30 | import android.util.Log; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 31 | import android.util.Slog; |
Sudheer Shanka | c0fd5fa | 2020-03-15 23:31:37 -0700 | [diff] [blame] | 32 | import android.util.TimeUtils; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 33 | |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 34 | import com.android.internal.util.IndentingPrintWriter; |
| 35 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 36 | import java.io.File; |
Sudheer Shanka | ae53d11 | 2020-01-31 14:20:53 -0800 | [diff] [blame] | 37 | import java.util.concurrent.TimeUnit; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 38 | |
| 39 | class BlobStoreConfig { |
| 40 | public static final String TAG = "BlobStore"; |
Sudheer Shanka | e53e1ed | 2020-02-03 17:15:24 -0800 | [diff] [blame] | 41 | public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 42 | |
Sudheer Shanka | 1406bc8 | 2020-02-03 12:27:24 -0800 | [diff] [blame] | 43 | // Initial version. |
| 44 | public static final int XML_VERSION_INIT = 1; |
| 45 | // Added a string variant of lease description. |
| 46 | public static final int XML_VERSION_ADD_STRING_DESC = 2; |
Sudheer Shanka | 1b6b825 | 2020-03-04 22:19:21 -0800 | [diff] [blame] | 47 | public static final int XML_VERSION_ADD_DESC_RES_NAME = 3; |
Sudheer Shanka | 1406bc8 | 2020-02-03 12:27:24 -0800 | [diff] [blame] | 48 | |
Sudheer Shanka | 1b6b825 | 2020-03-04 22:19:21 -0800 | [diff] [blame] | 49 | public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_DESC_RES_NAME; |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 50 | |
Sudheer Shanka | 9f2296a | 2020-01-25 23:22:34 -0800 | [diff] [blame] | 51 | private static final String ROOT_DIR_NAME = "blobstore"; |
| 52 | private static final String BLOBS_DIR_NAME = "blobs"; |
| 53 | private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml"; |
| 54 | private static final String BLOBS_INDEX_FILE_NAME = "blobs_index.xml"; |
| 55 | |
Sudheer Shanka | ae53d11 | 2020-01-31 14:20:53 -0800 | [diff] [blame] | 56 | /** |
| 57 | * Job Id for idle maintenance job ({@link BlobStoreIdleJobService}). |
| 58 | */ |
| 59 | public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L |
| 60 | /** |
| 61 | * Max time period (in millis) between each idle maintenance job run. |
| 62 | */ |
| 63 | public static final long IDLE_JOB_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); |
| 64 | |
| 65 | /** |
| 66 | * Timeout in millis after which sessions with no updates will be deleted. |
| 67 | */ |
| 68 | public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7); |
| 69 | |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 70 | public static class DeviceConfigProperties { |
| 71 | /** |
| 72 | * Denotes how low the limit for the amount of data, that an app will be allowed to acquire |
| 73 | * a lease on, can be. |
| 74 | */ |
| 75 | public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR = |
| 76 | "total_bytes_per_app_limit_floor"; |
| 77 | public static final long DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR = |
| 78 | DataUnit.MEBIBYTES.toBytes(300); // 300 MiB |
| 79 | public static long TOTAL_BYTES_PER_APP_LIMIT_FLOOR = |
| 80 | DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR; |
| 81 | |
| 82 | /** |
| 83 | * Denotes the maximum amount of data an app can acquire a lease on, in terms of fraction |
| 84 | * of total disk space. |
| 85 | */ |
| 86 | public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION = |
| 87 | "total_bytes_per_app_limit_fraction"; |
| 88 | public static final float DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION = 0.01f; |
| 89 | public static float TOTAL_BYTES_PER_APP_LIMIT_FRACTION = |
| 90 | DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION; |
| 91 | |
Sudheer Shanka | c0fd5fa | 2020-03-15 23:31:37 -0700 | [diff] [blame] | 92 | /** |
| 93 | * Denotes the duration from the time a blob is committed that we wait for a lease to |
| 94 | * be acquired before deciding to delete the blob for having no leases. |
| 95 | */ |
| 96 | public static final String KEY_LEASE_ACQUISITION_WAIT_DURATION_MS = |
| 97 | "lease_acquisition_wait_time_ms"; |
| 98 | public static final long DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS = |
| 99 | TimeUnit.HOURS.toMillis(6); |
| 100 | public static long LEASE_ACQUISITION_WAIT_DURATION_MS = |
| 101 | DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS; |
| 102 | |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 103 | static void refresh(Properties properties) { |
| 104 | if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) { |
| 105 | return; |
| 106 | } |
| 107 | properties.getKeyset().forEach(key -> { |
| 108 | switch (key) { |
| 109 | case KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR: |
| 110 | TOTAL_BYTES_PER_APP_LIMIT_FLOOR = properties.getLong(key, |
| 111 | DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR); |
| 112 | break; |
| 113 | case KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION: |
| 114 | TOTAL_BYTES_PER_APP_LIMIT_FRACTION = properties.getFloat(key, |
| 115 | DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION); |
| 116 | break; |
Sudheer Shanka | c0fd5fa | 2020-03-15 23:31:37 -0700 | [diff] [blame] | 117 | case KEY_LEASE_ACQUISITION_WAIT_DURATION_MS: |
| 118 | LEASE_ACQUISITION_WAIT_DURATION_MS = properties.getLong(key, |
| 119 | DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS); |
| 120 | break; |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 121 | default: |
| 122 | Slog.wtf(TAG, "Unknown key in device config properties: " + key); |
| 123 | } |
| 124 | }); |
| 125 | } |
| 126 | |
| 127 | static void dump(IndentingPrintWriter fout, Context context) { |
| 128 | final String dumpFormat = "%s: [cur: %s, def: %s]"; |
| 129 | fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR, |
| 130 | formatFileSize(context, TOTAL_BYTES_PER_APP_LIMIT_FLOOR, FLAG_IEC_UNITS), |
| 131 | formatFileSize(context, DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR, |
| 132 | FLAG_IEC_UNITS))); |
| 133 | fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION, |
| 134 | TOTAL_BYTES_PER_APP_LIMIT_FRACTION, |
| 135 | DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION)); |
Sudheer Shanka | c0fd5fa | 2020-03-15 23:31:37 -0700 | [diff] [blame] | 136 | fout.println(String.format(dumpFormat, KEY_LEASE_ACQUISITION_WAIT_DURATION_MS, |
| 137 | TimeUtils.formatDuration(LEASE_ACQUISITION_WAIT_DURATION_MS), |
| 138 | TimeUtils.formatDuration(DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS))); |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 139 | } |
| 140 | } |
| 141 | |
| 142 | public static void initialize(Context context) { |
| 143 | DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_BLOBSTORE, |
| 144 | context.getMainExecutor(), |
| 145 | properties -> DeviceConfigProperties.refresh(properties)); |
| 146 | DeviceConfigProperties.refresh(DeviceConfig.getProperties(NAMESPACE_BLOBSTORE)); |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Returns the maximum amount of data that an app can acquire a lease on. |
| 151 | */ |
| 152 | public static long getAppDataBytesLimit() { |
| 153 | final long totalBytesLimit = (long) (Environment.getDataSystemDirectory().getTotalSpace() |
| 154 | * DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FRACTION); |
| 155 | return Math.max(DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FLOOR, totalBytesLimit); |
| 156 | } |
| 157 | |
Sudheer Shanka | c0fd5fa | 2020-03-15 23:31:37 -0700 | [diff] [blame] | 158 | /** |
| 159 | * Returns whether the wait time for lease acquisition for a blob has elapsed. |
| 160 | */ |
| 161 | public static boolean hasLeaseWaitTimeElapsed(long commitTimeMs) { |
| 162 | return commitTimeMs + DeviceConfigProperties.LEASE_ACQUISITION_WAIT_DURATION_MS |
| 163 | < System.currentTimeMillis(); |
| 164 | } |
| 165 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 166 | @Nullable |
| 167 | public static File prepareBlobFile(long sessionId) { |
| 168 | final File blobsDir = prepareBlobsDir(); |
| 169 | return blobsDir == null ? null : getBlobFile(blobsDir, sessionId); |
| 170 | } |
| 171 | |
| 172 | @NonNull |
| 173 | public static File getBlobFile(long sessionId) { |
| 174 | return getBlobFile(getBlobsDir(), sessionId); |
| 175 | } |
| 176 | |
| 177 | @NonNull |
| 178 | private static File getBlobFile(File blobsDir, long sessionId) { |
| 179 | return new File(blobsDir, String.valueOf(sessionId)); |
| 180 | } |
| 181 | |
| 182 | @Nullable |
| 183 | public static File prepareBlobsDir() { |
| 184 | final File blobsDir = getBlobsDir(prepareBlobStoreRootDir()); |
| 185 | if (!blobsDir.exists() && !blobsDir.mkdir()) { |
| 186 | Slog.e(TAG, "Failed to mkdir(): " + blobsDir); |
| 187 | return null; |
| 188 | } |
| 189 | return blobsDir; |
| 190 | } |
| 191 | |
| 192 | @NonNull |
| 193 | public static File getBlobsDir() { |
| 194 | return getBlobsDir(getBlobStoreRootDir()); |
| 195 | } |
| 196 | |
| 197 | @NonNull |
| 198 | private static File getBlobsDir(File blobsRootDir) { |
Sudheer Shanka | 9f2296a | 2020-01-25 23:22:34 -0800 | [diff] [blame] | 199 | return new File(blobsRootDir, BLOBS_DIR_NAME); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 200 | } |
| 201 | |
| 202 | @Nullable |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 203 | public static File prepareSessionIndexFile() { |
| 204 | final File blobStoreRootDir = prepareBlobStoreRootDir(); |
| 205 | if (blobStoreRootDir == null) { |
| 206 | return null; |
| 207 | } |
Sudheer Shanka | 9f2296a | 2020-01-25 23:22:34 -0800 | [diff] [blame] | 208 | return new File(blobStoreRootDir, SESSIONS_INDEX_FILE_NAME); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | @Nullable |
| 212 | public static File prepareBlobsIndexFile() { |
| 213 | final File blobsStoreRootDir = prepareBlobStoreRootDir(); |
| 214 | if (blobsStoreRootDir == null) { |
| 215 | return null; |
| 216 | } |
Sudheer Shanka | 9f2296a | 2020-01-25 23:22:34 -0800 | [diff] [blame] | 217 | return new File(blobsStoreRootDir, BLOBS_INDEX_FILE_NAME); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | @Nullable |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 221 | public static File prepareBlobStoreRootDir() { |
| 222 | final File blobStoreRootDir = getBlobStoreRootDir(); |
| 223 | if (!blobStoreRootDir.exists() && !blobStoreRootDir.mkdir()) { |
| 224 | Slog.e(TAG, "Failed to mkdir(): " + blobStoreRootDir); |
| 225 | return null; |
| 226 | } |
| 227 | return blobStoreRootDir; |
| 228 | } |
| 229 | |
| 230 | @NonNull |
| 231 | public static File getBlobStoreRootDir() { |
Sudheer Shanka | 9f2296a | 2020-01-25 23:22:34 -0800 | [diff] [blame] | 232 | return new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 233 | } |
Sudheer Shanka | 364364b | 2020-02-19 17:56:09 -0800 | [diff] [blame] | 234 | |
| 235 | public static void dump(IndentingPrintWriter fout, Context context) { |
| 236 | fout.println("XML current version: " + XML_VERSION_CURRENT); |
| 237 | |
| 238 | fout.println("Idle job ID: " + IDLE_JOB_ID); |
| 239 | fout.println("Idle job period: " + formatDuration(IDLE_JOB_PERIOD_MILLIS)); |
| 240 | |
| 241 | fout.println("Session expiry timeout: " + formatDuration(SESSION_EXPIRY_TIMEOUT_MILLIS)); |
| 242 | |
| 243 | fout.println("Total bytes per app limit: " + formatFileSize(context, |
| 244 | getAppDataBytesLimit(), FLAG_IEC_UNITS)); |
| 245 | |
| 246 | fout.println("Device config properties:"); |
| 247 | fout.increaseIndent(); |
| 248 | DeviceConfigProperties.dump(fout, context); |
| 249 | fout.decreaseIndent(); |
| 250 | } |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 251 | } |