diff options
author | 2021-01-11 12:40:49 -0700 | |
---|---|---|
committer | 2021-01-12 13:51:28 -0700 | |
commit | 8eebdbe4b5a9576619de642082e164fce85d83e2 (patch) | |
tree | adc7d910eeb248cba9a07a50326b6b6d7c89265d | |
parent | 47e8e59379c5be0123ddc03ddee1f3a7ca8ab5dd (diff) |
Interface to dynamically generate DropBox data.
The existing DropBoxManager APIs require that data be entirely
pre-cooked before it can be added. This adds significant overhead
to Log.wtf() style messages which collect logcat data, since we
need to copy that log data through a pipe to an in-memory buffer.
To avoid that overhead, this change introduces an EntrySource
interface which can either be pre-cooked data or dynamically
generated data written directly to an open FD on disk. Future
changes will adjust the Log.wtf() logic to use this new interface
to have logcat write directly into an FD.
In addition, this interface paves the way for leveraging a newer
F2FS feature which transparently compresses and decompresses data in
the kernel, instead of forcing us to use DEFLATE in userspace.
This change drops periodic quota checking while recording new
entries, and instead adjusts to pre-flight the quota checks before
writing starts. It also drops the expensive fsync(), since these
logs are collected on a best-effort basis.
Bug: 176843501
Test: atest CtsDropBoxManagerTestCases
Test: atest FrameworksServicesTests:com.android.server.DropBoxTest
Change-Id: Ic78e99a32cfaf4edac066a73a6864c9c9e9fdeef
-rw-r--r-- | core/api/current.txt | 22 | ||||
-rw-r--r-- | core/java/android/os/DropBoxManager.java | 83 | ||||
-rw-r--r-- | core/java/com/android/internal/os/IDropBoxManagerService.aidl | 9 | ||||
-rw-r--r-- | libs/services/include/android/os/DropBoxManager.h | 2 | ||||
-rw-r--r-- | libs/services/src/os/DropBoxManager.cpp | 31 | ||||
-rw-r--r-- | services/core/java/com/android/server/DropBoxManagerInternal.java | 40 | ||||
-rw-r--r-- | services/core/java/com/android/server/DropBoxManagerService.java | 174 | ||||
-rw-r--r-- | services/tests/servicestests/src/com/android/server/DropBoxTest.java | 68 |
8 files changed, 296 insertions, 133 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 0defc05fc5d0..4d32558ab9d5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -30298,9 +30298,9 @@ package android.os { public class DropBoxManager { ctor protected DropBoxManager(); - method public void addData(String, byte[], int); - method public void addFile(String, java.io.File, int) throws java.io.IOException; - method public void addText(String, String); + method public void addData(@NonNull String, @Nullable byte[], int); + method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException; + method public void addText(@NonNull String, @NonNull String); method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long); method public boolean isTagEnabled(String); field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED"; @@ -30313,17 +30313,17 @@ package android.os { } public static class DropBoxManager.Entry implements java.io.Closeable android.os.Parcelable { - ctor public DropBoxManager.Entry(String, long); - ctor public DropBoxManager.Entry(String, long, String); - ctor public DropBoxManager.Entry(String, long, byte[], int); - ctor public DropBoxManager.Entry(String, long, android.os.ParcelFileDescriptor, int); - ctor public DropBoxManager.Entry(String, long, java.io.File, int) throws java.io.IOException; + ctor public DropBoxManager.Entry(@NonNull String, long); + ctor public DropBoxManager.Entry(@NonNull String, long, @NonNull String); + ctor public DropBoxManager.Entry(@NonNull String, long, @Nullable byte[], int); + ctor public DropBoxManager.Entry(@NonNull String, long, @Nullable android.os.ParcelFileDescriptor, int); + ctor public DropBoxManager.Entry(@NonNull String, long, @NonNull java.io.File, int) throws java.io.IOException; method public void close(); method public int describeContents(); method public int getFlags(); - method public java.io.InputStream getInputStream() throws java.io.IOException; - method public String getTag(); - method public String getText(int); + method @Nullable public java.io.InputStream getInputStream() throws java.io.IOException; + method @NonNull public String getTag(); + method @Nullable public String getText(int); method public long getTimeMillis(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.DropBoxManager.Entry> CREATOR; diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index 3dce130f5d61..575fc4c8931f 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -19,6 +19,10 @@ package android.os; import static android.Manifest.permission.PACKAGE_USAGE_STATS; import static android.Manifest.permission.READ_LOGS; +import android.annotation.BytesLong; +import android.annotation.CurrentTimeMillisLong; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; @@ -35,6 +39,9 @@ import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.charset.StandardCharsets; import java.util.zip.GZIPInputStream; /** @@ -54,6 +61,11 @@ public class DropBoxManager { @UnsupportedAppUsage private final IDropBoxManagerService mService; + /** @hide */ + @IntDef(flag = true, prefix = { "IS_" }, value = { IS_EMPTY, IS_TEXT, IS_GZIPPED }) + @Retention(RetentionPolicy.SOURCE) + public @interface Flags {} + /** Flag value: Entry's content was deleted to save space. */ public static final int IS_EMPTY = 1; @@ -105,15 +117,15 @@ public class DropBoxManager { * {@link #close()} when you are done using it. */ public static class Entry implements Parcelable, Closeable { - private final String mTag; - private final long mTimeMillis; + private final @NonNull String mTag; + private final @CurrentTimeMillisLong long mTimeMillis; - private final byte[] mData; - private final ParcelFileDescriptor mFileDescriptor; - private final int mFlags; + private final @Nullable byte[] mData; + private final @Nullable ParcelFileDescriptor mFileDescriptor; + private final @Flags int mFlags; /** Create a new empty Entry with no contents. */ - public Entry(String tag, long millis) { + public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis) { if (tag == null) throw new NullPointerException("tag == null"); mTag = tag; @@ -124,13 +136,14 @@ public class DropBoxManager { } /** Create a new Entry with plain text contents. */ - public Entry(String tag, long millis, String text) { + public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis, + @NonNull String text) { if (tag == null) throw new NullPointerException("tag == null"); if (text == null) throw new NullPointerException("text == null"); mTag = tag; mTimeMillis = millis; - mData = text.getBytes(); + mData = text.getBytes(StandardCharsets.UTF_8); mFileDescriptor = null; mFlags = IS_TEXT; } @@ -139,7 +152,8 @@ public class DropBoxManager { * Create a new Entry with byte array contents. * The data array must not be modified after creating this entry. */ - public Entry(String tag, long millis, byte[] data, int flags) { + public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis, + @Nullable byte[] data, @Flags int flags) { if (tag == null) throw new NullPointerException("tag == null"); if (((flags & IS_EMPTY) != 0) != (data == null)) { throw new IllegalArgumentException("Bad flags: " + flags); @@ -156,7 +170,8 @@ public class DropBoxManager { * Create a new Entry with streaming data contents. * Takes ownership of the ParcelFileDescriptor. */ - public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) { + public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis, + @Nullable ParcelFileDescriptor data, @Flags int flags) { if (tag == null) throw new NullPointerException("tag == null"); if (((flags & IS_EMPTY) != 0) != (data == null)) { throw new IllegalArgumentException("Bad flags: " + flags); @@ -173,7 +188,8 @@ public class DropBoxManager { * Create a new Entry with the contents read from a file. * The file will be read when the entry's contents are requested. */ - public Entry(String tag, long millis, File data, int flags) throws IOException { + public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis, + @NonNull File data, @Flags int flags) throws IOException { if (tag == null) throw new NullPointerException("tag == null"); if ((flags & IS_EMPTY) != 0) throw new IllegalArgumentException("Bad flags: " + flags); @@ -190,19 +206,26 @@ public class DropBoxManager { } /** @return the tag originally attached to the entry. */ - public String getTag() { return mTag; } + public @NonNull String getTag() { + return mTag; + } /** @return time when the entry was originally created. */ - public long getTimeMillis() { return mTimeMillis; } + public @CurrentTimeMillisLong long getTimeMillis() { + return mTimeMillis; + } /** @return flags describing the content returned by {@link #getInputStream()}. */ - public int getFlags() { return mFlags & ~IS_GZIPPED; } // getInputStream() decompresses. + public @Flags int getFlags() { + // getInputStream() decompresses. + return mFlags & ~IS_GZIPPED; + } /** * @param maxBytes of string to return (will truncate at this length). * @return the uncompressed text contents of the entry, null if the entry is not text. */ - public String getText(int maxBytes) { + public @Nullable String getText(@BytesLong int maxBytes) { if ((mFlags & IS_TEXT) == 0) return null; if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length)); @@ -225,7 +248,7 @@ public class DropBoxManager { } /** @return the uncompressed contents of the entry, or null if the contents were lost */ - public InputStream getInputStream() throws IOException { + public @Nullable InputStream getInputStream() throws IOException { InputStream is; if (mData != null) { is = new ByteArrayInputStream(mData); @@ -293,17 +316,8 @@ public class DropBoxManager { * @param tag describing the type of entry being stored * @param data value to store */ - public void addText(String tag, String data) { - try { - mService.add(new Entry(tag, 0, data)); - } catch (RemoteException e) { - if (e instanceof TransactionTooLargeException - && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { - Log.e(TAG, "App sent too much data, so it was ignored", e); - return; - } - throw e.rethrowFromSystemServer(); - } + public void addText(@NonNull String tag, @NonNull String data) { + addData(tag, data.getBytes(StandardCharsets.UTF_8), IS_TEXT); } /** @@ -313,10 +327,10 @@ public class DropBoxManager { * @param data value to store * @param flags describing the data */ - public void addData(String tag, byte[] data, int flags) { + public void addData(@NonNull String tag, @Nullable byte[] data, @Flags int flags) { if (data == null) throw new NullPointerException("data == null"); try { - mService.add(new Entry(tag, 0, data, flags)); + mService.addData(tag, data, flags); } catch (RemoteException e) { if (e instanceof TransactionTooLargeException && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { @@ -336,15 +350,14 @@ public class DropBoxManager { * @param flags describing the data * @throws IOException if the file can't be opened */ - public void addFile(String tag, File file, int flags) throws IOException { + public void addFile(@NonNull String tag, @NonNull File file, @Flags int flags) + throws IOException { if (file == null) throw new NullPointerException("file == null"); - Entry entry = new Entry(tag, 0, file, flags); - try { - mService.add(entry); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, + ParcelFileDescriptor.MODE_READ_ONLY)) { + mService.addFile(tag, pfd, flags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } finally { - entry.close(); } } diff --git a/core/java/com/android/internal/os/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl index 9141719d7a35..38a7203ead4a 100644 --- a/core/java/com/android/internal/os/IDropBoxManagerService.aidl +++ b/core/java/com/android/internal/os/IDropBoxManagerService.aidl @@ -17,6 +17,7 @@ package com.android.internal.os; import android.os.DropBoxManager; +import android.os.ParcelFileDescriptor; /** * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the @@ -26,12 +27,8 @@ import android.os.DropBoxManager; * @hide */ interface IDropBoxManagerService { - /** - * @see DropBoxManager#addText - * @see DropBoxManager#addData - * @see DropBoxManager#addFile - */ - void add(in DropBoxManager.Entry entry); + void addData(String tag, in byte[] data, int flags); + void addFile(String tag, in ParcelFileDescriptor fd, int flags); /** @see DropBoxManager#getNextEntry */ boolean isTagEnabled(String tag); diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h index 07472435d8a3..5689286f0b32 100644 --- a/libs/services/include/android/os/DropBoxManager.h +++ b/libs/services/include/android/os/DropBoxManager.h @@ -93,8 +93,6 @@ private: enum { HAS_BYTE_ARRAY = 8 }; - - Status add(const Entry& entry); }; }} // namespace android::os diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp index 429f996bd65e..3716e019f69a 100644 --- a/libs/services/src/os/DropBoxManager.cpp +++ b/libs/services/src/os/DropBoxManager.cpp @@ -18,7 +18,9 @@ #include <android/os/DropBoxManager.h> +#include <android-base/unique_fd.h> #include <binder/IServiceManager.h> +#include <binder/ParcelFileDescriptor.h> #include <com/android/internal/os/IDropBoxManagerService.h> #include <cutils/log.h> @@ -178,18 +180,24 @@ DropBoxManager::~DropBoxManager() Status DropBoxManager::addText(const String16& tag, const string& text) { - Entry entry(tag, IS_TEXT); - entry.mData.assign(text.c_str(), text.c_str() + text.size()); - return add(entry); + return addData(tag, reinterpret_cast<uint8_t const*>(text.c_str()), text.size(), IS_TEXT); } Status DropBoxManager::addData(const String16& tag, uint8_t const* data, size_t size, int flags) { - Entry entry(tag, flags); - entry.mData.assign(data, data+size); - return add(entry); + sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>( + defaultServiceManager()->getService(android::String16("dropbox"))); + if (service == NULL) { + return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); + } + ALOGD("About to call service->add()"); + vector<uint8_t> dataArg; + dataArg.assign(data, data + size); + Status status = service->addData(tag, dataArg, flags); + ALOGD("service->add returned %s", status.toString8().string()); + return status; } Status @@ -213,20 +221,15 @@ DropBoxManager::addFile(const String16& tag, int fd, int flags) ALOGW("DropboxManager: %s", message.c_str()); return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str()); } - Entry entry(tag, flags, fd); - return add(entry); -} - -Status -DropBoxManager::add(const Entry& entry) -{ sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>( defaultServiceManager()->getService(android::String16("dropbox"))); if (service == NULL) { return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); } ALOGD("About to call service->add()"); - Status status = service->add(entry); + android::base::unique_fd uniqueFd(fd); + android::os::ParcelFileDescriptor parcelFd(std::move(uniqueFd)); + Status status = service->addFile(tag, parcelFd, flags); ALOGD("service->add returned %s", status.toString8().string()); return status; } diff --git a/services/core/java/com/android/server/DropBoxManagerInternal.java b/services/core/java/com/android/server/DropBoxManagerInternal.java new file mode 100644 index 000000000000..3785a9c011ce --- /dev/null +++ b/services/core/java/com/android/server/DropBoxManagerInternal.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.annotation.BytesLong; +import android.annotation.NonNull; +import android.os.DropBoxManager; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.IOException; + +public abstract class DropBoxManagerInternal { + public abstract void addEntry(@NonNull String tag, @NonNull EntrySource source, + @DropBoxManager.Flags int flags); + + /** + * Interface which describes a pending entry which knows how to write itself + * to the given FD. This abstraction supports implementations which may want + * to dynamically generate the entry contents. + */ + public interface EntrySource extends Closeable { + public @BytesLong long length(); + public void writeTo(@NonNull FileDescriptor fd) throws IOException; + } +} diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index 30fc3364f6b7..a6d9bf8bc55b 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -35,6 +35,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; @@ -43,6 +44,10 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.service.dropbox.DropBoxManagerServiceDumpProto; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.system.StructStat; import android.text.TextUtils; import android.text.format.TimeMigrationUtils; import android.util.ArrayMap; @@ -56,18 +61,19 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IDropBoxManagerService; import com.android.internal.util.DumpUtils; import com.android.internal.util.ObjectUtils; +import com.android.server.DropBoxManagerInternal.EntrySource; import libcore.io.IoUtils; -import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.SortedSet; @@ -93,6 +99,9 @@ public final class DropBoxManagerService extends SystemService { // Max number of bytes of a dropbox entry to write into protobuf. private static final int PROTO_MAX_DATA_BYTES = 256 * 1024; + // Size beyond which to force-compress newly added entries. + private static final long COMPRESS_THRESHOLD_BYTES = 16_384; + // TODO: This implementation currently uses one file per entry, which is // inefficient for smallish entries -- consider using a single queue file // per tag (or even globally) instead. @@ -149,8 +158,13 @@ public final class DropBoxManagerService extends SystemService { private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() { @Override - public void add(DropBoxManager.Entry entry) { - DropBoxManagerService.this.add(entry); + public void addData(String tag, byte[] data, int flags) { + DropBoxManagerService.this.addData(tag, data, flags); + } + + @Override + public void addFile(String tag, ParcelFileDescriptor fd, int flags) { + DropBoxManagerService.this.addFile(tag, fd, flags); } @Override @@ -333,6 +347,7 @@ public final class DropBoxManagerService extends SystemService { mDropBoxDir = path; mContentResolver = getContext().getContentResolver(); mHandler = new DropBoxManagerBroadcastHandler(looper); + LocalServices.addService(DropBoxManagerInternal.class, new DropBoxManagerInternalImpl()); } @Override @@ -374,77 +389,101 @@ public final class DropBoxManagerService extends SystemService { return mStub; } - public void add(DropBoxManager.Entry entry) { - File temp = null; - InputStream input = null; - OutputStream output = null; - final String tag = entry.getTag(); + public void addData(String tag, byte[] data, int flags) { + addEntry(tag, new ByteArrayInputStream(data), data.length, flags); + } + + public void addFile(String tag, ParcelFileDescriptor fd, int flags) { + final StructStat stat; try { - int flags = entry.getFlags(); - Slog.i(TAG, "add tag=" + tag + " isTagEnabled=" + isTagEnabled(tag) - + " flags=0x" + Integer.toHexString(flags)); - if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException(); + stat = Os.fstat(fd.getFileDescriptor()); - init(); - if (!isTagEnabled(tag)) return; - long max = trimToFit(); - long lastTrim = System.currentTimeMillis(); + // Verify caller isn't playing games with pipes or sockets + if (!OsConstants.S_ISREG(stat.st_mode)) { + throw new IllegalArgumentException(tag + " entry must be real file"); + } + } catch (ErrnoException e) { + throw new IllegalArgumentException(e); + } - byte[] buffer = new byte[mBlockSize]; - input = entry.getInputStream(); + addEntry(tag, new ParcelFileDescriptor.AutoCloseInputStream(fd), stat.st_size, flags); + } - // First, accumulate up to one block worth of data in memory before - // deciding whether to compress the data or not. + public void addEntry(String tag, InputStream in, long length, int flags) { + // If entry being added is large, and if it's not already compressed, + // then we'll force compress it during write + boolean forceCompress = false; + if ((flags & DropBoxManager.IS_GZIPPED) == 0 + && length > COMPRESS_THRESHOLD_BYTES) { + forceCompress = true; + flags |= DropBoxManager.IS_GZIPPED; + } - int read = 0; - while (read < buffer.length) { - int n = input.read(buffer, read, buffer.length - read); - if (n <= 0) break; - read += n; - } + addEntry(tag, new SimpleEntrySource(in, length, forceCompress), flags); + } + + /** + * Simple entry which contains data ready to be written. + */ + public static class SimpleEntrySource implements EntrySource { + private final InputStream in; + private final long length; + private final boolean forceCompress; + + public SimpleEntrySource(InputStream in, long length, boolean forceCompress) { + this.in = in; + this.length = length; + this.forceCompress = forceCompress; + } + + public long length() { + return length; + } - // If we have at least one block, compress it -- otherwise, just write - // the data in uncompressed form. - - temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp"); - int bufferSize = mBlockSize; - if (bufferSize > 4096) bufferSize = 4096; - if (bufferSize < 512) bufferSize = 512; - FileOutputStream foutput = new FileOutputStream(temp); - output = new BufferedOutputStream(foutput, bufferSize); - if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) { - output = new GZIPOutputStream(output); - flags = flags | DropBoxManager.IS_GZIPPED; + @Override + public void writeTo(FileDescriptor fd) throws IOException { + // No need to buffer the output here, since data is either coming + // from an in-memory buffer, or another file on disk; if we buffered + // we'd lose out on sendfile() optimizations + if (forceCompress) { + FileUtils.copy(in, new GZIPOutputStream(new FileOutputStream(fd))); + } else { + FileUtils.copy(in, new FileOutputStream(fd)); } + } - do { - output.write(buffer, 0, read); + @Override + public void close() throws IOException { + FileUtils.closeQuietly(in); + } + } - long now = System.currentTimeMillis(); - if (now - lastTrim > 30 * 1000) { - max = trimToFit(); // In case data dribbles in slowly - lastTrim = now; - } + public void addEntry(String tag, EntrySource entry, int flags) { + File temp = null; + try { + Slog.i(TAG, "add tag=" + tag + " isTagEnabled=" + isTagEnabled(tag) + + " flags=0x" + Integer.toHexString(flags)); + if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException(); - read = input.read(buffer); - if (read <= 0) { - FileUtils.sync(foutput); - output.close(); // Get a final size measurement - output = null; - } else { - output.flush(); // So the size measurement is pseudo-reasonable - } + init(); - long len = temp.length(); - if (len > max) { - Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " - + max + " bytes)"); - temp.delete(); - temp = null; // Pass temp = null to createEntry() to leave a tombstone - break; + // Bail early if we know tag is disabled + if (!isTagEnabled(tag)) return; + + // Drop entries which are too large for our quota + final long length = entry.length(); + final long max = trimToFit(); + if (length > max) { + // Log and fall through to create empty tombstone below + Slog.w(TAG, "Dropping: " + tag + " (" + length + " > " + max + " bytes)"); + } else { + temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp"); + try (FileOutputStream out = new FileOutputStream(temp)) { + entry.writeTo(out.getFD()); } - } while (read > 0); + } + // Writing above succeeded, so create the finalized entry long time = createEntry(temp, tag, flags); temp = null; @@ -461,9 +500,7 @@ public final class DropBoxManagerService extends SystemService { } catch (IOException e) { Slog.e(TAG, "Can't write: " + tag, e); } finally { - IoUtils.closeQuietly(output); - IoUtils.closeQuietly(input); - entry.close(); + IoUtils.closeQuietly(entry); if (temp != null) temp.delete(); } } @@ -1187,4 +1224,11 @@ public final class DropBoxManagerService extends SystemService { mLowPriorityTags.add(lowPrioritytags[i]); } } + + private final class DropBoxManagerInternalImpl extends DropBoxManagerInternal { + @Override + public void addEntry(String tag, EntrySource entry, int flags) { + DropBoxManagerService.this.addEntry(tag, entry, flags); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/DropBoxTest.java b/services/tests/servicestests/src/com/android/server/DropBoxTest.java index 56773e831902..a25f4920030c 100644 --- a/services/tests/servicestests/src/com/android/server/DropBoxTest.java +++ b/services/tests/servicestests/src/com/android/server/DropBoxTest.java @@ -31,15 +31,20 @@ import android.os.UserHandle; import android.provider.Settings; import android.test.AndroidTestCase; +import com.android.server.DropBoxManagerInternal.EntrySource; import com.android.server.DropBoxManagerService.EntryFile; +import libcore.io.Streams; + import java.io.BufferedReader; import java.io.File; +import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.zip.GZIPOutputStream; @@ -56,6 +61,8 @@ public class DropBoxTest extends AndroidTestCase { protected void setUp() throws Exception { super.setUp(); + LocalServices.removeServiceForTest(DropBoxManagerInternal.class); + mContext = new ContextWrapper(super.getContext()) { @Override public void sendBroadcastAsUser(Intent intent, @@ -212,6 +219,67 @@ public class DropBoxTest extends AndroidTestCase { e3.close(); } + public void testAddEntry_Success() throws Exception { + File dir = getEmptyDir("testAddEntry"); + long before = System.currentTimeMillis(); + + DropBoxManagerService service = new DropBoxManagerService(getContext(), dir, + Looper.getMainLooper()); + DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub()); + + LocalServices.getService(DropBoxManagerInternal.class).addEntry("DropBoxTest", + new EntrySource() { + @Override + public void writeTo(FileDescriptor fd) throws IOException { + try (FileOutputStream out = new FileOutputStream(fd)) { + out.write("test".getBytes(StandardCharsets.UTF_8)); + } + } + + @Override + public void close() throws IOException { + } + + @Override + public long length() { + return 0; + } + }, DropBoxManager.IS_TEXT); + + DropBoxManager.Entry entry = dropbox.getNextEntry("DropBoxTest", before); + assertEquals(DropBoxManager.IS_TEXT, entry.getFlags()); + assertEquals("test", new String(Streams.readFully(entry.getInputStream()))); + } + + public void testAddEntry_Failure() throws Exception { + File dir = getEmptyDir("testAddEntry"); + long before = System.currentTimeMillis(); + + DropBoxManagerService service = new DropBoxManagerService(getContext(), dir, + Looper.getMainLooper()); + DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub()); + + LocalServices.getService(DropBoxManagerInternal.class).addEntry("DropBoxTest", + new EntrySource() { + @Override + public void writeTo(FileDescriptor fd) throws IOException { + throw new IOException(); + } + + @Override + public void close() throws IOException { + } + + @Override + public long length() { + return 0; + } + }, DropBoxManager.IS_TEXT); + + DropBoxManager.Entry entry = dropbox.getNextEntry("DropBoxTest", before); + assertNull(entry); + } + public void testAddEntriesInTheFuture() throws Exception { File dir = getEmptyDir("testAddEntriesInTheFuture"); long before = System.currentTimeMillis(); |