blob: c4a06c8f53db7f6f010948b9c8852dd1f20226c0 [file] [log] [blame]
Songchun Fan1a52cf72019-12-05 13:00:47 -08001/*
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
17package android.service.incremental;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.Service;
23import android.content.Intent;
24import android.content.pm.IDataLoader;
25import android.content.pm.IDataLoaderStatusListener;
26import android.content.pm.InstallationFile;
27import android.os.Bundle;
28import android.os.IBinder;
29import android.os.incremental.IncrementalDataLoaderParams;
30import android.os.incremental.IncrementalDataLoaderParamsParcel;
31import android.os.incremental.IncrementalFileSystemControlParcel;
32import android.os.incremental.NamedParcelFileDescriptor;
33import android.util.Slog;
34
35import java.io.IOException;
36import java.lang.annotation.Retention;
37import java.lang.annotation.RetentionPolicy;
38import java.util.Collection;
39import java.util.List;
40
41
42/**
43 * The base class for implementing data loader service to control data loaders. Expecting
44 * Incremental Service to bind to a children class of this.
45 *
46 * @hide
47 *
48 * Hide for now, should be @SystemApi
49 * TODO(b/136132412): update with latest API design
50 */
51public abstract class IncrementalDataLoaderService extends Service {
52 private static final String TAG = "IncrementalDataLoaderService";
53 private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
54
55 public static final int DATA_LOADER_READY =
56 IDataLoaderStatusListener.DATA_LOADER_READY;
57 public static final int DATA_LOADER_NOT_READY =
58 IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
59 public static final int DATA_LOADER_RUNNING =
60 IDataLoaderStatusListener.DATA_LOADER_RUNNING;
61 public static final int DATA_LOADER_STOPPED =
62 IDataLoaderStatusListener.DATA_LOADER_STOPPED;
63 public static final int DATA_LOADER_SLOW_CONNECTION =
64 IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
65 public static final int DATA_LOADER_NO_CONNECTION =
66 IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
67 public static final int DATA_LOADER_CONNECTION_OK =
68 IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
69
70 @Retention(RetentionPolicy.SOURCE)
71 @IntDef(prefix = {"DATA_LOADER_"}, value = {
72 DATA_LOADER_READY,
73 DATA_LOADER_NOT_READY,
74 DATA_LOADER_RUNNING,
75 DATA_LOADER_STOPPED,
76 DATA_LOADER_SLOW_CONNECTION,
77 DATA_LOADER_NO_CONNECTION,
78 DATA_LOADER_CONNECTION_OK
79 })
80 public @interface DataLoaderStatus {
81 }
82
83 /**
84 * Incremental FileSystem block size.
85 **/
86 public static final int BLOCK_SIZE = 4096;
87
88 /**
89 * Data compression types
90 */
91 public static final int COMPRESSION_NONE = 0;
92 public static final int COMPRESSION_LZ4 = 1;
93
94 /**
95 * @hide
96 */
97 @Retention(RetentionPolicy.SOURCE)
98 @IntDef({COMPRESSION_NONE, COMPRESSION_LZ4})
99 public @interface CompressionType {
100 }
101
102 /**
103 * Managed DataLoader interface. Each instance corresponds to a single Incremental File System
104 * instance.
105 */
106 public abstract static class DataLoader {
107 /**
108 * A virtual constructor used to do simple initialization. Not ready to serve any data yet.
109 * All heavy-lifting has to be done in onStart.
110 *
111 * @param params Data loader configuration parameters.
112 * @param connector IncFS API wrapper.
113 * @param listener Used for reporting internal state to IncrementalService.
114 * @return True if initialization of a Data Loader was successful. False will be reported to
115 * IncrementalService and can cause an unmount of an IFS instance.
116 */
117 public abstract boolean onCreate(@NonNull IncrementalDataLoaderParams params,
118 @NonNull FileSystemConnector connector,
119 @NonNull StatusListener listener);
120
121 /**
122 * Start the data loader. After this method returns data loader is considered to be ready to
123 * receive callbacks from IFS, supply data via connector and send status updates via
124 * callbacks.
125 *
126 * @return True if Data Loader was able to start. False will be reported to
127 * IncrementalService and can cause an unmount of an IFS instance.
128 */
129 public abstract boolean onStart();
130
131 /**
132 * Stop the data loader. Use to stop any additional threads and free up resources. Data
133 * loader is not longer responsible for supplying data. Start/Stop pair can be called
134 * multiple times e.g. if IFS detects corruption and data needs to be re-loaded.
135 */
136 public abstract void onStop();
137
138 /**
139 * Virtual destructor. Use to cleanup all internal state. After this method returns, the
140 * data loader can no longer use connector or callbacks. For any additional operations with
141 * this instance of IFS a new DataLoader will be created using createDataLoader method.
142 */
143 public abstract void onDestroy();
144
145 /**
146 * IFS reports a pending read each time the page needs to be loaded, e.g. missing.
147 *
148 * @param pendingReads array of blocks to load.
149 *
150 * TODO(b/136132412): avoid using collections
151 */
152 public abstract void onPendingReads(
153 @NonNull Collection<FileSystemConnector.PendingReadInfo> pendingReads);
154
155 /**
156 * IFS tracks all reads and reports them using onPageReads.
157 *
158 * @param reads array of blocks.
159 *
160 * TODO(b/136132412): avoid using collections
161 */
162 public abstract void onPageReads(@NonNull Collection<FileSystemConnector.ReadInfo> reads);
163
164 /**
165 * IFS informs data loader that a new file has been created.
166 * <p>
167 * This can be used to prepare the data loader before it starts loading data. For example,
168 * the data loader can keep a list of newly created files, so that it knows what files to
169 * download from the server.
170 *
171 * @param inode The inode value of the new file.
172 * @param metadata The metadata of the new file.
173 */
174 public abstract void onFileCreated(long inode, byte[] metadata);
175 }
176
177 /**
178 * DataLoader factory method.
179 *
180 * @return An instance of a DataLoader.
181 */
182 public abstract @Nullable DataLoader onCreateDataLoader();
183
184 /**
185 * @hide
186 */
187 public final @NonNull IBinder onBind(@NonNull Intent intent) {
188 return (IBinder) mBinder;
189 }
190
191 private class DataLoaderBinderService extends IDataLoader.Stub {
192 private int mId;
193
194 @Override
195 public void create(int id, @NonNull Bundle options,
196 @NonNull IDataLoaderStatusListener listener)
197 throws IllegalArgumentException, RuntimeException {
198 mId = id;
199 final IncrementalDataLoaderParamsParcel params = options.getParcelable("params");
200 if (params == null) {
201 throw new IllegalArgumentException("Must specify Incremental data loader params");
202 }
203 final IncrementalFileSystemControlParcel control =
204 options.getParcelable("control");
205 if (control == null) {
206 throw new IllegalArgumentException("Must specify Incremental control parcel");
207 }
208 mStatusListener = listener;
209 try {
210 if (!nativeCreateDataLoader(id, control, params, listener)) {
211 Slog.e(TAG, "Failed to create native loader for " + mId);
212 }
213 } catch (Exception ex) {
214 destroy();
215 throw new RuntimeException(ex);
216 } finally {
217 // Closing FDs.
218 if (control.cmd != null) {
219 try {
220 control.cmd.close();
221 } catch (IOException e) {
222 Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
223 }
224 }
225 if (control.log != null) {
226 try {
227 control.log.close();
228 } catch (IOException e) {
229 Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
230 }
231 }
232 NamedParcelFileDescriptor[] fds = params.dynamicArgs;
233 for (NamedParcelFileDescriptor nfd : fds) {
234 try {
235 nfd.fd.close();
236 } catch (IOException e) {
237 Slog.e(TAG,
238 "Failed to close DynamicArgs parcel file descriptor " + e);
239 }
240 }
241 }
242 }
243
244 @Override
245 public void start(List<InstallationFile> fileInfos) {
246 if (!nativeStartDataLoader(mId)) {
247 Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
248 }
249 }
250
251 @Override
252 public void stop() {
253 if (!nativeStopDataLoader(mId)) {
254 Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
255 }
256 }
257
258 @Override
259 public void destroy() {
260 if (!nativeDestroyDataLoader(mId)) {
261 Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
262 }
263 }
264
265 @Override
266 // TODO(b/136132412): remove this
267 public void onFileCreated(long inode, byte[] metadata) {
268 if (!nativeOnFileCreated(mId, inode, metadata)) {
269 Slog.w(TAG, "Failed to handle onFileCreated for storage:" + mId
270 + " inode:" + inode);
271 }
272 }
273 }
274
275 /**
276 * IncFs API wrapper for writing pages and getting page missing info. Non-hidden methods are
277 * expected to be called by the IncrementalDataLoaderService implemented by developers.
278 *
279 * @hide
280 *
281 * TODO(b/136132412) Should be @SystemApi
282 */
283 public static final class FileSystemConnector {
284 /**
285 * Defines a block address. A block is the unit of data chunk that IncFs operates with.
286 *
287 * @hide
288 */
289 public static class BlockAddress {
290 /**
291 * Linux inode uniquely identifies file within a single IFS instance.
292 */
293 private final long mFileIno;
294 /**
295 * Index of a 4K block within a file.
296 */
297 private final int mBlockIndex;
298
299 public BlockAddress(long fileIno, int blockIndex) {
300 this.mFileIno = fileIno;
301 this.mBlockIndex = blockIndex;
302 }
303
304 public long getFileIno() {
305 return mFileIno;
306 }
307
308 public int getBlockIndex() {
309 return mBlockIndex;
310 }
311 }
312
313 /**
314 * A block is the unit of data chunk that IncFs operates with.
315 *
316 * @hide
317 */
318 public static class Block extends BlockAddress {
319 /**
320 * Data content of the block.
321 */
322 private final @NonNull byte[] mDataBytes;
323
324 public Block(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
325 super(fileIno, blockIndex);
326 this.mDataBytes = dataBytes;
327 }
328 }
329
330 /**
331 * Defines a page/block inside a file.
332 */
333 public static class DataBlock extends Block {
334 /**
335 * Compression type of the data block.
336 */
337 private final @CompressionType int mCompressionType;
338
339 public DataBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes,
340 @CompressionType int compressionType) {
341 super(fileIno, blockIndex, dataBytes);
342 this.mCompressionType = compressionType;
343 }
344 }
345
346 /**
347 * Defines a hash block for a certain file. A hash block index is the index in an array of
348 * hashes which is the 1-d representation of the hash tree. One DataBlock might be
349 * associated with multiple HashBlocks.
350 */
351 public static class HashBlock extends Block {
352 public HashBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
353 super(fileIno, blockIndex, dataBytes);
354 }
355 }
356
357 /**
358 * Information about a page that is pending to be read.
359 */
360 public static class PendingReadInfo extends BlockAddress {
361 PendingReadInfo(long fileIno, int blockIndex) {
362 super(fileIno, blockIndex);
363 }
364 }
365
366 /**
367 * Information about a page that is read.
368 */
369 public static class ReadInfo extends BlockAddress {
370 /**
371 * A monotonically increasing read timestamp.
372 */
373 private final long mTimePoint;
374 /**
375 * Number of blocks read starting from blockIndex.
376 */
377 private final int mBlockCount;
378
379 ReadInfo(long timePoint, long fileIno, int firstBlockIndex, int blockCount) {
380 super(fileIno, firstBlockIndex);
381 this.mTimePoint = timePoint;
382 this.mBlockCount = blockCount;
383 }
384
385 public long getTimePoint() {
386 return mTimePoint;
387 }
388
389 public int getBlockCount() {
390 return mBlockCount;
391 }
392 }
393
394 /**
395 * Defines the dynamic information about an IncFs file.
396 */
397 public static class FileInfo {
398 /**
399 * BitSet to show if any block is available at each block index.
400 */
401 private final @NonNull
402 byte[] mBlockBitmap;
403
404 /**
405 * @hide
406 */
407 public FileInfo(@NonNull byte[] blockBitmap) {
408 this.mBlockBitmap = blockBitmap;
409 }
410 }
411
412 /**
413 * Creates a wrapper for a native instance.
414 */
415 FileSystemConnector(long nativeInstance) {
416 mNativeInstance = nativeInstance;
417 }
418
419 /**
420 * Checks whether a range in a file if loaded.
421 *
422 * @param node inode of the file.
423 * @param start The starting offset of the range.
424 * @param end The ending offset of the range.
425 * @return True if the file is fully loaded.
426 */
427 public boolean isFileRangeLoaded(long node, long start, long end) {
428 return nativeIsFileRangeLoadedNode(mNativeInstance, node, start, end);
429 }
430
431 /**
432 * Gets the metadata of a file.
433 *
434 * @param node inode of the file.
435 * @return The metadata object.
436 */
437 @NonNull
438 public byte[] getFileMetadata(long node) throws IOException {
439 final byte[] metadata = nativeGetFileMetadataNode(mNativeInstance, node);
440 if (metadata == null || metadata.length == 0) {
441 throw new IOException(
442 "IncrementalFileSystem failed to obtain metadata for node: " + node);
443 }
444 return metadata;
445 }
446
447 /**
448 * Gets the dynamic information of a file, such as page bitmaps. Can be used to get missing
449 * page indices by the FileSystemConnector.
450 *
451 * @param node inode of the file.
452 * @return Dynamic file info.
453 */
454 @NonNull
455 public FileInfo getDynamicFileInfo(long node) throws IOException {
456 final byte[] blockBitmap = nativeGetFileInfoNode(mNativeInstance, node);
457 if (blockBitmap == null || blockBitmap.length == 0) {
458 throw new IOException(
459 "IncrementalFileSystem failed to obtain dynamic file info for node: "
460 + node);
461 }
462 return new FileInfo(blockBitmap);
463 }
464
465 /**
466 * Writes a page's data and/or hashes.
467 *
468 * @param dataBlocks the DataBlock objects that contain data block index and data bytes.
469 * @param hashBlocks the HashBlock objects that contain hash indices and hash bytes.
470 *
471 * TODO(b/136132412): change API to avoid dynamic allocation of data block objects
472 */
473 public void writeMissingData(@NonNull DataBlock[] dataBlocks,
474 @Nullable HashBlock[] hashBlocks) throws IOException {
475 if (!nativeWriteMissingData(mNativeInstance, dataBlocks, hashBlocks)) {
476 throw new IOException("IncrementalFileSystem failed to write missing data.");
477 }
478 }
479
480 /**
481 * Writes the signer block of a file. Expecting the connector to call this when it got
482 * signing data from data loader.
483 *
484 * @param node the file to be written to.
485 * @param signerData the raw signer data byte array.
486 */
487 public void writeSignerData(long node, @NonNull byte[] signerData)
488 throws IOException {
489 if (!nativeWriteSignerDataNode(mNativeInstance, node, signerData)) {
490 throw new IOException(
491 "IncrementalFileSystem failed to write signer data of node " + node);
492 }
493 }
494
495 private final long mNativeInstance;
496 }
497
498 /**
499 * Wrapper for native reporting DataLoader statuses.
500 *
501 * @hide
502 *
503 * TODO(b/136132412) Should be @SystemApi
504 */
505 public static final class StatusListener {
506 /**
507 * Creates a wrapper for a native instance.
508 *
509 * @hide
510 */
511 StatusListener(long nativeInstance) {
512 mNativeInstance = nativeInstance;
513 }
514
515 /**
516 * Report the status of DataLoader. Used for system-wide notifications e.g., disabling
517 * applications which rely on this data loader to function properly.
518 *
519 * @param status status to report.
520 * @return True if status was reported successfully.
521 */
522 public boolean onStatusChanged(@DataLoaderStatus int status) {
523 return nativeReportStatus(mNativeInstance, status);
524 }
525
526 private final long mNativeInstance;
527 }
528
529 private IDataLoaderStatusListener mStatusListener = null;
530
531 /* Native methods */
532 private native boolean nativeCreateDataLoader(int storageId,
533 @NonNull IncrementalFileSystemControlParcel control,
534 @NonNull IncrementalDataLoaderParamsParcel params,
535 IDataLoaderStatusListener listener);
536
537 private native boolean nativeStartDataLoader(int storageId);
538
539 private native boolean nativeStopDataLoader(int storageId);
540
541 private native boolean nativeDestroyDataLoader(int storageId);
542
543 private static native boolean nativeOnFileCreated(int storageId,
544 long inode, byte[] metadata);
545
546 private static native boolean nativeIsFileRangeLoadedNode(
547 long nativeInstance, long node, long start, long end);
548
549 private static native boolean nativeWriteMissingData(
550 long nativeInstance, FileSystemConnector.DataBlock[] dataBlocks,
551 FileSystemConnector.HashBlock[] hashBlocks);
552
553 private static native boolean nativeWriteSignerDataNode(
554 long nativeInstance, long node, byte[] signerData);
555
556 private static native byte[] nativeGetFileMetadataNode(
557 long nativeInstance, long node);
558
559 private static native byte[] nativeGetFileInfoNode(
560 long nativeInstance, long node);
561
562 private static native boolean nativeReportStatus(long nativeInstance, int status);
563}