blob: d780d5dab14db5419349340809b0e38880de657e [file] [log] [blame]
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001/*
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 */
16package com.android.server.blob;
17
Sudheer Shanka364364b2020-02-19 17:56:09 -080018import static android.provider.DeviceConfig.NAMESPACE_BLOBSTORE;
19import static android.text.format.Formatter.FLAG_IEC_UNITS;
20import static android.text.format.Formatter.formatFileSize;
Sudheer Shanka364364b2020-02-19 17:56:09 -080021
Sudheer Shankaab1d4162020-01-07 10:37:50 -080022import android.annotation.NonNull;
23import android.annotation.Nullable;
Sudheer Shanka364364b2020-02-19 17:56:09 -080024import android.content.Context;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080025import android.os.Environment;
Sudheer Shanka364364b2020-02-19 17:56:09 -080026import android.provider.DeviceConfig;
27import android.provider.DeviceConfig.Properties;
28import android.util.DataUnit;
Sudheer Shankae53e1ed2020-02-03 17:15:24 -080029import android.util.Log;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080030import android.util.Slog;
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -070031import android.util.TimeUtils;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080032
Sudheer Shanka364364b2020-02-19 17:56:09 -080033import com.android.internal.util.IndentingPrintWriter;
34
Sudheer Shankaab1d4162020-01-07 10:37:50 -080035import java.io.File;
Sudheer Shankaae53d112020-01-31 14:20:53 -080036import java.util.concurrent.TimeUnit;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080037
38class BlobStoreConfig {
39 public static final String TAG = "BlobStore";
Sudheer Shankae53e1ed2020-02-03 17:15:24 -080040 public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
Sudheer Shankaab1d4162020-01-07 10:37:50 -080041
Sudheer Shanka1406bc82020-02-03 12:27:24 -080042 // Initial version.
43 public static final int XML_VERSION_INIT = 1;
44 // Added a string variant of lease description.
45 public static final int XML_VERSION_ADD_STRING_DESC = 2;
Sudheer Shanka1b6b8252020-03-04 22:19:21 -080046 public static final int XML_VERSION_ADD_DESC_RES_NAME = 3;
Sudheer Shankaaf3f8872020-03-24 11:26:23 -070047 public static final int XML_VERSION_ADD_COMMIT_TIME = 4;
Sudheer Shankab2a17b72020-05-28 01:14:10 -070048 public static final int XML_VERSION_ADD_SESSION_CREATION_TIME = 5;
Sudheer Shanka1406bc82020-02-03 12:27:24 -080049
Sudheer Shankab2a17b72020-05-28 01:14:10 -070050 public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080051
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -070052 public static final long INVALID_BLOB_ID = 0;
53 public static final long INVALID_BLOB_SIZE = 0;
54
Sudheer Shanka9f2296a2020-01-25 23:22:34 -080055 private static final String ROOT_DIR_NAME = "blobstore";
56 private static final String BLOBS_DIR_NAME = "blobs";
57 private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml";
58 private static final String BLOBS_INDEX_FILE_NAME = "blobs_index.xml";
59
Sudheer Shankaae53d112020-01-31 14:20:53 -080060 /**
61 * Job Id for idle maintenance job ({@link BlobStoreIdleJobService}).
62 */
63 public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L
Sudheer Shankaae53d112020-01-31 14:20:53 -080064
Sudheer Shanka364364b2020-02-19 17:56:09 -080065 public static class DeviceConfigProperties {
66 /**
Sudheer Shanka90ac4a62020-05-25 15:40:21 -070067 * Denotes the max time period (in millis) between each idle maintenance job run.
68 */
69 public static final String KEY_IDLE_JOB_PERIOD_MS = "idle_job_period_ms";
70 public static final long DEFAULT_IDLE_JOB_PERIOD_MS = TimeUnit.DAYS.toMillis(1);
71 public static long IDLE_JOB_PERIOD_MS = DEFAULT_IDLE_JOB_PERIOD_MS;
72
73 /**
74 * Denotes the timeout in millis after which sessions with no updates will be deleted.
75 */
76 public static final String KEY_SESSION_EXPIRY_TIMEOUT_MS =
77 "session_expiry_timeout_ms";
78 public static final long DEFAULT_SESSION_EXPIRY_TIMEOUT_MS = TimeUnit.DAYS.toMillis(7);
79 public static long SESSION_EXPIRY_TIMEOUT_MS = DEFAULT_SESSION_EXPIRY_TIMEOUT_MS;
80
81 /**
Sudheer Shanka364364b2020-02-19 17:56:09 -080082 * Denotes how low the limit for the amount of data, that an app will be allowed to acquire
83 * a lease on, can be.
84 */
85 public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
86 "total_bytes_per_app_limit_floor";
87 public static final long DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
88 DataUnit.MEBIBYTES.toBytes(300); // 300 MiB
89 public static long TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
90 DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR;
91
92 /**
93 * Denotes the maximum amount of data an app can acquire a lease on, in terms of fraction
94 * of total disk space.
95 */
96 public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
97 "total_bytes_per_app_limit_fraction";
98 public static final float DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION = 0.01f;
99 public static float TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
100 DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION;
101
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700102 /**
103 * Denotes the duration from the time a blob is committed that we wait for a lease to
104 * be acquired before deciding to delete the blob for having no leases.
105 */
106 public static final String KEY_LEASE_ACQUISITION_WAIT_DURATION_MS =
107 "lease_acquisition_wait_time_ms";
108 public static final long DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS =
109 TimeUnit.HOURS.toMillis(6);
110 public static long LEASE_ACQUISITION_WAIT_DURATION_MS =
111 DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS;
112
Sudheer Shankaaf3f8872020-03-24 11:26:23 -0700113 /**
114 * Denotes the duration from the time a blob is committed that any new commits of the same
115 * data blob from the same committer will be treated as if they occurred at the earlier
116 * commit time.
117 */
118 public static final String KEY_COMMIT_COOL_OFF_DURATION_MS =
119 "commit_cool_off_duration_ms";
120 public static final long DEFAULT_COMMIT_COOL_OFF_DURATION_MS =
121 TimeUnit.HOURS.toMillis(48);
122 public static long COMMIT_COOL_OFF_DURATION_MS =
123 DEFAULT_COMMIT_COOL_OFF_DURATION_MS;
124
Sudheer Shankad6f47942020-06-18 16:46:36 -0700125 /**
126 * Denotes whether to use RevocableFileDescriptor when apps try to read session/blob data.
127 */
128 public static final String KEY_USE_REVOCABLE_FD_FOR_READS =
129 "use_revocable_fd_for_reads";
130 public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = true;
131 public static boolean USE_REVOCABLE_FD_FOR_READS =
132 DEFAULT_USE_REVOCABLE_FD_FOR_READS;
133
Sudheer Shanka364364b2020-02-19 17:56:09 -0800134 static void refresh(Properties properties) {
135 if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
136 return;
137 }
138 properties.getKeyset().forEach(key -> {
139 switch (key) {
Sudheer Shanka90ac4a62020-05-25 15:40:21 -0700140 case KEY_IDLE_JOB_PERIOD_MS:
141 IDLE_JOB_PERIOD_MS = properties.getLong(key, DEFAULT_IDLE_JOB_PERIOD_MS);
142 break;
143 case KEY_SESSION_EXPIRY_TIMEOUT_MS:
144 SESSION_EXPIRY_TIMEOUT_MS = properties.getLong(key,
145 DEFAULT_SESSION_EXPIRY_TIMEOUT_MS);
146 break;
Sudheer Shanka364364b2020-02-19 17:56:09 -0800147 case KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR:
148 TOTAL_BYTES_PER_APP_LIMIT_FLOOR = properties.getLong(key,
149 DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR);
150 break;
151 case KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION:
152 TOTAL_BYTES_PER_APP_LIMIT_FRACTION = properties.getFloat(key,
153 DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
154 break;
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700155 case KEY_LEASE_ACQUISITION_WAIT_DURATION_MS:
156 LEASE_ACQUISITION_WAIT_DURATION_MS = properties.getLong(key,
157 DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS);
158 break;
Sudheer Shanka13d78642020-04-22 03:56:38 -0700159 case KEY_COMMIT_COOL_OFF_DURATION_MS:
160 COMMIT_COOL_OFF_DURATION_MS = properties.getLong(key,
161 DEFAULT_COMMIT_COOL_OFF_DURATION_MS);
162 break;
Sudheer Shankad6f47942020-06-18 16:46:36 -0700163 case KEY_USE_REVOCABLE_FD_FOR_READS:
164 USE_REVOCABLE_FD_FOR_READS = properties.getBoolean(key,
165 DEFAULT_USE_REVOCABLE_FD_FOR_READS);
166 break;
Sudheer Shanka364364b2020-02-19 17:56:09 -0800167 default:
168 Slog.wtf(TAG, "Unknown key in device config properties: " + key);
169 }
170 });
171 }
172
173 static void dump(IndentingPrintWriter fout, Context context) {
174 final String dumpFormat = "%s: [cur: %s, def: %s]";
Sudheer Shanka90ac4a62020-05-25 15:40:21 -0700175 fout.println(String.format(dumpFormat, KEY_IDLE_JOB_PERIOD_MS,
176 TimeUtils.formatDuration(IDLE_JOB_PERIOD_MS),
177 TimeUtils.formatDuration(DEFAULT_IDLE_JOB_PERIOD_MS)));
178 fout.println(String.format(dumpFormat, KEY_SESSION_EXPIRY_TIMEOUT_MS,
179 TimeUtils.formatDuration(SESSION_EXPIRY_TIMEOUT_MS),
180 TimeUtils.formatDuration(DEFAULT_SESSION_EXPIRY_TIMEOUT_MS)));
Sudheer Shanka364364b2020-02-19 17:56:09 -0800181 fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
182 formatFileSize(context, TOTAL_BYTES_PER_APP_LIMIT_FLOOR, FLAG_IEC_UNITS),
183 formatFileSize(context, DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
184 FLAG_IEC_UNITS)));
185 fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
186 TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
187 DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION));
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700188 fout.println(String.format(dumpFormat, KEY_LEASE_ACQUISITION_WAIT_DURATION_MS,
189 TimeUtils.formatDuration(LEASE_ACQUISITION_WAIT_DURATION_MS),
190 TimeUtils.formatDuration(DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS)));
Sudheer Shanka13d78642020-04-22 03:56:38 -0700191 fout.println(String.format(dumpFormat, KEY_COMMIT_COOL_OFF_DURATION_MS,
192 TimeUtils.formatDuration(COMMIT_COOL_OFF_DURATION_MS),
193 TimeUtils.formatDuration(DEFAULT_COMMIT_COOL_OFF_DURATION_MS)));
Sudheer Shankad6f47942020-06-18 16:46:36 -0700194 fout.println(String.format(dumpFormat, KEY_USE_REVOCABLE_FD_FOR_READS,
195 USE_REVOCABLE_FD_FOR_READS, DEFAULT_USE_REVOCABLE_FD_FOR_READS));
Sudheer Shanka364364b2020-02-19 17:56:09 -0800196 }
197 }
198
199 public static void initialize(Context context) {
200 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_BLOBSTORE,
201 context.getMainExecutor(),
202 properties -> DeviceConfigProperties.refresh(properties));
203 DeviceConfigProperties.refresh(DeviceConfig.getProperties(NAMESPACE_BLOBSTORE));
204 }
205
206 /**
Sudheer Shanka90ac4a62020-05-25 15:40:21 -0700207 * Returns the max time period (in millis) between each idle maintenance job run.
208 */
209 public static long getIdleJobPeriodMs() {
210 return DeviceConfigProperties.IDLE_JOB_PERIOD_MS;
211 }
212
213 /**
214 * Returns whether a session is expired or not. A session is considered expired if the session
215 * has not been modified in a while (i.e. SESSION_EXPIRY_TIMEOUT_MS).
216 */
217 public static boolean hasSessionExpired(long sessionLastModifiedMs) {
218 return sessionLastModifiedMs
219 < System.currentTimeMillis() - DeviceConfigProperties.SESSION_EXPIRY_TIMEOUT_MS;
220 }
221
222 /**
Sudheer Shanka364364b2020-02-19 17:56:09 -0800223 * Returns the maximum amount of data that an app can acquire a lease on.
224 */
225 public static long getAppDataBytesLimit() {
226 final long totalBytesLimit = (long) (Environment.getDataSystemDirectory().getTotalSpace()
227 * DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
228 return Math.max(DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FLOOR, totalBytesLimit);
229 }
230
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700231 /**
232 * Returns whether the wait time for lease acquisition for a blob has elapsed.
233 */
234 public static boolean hasLeaseWaitTimeElapsed(long commitTimeMs) {
235 return commitTimeMs + DeviceConfigProperties.LEASE_ACQUISITION_WAIT_DURATION_MS
236 < System.currentTimeMillis();
237 }
238
Sudheer Shankaaf3f8872020-03-24 11:26:23 -0700239 /**
240 * Returns an adjusted commit time depending on whether commit cool-off period has elapsed.
241 *
242 * If this is the initial commit or the earlier commit cool-off period has elapsed, then
243 * the new commit time is used. Otherwise, the earlier commit time is used.
244 */
245 public static long getAdjustedCommitTimeMs(long oldCommitTimeMs, long newCommitTimeMs) {
246 if (oldCommitTimeMs == 0 || hasCommitCoolOffPeriodElapsed(oldCommitTimeMs)) {
247 return newCommitTimeMs;
248 }
249 return oldCommitTimeMs;
250 }
251
252 /**
253 * Returns whether the commit cool-off period has elapsed.
254 */
255 private static boolean hasCommitCoolOffPeriodElapsed(long commitTimeMs) {
256 return commitTimeMs + DeviceConfigProperties.COMMIT_COOL_OFF_DURATION_MS
257 < System.currentTimeMillis();
258 }
259
Sudheer Shankad6f47942020-06-18 16:46:36 -0700260 /**
261 * Return whether to use RevocableFileDescriptor when apps try to read session/blob data.
262 */
263 public static boolean shouldUseRevocableFdForReads() {
264 return DeviceConfigProperties.USE_REVOCABLE_FD_FOR_READS;
265 }
266
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800267 @Nullable
268 public static File prepareBlobFile(long sessionId) {
269 final File blobsDir = prepareBlobsDir();
270 return blobsDir == null ? null : getBlobFile(blobsDir, sessionId);
271 }
272
273 @NonNull
274 public static File getBlobFile(long sessionId) {
275 return getBlobFile(getBlobsDir(), sessionId);
276 }
277
278 @NonNull
279 private static File getBlobFile(File blobsDir, long sessionId) {
280 return new File(blobsDir, String.valueOf(sessionId));
281 }
282
283 @Nullable
284 public static File prepareBlobsDir() {
285 final File blobsDir = getBlobsDir(prepareBlobStoreRootDir());
286 if (!blobsDir.exists() && !blobsDir.mkdir()) {
287 Slog.e(TAG, "Failed to mkdir(): " + blobsDir);
288 return null;
289 }
290 return blobsDir;
291 }
292
293 @NonNull
294 public static File getBlobsDir() {
295 return getBlobsDir(getBlobStoreRootDir());
296 }
297
298 @NonNull
299 private static File getBlobsDir(File blobsRootDir) {
Sudheer Shanka9f2296a2020-01-25 23:22:34 -0800300 return new File(blobsRootDir, BLOBS_DIR_NAME);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800301 }
302
303 @Nullable
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800304 public static File prepareSessionIndexFile() {
305 final File blobStoreRootDir = prepareBlobStoreRootDir();
306 if (blobStoreRootDir == null) {
307 return null;
308 }
Sudheer Shanka9f2296a2020-01-25 23:22:34 -0800309 return new File(blobStoreRootDir, SESSIONS_INDEX_FILE_NAME);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800310 }
311
312 @Nullable
313 public static File prepareBlobsIndexFile() {
314 final File blobsStoreRootDir = prepareBlobStoreRootDir();
315 if (blobsStoreRootDir == null) {
316 return null;
317 }
Sudheer Shanka9f2296a2020-01-25 23:22:34 -0800318 return new File(blobsStoreRootDir, BLOBS_INDEX_FILE_NAME);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800319 }
320
321 @Nullable
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800322 public static File prepareBlobStoreRootDir() {
323 final File blobStoreRootDir = getBlobStoreRootDir();
324 if (!blobStoreRootDir.exists() && !blobStoreRootDir.mkdir()) {
325 Slog.e(TAG, "Failed to mkdir(): " + blobStoreRootDir);
326 return null;
327 }
328 return blobStoreRootDir;
329 }
330
331 @NonNull
332 public static File getBlobStoreRootDir() {
Sudheer Shanka9f2296a2020-01-25 23:22:34 -0800333 return new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800334 }
Sudheer Shanka364364b2020-02-19 17:56:09 -0800335
336 public static void dump(IndentingPrintWriter fout, Context context) {
337 fout.println("XML current version: " + XML_VERSION_CURRENT);
338
339 fout.println("Idle job ID: " + IDLE_JOB_ID);
Sudheer Shanka364364b2020-02-19 17:56:09 -0800340
341 fout.println("Total bytes per app limit: " + formatFileSize(context,
342 getAppDataBytesLimit(), FLAG_IEC_UNITS));
343
344 fout.println("Device config properties:");
345 fout.increaseIndent();
346 DeviceConfigProperties.dump(fout, context);
347 fout.decreaseIndent();
348 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800349}