Loading core/java/com/android/internal/util/ObjectUtils.java +8 −0 Original line number Diff line number Diff line Loading @@ -28,4 +28,12 @@ public class ObjectUtils { public static <T> T firstNotNull(@Nullable T a, @NonNull T b) { return a != null ? a : Preconditions.checkNotNull(b); } public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) { if (a != null) { return (b != null) ? a.compareTo(b) : 1; } else { return (b != null) ? -1 : 0; } } } core/tests/utiltests/src/com/android/internal/util/ObjectUtilsTest.java 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.internal.util; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; @SmallTest public class ObjectUtilsTest extends AndroidTestCase { public void testCompare() { assertEquals(0, ObjectUtils.compare(null, null)); assertEquals(1, ObjectUtils.compare("a", null)); assertEquals(-1, ObjectUtils.compare(null, "a")); assertEquals(0, ObjectUtils.compare("a", "a")); assertEquals(-1, ObjectUtils.compare("a", "b")); assertEquals(1, ObjectUtils.compare("b", "a")); } } services/core/java/com/android/server/DropBoxManagerService.java +148 −76 Original line number Diff line number Diff line Loading @@ -29,18 +29,23 @@ import android.os.Debug; import android.os.DropBoxManager; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.StatFs; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.text.format.Time; import android.util.ArrayMap; import android.util.Slog; import libcore.io.IoUtils; 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 java.io.BufferedOutputStream; import java.io.File; Loading @@ -52,7 +57,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; import java.util.zip.GZIPOutputStream; Loading Loading @@ -87,7 +92,7 @@ public final class DropBoxManagerService extends SystemService { // Accounting of all currently written log files (set in init()). private FileList mAllFiles = null; private HashMap<String, FileList> mFilesByTag = null; private ArrayMap<String, FileList> mFilesByTag = null; // Various bits of disk information Loading Loading @@ -153,7 +158,7 @@ public final class DropBoxManagerService extends SystemService { * @param context to use for receiving free space & gservices intents */ public DropBoxManagerService(final Context context) { this(context, new File("/data/system/dropbox")); this(context, new File("/data/system/dropbox"), FgThread.get().getLooper()); } /** Loading @@ -163,11 +168,12 @@ public final class DropBoxManagerService extends SystemService { * @param context to use for receiving free space & gservices intents * @param path to store drop box entries in */ public DropBoxManagerService(final Context context, File path) { @VisibleForTesting public DropBoxManagerService(final Context context, File path, Looper looper) { super(context); mDropBoxDir = path; mContentResolver = getContext().getContentResolver(); mHandler = new Handler() { mHandler = new Handler(looper) { @Override public void handleMessage(Message msg) { if (msg.what == MSG_SEND_BROADCAST) { Loading Loading @@ -338,11 +344,12 @@ public final class DropBoxManagerService extends SystemService { if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) { return new DropBoxManager.Entry(entry.tag, entry.timestampMillis); } final File file = entry.getFile(mDropBoxDir); try { return new DropBoxManager.Entry( entry.tag, entry.timestampMillis, entry.file, entry.flags); entry.tag, entry.timestampMillis, file, entry.flags); } catch (IOException e) { Slog.e(TAG, "Can't read: " + entry.file, e); Slog.wtf(TAG, "Can't read: " + file, e); // Continue to next file } } Loading Loading @@ -410,7 +417,9 @@ public final class DropBoxManagerService extends SystemService { numFound++; if (doPrint) out.append("========================================\n"); out.append(date).append(" ").append(entry.tag == null ? "(no tag)" : entry.tag); if (entry.file == null) { final File file = entry.getFile(mDropBoxDir); if (file == null) { out.append(" (no file)\n"); continue; } else if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) { Loading @@ -420,12 +429,12 @@ public final class DropBoxManagerService extends SystemService { out.append(" ("); if ((entry.flags & DropBoxManager.IS_GZIPPED) != 0) out.append("compressed "); out.append((entry.flags & DropBoxManager.IS_TEXT) != 0 ? "text" : "data"); out.append(", ").append(entry.file.length()).append(" bytes)\n"); out.append(", ").append(file.length()).append(" bytes)\n"); } if (doFile || (doPrint && (entry.flags & DropBoxManager.IS_TEXT) == 0)) { if (!doPrint) out.append(" "); out.append(entry.file.getPath()).append("\n"); out.append(file.getPath()).append("\n"); } if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) { Loading @@ -433,7 +442,7 @@ public final class DropBoxManagerService extends SystemService { InputStreamReader isr = null; try { dbe = new DropBoxManager.Entry( entry.tag, entry.timestampMillis, entry.file, entry.flags); entry.tag, entry.timestampMillis, file, entry.flags); if (doPrint) { isr = new InputStreamReader(dbe.getInputStream()); Loading Loading @@ -466,7 +475,7 @@ public final class DropBoxManagerService extends SystemService { } } catch (IOException e) { out.append("*** ").append(e.toString()).append("\n"); Slog.e(TAG, "Can't read: " + entry.file, e); Slog.e(TAG, "Can't read: " + file, e); } finally { if (dbe != null) dbe.close(); if (isr != null) { Loading Loading @@ -509,29 +518,37 @@ public final class DropBoxManagerService extends SystemService { } } /** Metadata describing an on-disk log file. */ private static final class EntryFile implements Comparable<EntryFile> { /** * Metadata describing an on-disk log file. * * Note its instances do no have knowledge on what directory they're stored, just to save * 4/8 bytes per instance. Instead, {@link #getFile} takes a directory so it can build a * fullpath. */ @VisibleForTesting static final class EntryFile implements Comparable<EntryFile> { public final String tag; public final long timestampMillis; public final int flags; public final File file; public final int blocks; /** Sorts earlier EntryFile instances before later ones. */ public final int compareTo(EntryFile o) { if (timestampMillis < o.timestampMillis) return -1; if (timestampMillis > o.timestampMillis) return 1; if (file != null && o.file != null) return file.compareTo(o.file); if (o.file != null) return -1; if (file != null) return 1; if (this == o) return 0; if (hashCode() < o.hashCode()) return -1; if (hashCode() > o.hashCode()) return 1; return 0; int comp = Long.compare(timestampMillis, o.timestampMillis); if (comp != 0) return comp; comp = ObjectUtils.compare(tag, o.tag); if (comp != 0) return comp; comp = Integer.compare(flags, o.flags); if (comp != 0) return comp; return Integer.compare(hashCode(), o.hashCode()); } /** * Moves an existing temporary file to a new log filename. * * @param temp file to rename * @param dir to store file in * @param tag to use for new log file name Loading @@ -544,55 +561,56 @@ public final class DropBoxManagerService extends SystemService { int flags, int blockSize) throws IOException { if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException(); this.tag = tag; this.tag = TextUtils.safeIntern(tag); this.timestampMillis = timestampMillis; this.flags = flags; this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") + ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : "")); if (!temp.renameTo(this.file)) { throw new IOException("Can't rename " + temp + " to " + this.file); final File file = this.getFile(dir); if (!temp.renameTo(file)) { throw new IOException("Can't rename " + temp + " to " + file); } this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize); this.blocks = (int) ((file.length() + blockSize - 1) / blockSize); } /** * Creates a zero-length tombstone for a file whose contents were lost. * * @param dir to store file in * @param tag to use for new log file name * @param timestampMillis of log entry * @throws IOException if the file can't be created. */ public EntryFile(File dir, String tag, long timestampMillis) throws IOException { this.tag = tag; this.tag = TextUtils.safeIntern(tag); this.timestampMillis = timestampMillis; this.flags = DropBoxManager.IS_EMPTY; this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ".lost"); this.blocks = 0; new FileOutputStream(this.file).close(); new FileOutputStream(getFile(dir)).close(); } /** * Extracts metadata from an existing on-disk log filename. * * Note when a filename is not recognizable, it will create an instance that * {@link #hasFile()} would return false on, and also remove the file. * * @param file name of existing log file * @param blockSize to use for space accounting */ public EntryFile(File file, int blockSize) { this.file = file; this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize); String name = file.getName(); int at = name.lastIndexOf('@'); if (at < 0) { this.tag = null; this.timestampMillis = 0; this.flags = DropBoxManager.IS_EMPTY; return; } boolean parseFailure = false; String name = file.getName(); int flags = 0; this.tag = Uri.decode(name.substring(0, at)); String tag = null; long millis = 0; final int at = name.lastIndexOf('@'); if (at < 0) { parseFailure = true; } else { tag = Uri.decode(name.substring(0, at)); if (name.endsWith(".gz")) { flags |= DropBoxManager.IS_GZIPPED; name = name.substring(0, name.length() - 3); Loading @@ -606,14 +624,31 @@ public final class DropBoxManagerService extends SystemService { } else if (name.endsWith(".dat")) { name = name.substring(at + 1, name.length() - 4); } else { parseFailure = true; } if (!parseFailure) { try { millis = Long.parseLong(name); } catch (NumberFormatException e) { parseFailure = true; } } } if (parseFailure) { Slog.wtf(TAG, "Invalid filename: " + file); // Remove the file and return an empty instance. file.delete(); this.tag = null; this.flags = DropBoxManager.IS_EMPTY; this.timestampMillis = 0; this.blocks = 0; return; } this.flags = flags; long millis; try { millis = Long.parseLong(name); } catch (NumberFormatException e) { millis = 0; } this.blocks = (int) ((file.length() + blockSize - 1) / blockSize); this.tag = TextUtils.safeIntern(tag); this.flags = flags; this.timestampMillis = millis; } Loading @@ -625,9 +660,50 @@ public final class DropBoxManagerService extends SystemService { this.tag = null; this.timestampMillis = millis; this.flags = DropBoxManager.IS_EMPTY; this.file = null; this.blocks = 0; } /** * @return whether an entry actually has a backing file, or it's an empty "tombstone" * entry. */ public boolean hasFile() { return tag != null; } /** @return File extension for the flags. */ private String getExtension() { if ((flags & DropBoxManager.IS_EMPTY) != 0) { return ".lost"; } return ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") + ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : ""); } /** * @return filename for this entry without the pathname. */ public String getFilename() { return hasFile() ? Uri.encode(tag) + "@" + timestampMillis + getExtension() : null; } /** * Get a full-path {@link File} representing this entry. * @param dir Parent directly. The caller needs to pass it because {@link EntryFile}s don't * know in which directory they're stored. */ public File getFile(File dir) { return hasFile() ? new File(dir, getFilename()) : null; } /** * If an entry has a backing file, remove it. */ public void deleteFile(File dir) { if (hasFile()) { getFile(dir).delete(); } } } /////////////////////////////////////////////////////////////////////////// Loading @@ -651,7 +727,7 @@ public final class DropBoxManagerService extends SystemService { if (files == null) throw new IOException("Can't list files: " + mDropBoxDir); mAllFiles = new FileList(); mFilesByTag = new HashMap<String, FileList>(); mFilesByTag = new ArrayMap<>(); // Scan pre-existing files. for (File file : files) { Loading @@ -662,19 +738,15 @@ public final class DropBoxManagerService extends SystemService { } EntryFile entry = new EntryFile(file, mBlockSize); if (entry.tag == null) { Slog.w(TAG, "Unrecognized file: " + file); continue; } else if (entry.timestampMillis == 0) { Slog.w(TAG, "Invalid filename: " + file); file.delete(); continue; } if (entry.hasFile()) { // Enroll only when the filename is valid. Otherwise the above constructor // has removed the file already. enrollEntry(entry); } } } } /** Adds a disk log file to in-memory tracking for accounting and enumeration. */ private synchronized void enrollEntry(EntryFile entry) { Loading @@ -684,11 +756,11 @@ public final class DropBoxManagerService extends SystemService { // mFilesByTag is used for trimming, so don't list empty files. // (Zero-length/lost files are trimmed by date from mAllFiles.) if (entry.tag != null && entry.file != null && entry.blocks > 0) { if (entry.hasFile() && entry.blocks > 0) { FileList tagFiles = mFilesByTag.get(entry.tag); if (tagFiles == null) { tagFiles = new FileList(); mFilesByTag.put(entry.tag, tagFiles); mFilesByTag.put(TextUtils.safeIntern(entry.tag), tagFiles); } tagFiles.contents.add(entry); tagFiles.blocks += entry.blocks; Loading Loading @@ -722,8 +794,8 @@ public final class DropBoxManagerService extends SystemService { tagFiles.blocks -= late.blocks; } if ((late.flags & DropBoxManager.IS_EMPTY) == 0) { enrollEntry(new EntryFile( late.file, mDropBoxDir, late.tag, t++, late.flags, mBlockSize)); enrollEntry(new EntryFile(late.getFile(mDropBoxDir), mDropBoxDir, late.tag, t++, late.flags, mBlockSize)); } else { enrollEntry(new EntryFile(mDropBoxDir, late.tag, t++)); } Loading Loading @@ -757,7 +829,7 @@ public final class DropBoxManagerService extends SystemService { FileList tag = mFilesByTag.get(entry.tag); if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks; if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks; if (entry.file != null) entry.file.delete(); entry.deleteFile(mDropBoxDir); } // Compute overall quota (a fraction of available free space) in blocks. Loading Loading @@ -823,7 +895,7 @@ public final class DropBoxManagerService extends SystemService { if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks; try { if (entry.file != null) entry.file.delete(); entry.deleteFile(mDropBoxDir); enrollEntry(new EntryFile(mDropBoxDir, entry.tag, entry.timestampMillis)); } catch (IOException e) { Slog.e(TAG, "Can't write tombstone file", e); Loading services/tests/servicestests/src/com/android/server/DropBoxTest.java +278 −19 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/com/android/internal/util/ObjectUtils.java +8 −0 Original line number Diff line number Diff line Loading @@ -28,4 +28,12 @@ public class ObjectUtils { public static <T> T firstNotNull(@Nullable T a, @NonNull T b) { return a != null ? a : Preconditions.checkNotNull(b); } public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) { if (a != null) { return (b != null) ? a.compareTo(b) : 1; } else { return (b != null) ? -1 : 0; } } }
core/tests/utiltests/src/com/android/internal/util/ObjectUtilsTest.java 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.internal.util; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; @SmallTest public class ObjectUtilsTest extends AndroidTestCase { public void testCompare() { assertEquals(0, ObjectUtils.compare(null, null)); assertEquals(1, ObjectUtils.compare("a", null)); assertEquals(-1, ObjectUtils.compare(null, "a")); assertEquals(0, ObjectUtils.compare("a", "a")); assertEquals(-1, ObjectUtils.compare("a", "b")); assertEquals(1, ObjectUtils.compare("b", "a")); } }
services/core/java/com/android/server/DropBoxManagerService.java +148 −76 Original line number Diff line number Diff line Loading @@ -29,18 +29,23 @@ import android.os.Debug; import android.os.DropBoxManager; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.StatFs; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.text.format.Time; import android.util.ArrayMap; import android.util.Slog; import libcore.io.IoUtils; 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 java.io.BufferedOutputStream; import java.io.File; Loading @@ -52,7 +57,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; import java.util.zip.GZIPOutputStream; Loading Loading @@ -87,7 +92,7 @@ public final class DropBoxManagerService extends SystemService { // Accounting of all currently written log files (set in init()). private FileList mAllFiles = null; private HashMap<String, FileList> mFilesByTag = null; private ArrayMap<String, FileList> mFilesByTag = null; // Various bits of disk information Loading Loading @@ -153,7 +158,7 @@ public final class DropBoxManagerService extends SystemService { * @param context to use for receiving free space & gservices intents */ public DropBoxManagerService(final Context context) { this(context, new File("/data/system/dropbox")); this(context, new File("/data/system/dropbox"), FgThread.get().getLooper()); } /** Loading @@ -163,11 +168,12 @@ public final class DropBoxManagerService extends SystemService { * @param context to use for receiving free space & gservices intents * @param path to store drop box entries in */ public DropBoxManagerService(final Context context, File path) { @VisibleForTesting public DropBoxManagerService(final Context context, File path, Looper looper) { super(context); mDropBoxDir = path; mContentResolver = getContext().getContentResolver(); mHandler = new Handler() { mHandler = new Handler(looper) { @Override public void handleMessage(Message msg) { if (msg.what == MSG_SEND_BROADCAST) { Loading Loading @@ -338,11 +344,12 @@ public final class DropBoxManagerService extends SystemService { if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) { return new DropBoxManager.Entry(entry.tag, entry.timestampMillis); } final File file = entry.getFile(mDropBoxDir); try { return new DropBoxManager.Entry( entry.tag, entry.timestampMillis, entry.file, entry.flags); entry.tag, entry.timestampMillis, file, entry.flags); } catch (IOException e) { Slog.e(TAG, "Can't read: " + entry.file, e); Slog.wtf(TAG, "Can't read: " + file, e); // Continue to next file } } Loading Loading @@ -410,7 +417,9 @@ public final class DropBoxManagerService extends SystemService { numFound++; if (doPrint) out.append("========================================\n"); out.append(date).append(" ").append(entry.tag == null ? "(no tag)" : entry.tag); if (entry.file == null) { final File file = entry.getFile(mDropBoxDir); if (file == null) { out.append(" (no file)\n"); continue; } else if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) { Loading @@ -420,12 +429,12 @@ public final class DropBoxManagerService extends SystemService { out.append(" ("); if ((entry.flags & DropBoxManager.IS_GZIPPED) != 0) out.append("compressed "); out.append((entry.flags & DropBoxManager.IS_TEXT) != 0 ? "text" : "data"); out.append(", ").append(entry.file.length()).append(" bytes)\n"); out.append(", ").append(file.length()).append(" bytes)\n"); } if (doFile || (doPrint && (entry.flags & DropBoxManager.IS_TEXT) == 0)) { if (!doPrint) out.append(" "); out.append(entry.file.getPath()).append("\n"); out.append(file.getPath()).append("\n"); } if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) { Loading @@ -433,7 +442,7 @@ public final class DropBoxManagerService extends SystemService { InputStreamReader isr = null; try { dbe = new DropBoxManager.Entry( entry.tag, entry.timestampMillis, entry.file, entry.flags); entry.tag, entry.timestampMillis, file, entry.flags); if (doPrint) { isr = new InputStreamReader(dbe.getInputStream()); Loading Loading @@ -466,7 +475,7 @@ public final class DropBoxManagerService extends SystemService { } } catch (IOException e) { out.append("*** ").append(e.toString()).append("\n"); Slog.e(TAG, "Can't read: " + entry.file, e); Slog.e(TAG, "Can't read: " + file, e); } finally { if (dbe != null) dbe.close(); if (isr != null) { Loading Loading @@ -509,29 +518,37 @@ public final class DropBoxManagerService extends SystemService { } } /** Metadata describing an on-disk log file. */ private static final class EntryFile implements Comparable<EntryFile> { /** * Metadata describing an on-disk log file. * * Note its instances do no have knowledge on what directory they're stored, just to save * 4/8 bytes per instance. Instead, {@link #getFile} takes a directory so it can build a * fullpath. */ @VisibleForTesting static final class EntryFile implements Comparable<EntryFile> { public final String tag; public final long timestampMillis; public final int flags; public final File file; public final int blocks; /** Sorts earlier EntryFile instances before later ones. */ public final int compareTo(EntryFile o) { if (timestampMillis < o.timestampMillis) return -1; if (timestampMillis > o.timestampMillis) return 1; if (file != null && o.file != null) return file.compareTo(o.file); if (o.file != null) return -1; if (file != null) return 1; if (this == o) return 0; if (hashCode() < o.hashCode()) return -1; if (hashCode() > o.hashCode()) return 1; return 0; int comp = Long.compare(timestampMillis, o.timestampMillis); if (comp != 0) return comp; comp = ObjectUtils.compare(tag, o.tag); if (comp != 0) return comp; comp = Integer.compare(flags, o.flags); if (comp != 0) return comp; return Integer.compare(hashCode(), o.hashCode()); } /** * Moves an existing temporary file to a new log filename. * * @param temp file to rename * @param dir to store file in * @param tag to use for new log file name Loading @@ -544,55 +561,56 @@ public final class DropBoxManagerService extends SystemService { int flags, int blockSize) throws IOException { if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException(); this.tag = tag; this.tag = TextUtils.safeIntern(tag); this.timestampMillis = timestampMillis; this.flags = flags; this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") + ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : "")); if (!temp.renameTo(this.file)) { throw new IOException("Can't rename " + temp + " to " + this.file); final File file = this.getFile(dir); if (!temp.renameTo(file)) { throw new IOException("Can't rename " + temp + " to " + file); } this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize); this.blocks = (int) ((file.length() + blockSize - 1) / blockSize); } /** * Creates a zero-length tombstone for a file whose contents were lost. * * @param dir to store file in * @param tag to use for new log file name * @param timestampMillis of log entry * @throws IOException if the file can't be created. */ public EntryFile(File dir, String tag, long timestampMillis) throws IOException { this.tag = tag; this.tag = TextUtils.safeIntern(tag); this.timestampMillis = timestampMillis; this.flags = DropBoxManager.IS_EMPTY; this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ".lost"); this.blocks = 0; new FileOutputStream(this.file).close(); new FileOutputStream(getFile(dir)).close(); } /** * Extracts metadata from an existing on-disk log filename. * * Note when a filename is not recognizable, it will create an instance that * {@link #hasFile()} would return false on, and also remove the file. * * @param file name of existing log file * @param blockSize to use for space accounting */ public EntryFile(File file, int blockSize) { this.file = file; this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize); String name = file.getName(); int at = name.lastIndexOf('@'); if (at < 0) { this.tag = null; this.timestampMillis = 0; this.flags = DropBoxManager.IS_EMPTY; return; } boolean parseFailure = false; String name = file.getName(); int flags = 0; this.tag = Uri.decode(name.substring(0, at)); String tag = null; long millis = 0; final int at = name.lastIndexOf('@'); if (at < 0) { parseFailure = true; } else { tag = Uri.decode(name.substring(0, at)); if (name.endsWith(".gz")) { flags |= DropBoxManager.IS_GZIPPED; name = name.substring(0, name.length() - 3); Loading @@ -606,14 +624,31 @@ public final class DropBoxManagerService extends SystemService { } else if (name.endsWith(".dat")) { name = name.substring(at + 1, name.length() - 4); } else { parseFailure = true; } if (!parseFailure) { try { millis = Long.parseLong(name); } catch (NumberFormatException e) { parseFailure = true; } } } if (parseFailure) { Slog.wtf(TAG, "Invalid filename: " + file); // Remove the file and return an empty instance. file.delete(); this.tag = null; this.flags = DropBoxManager.IS_EMPTY; this.timestampMillis = 0; this.blocks = 0; return; } this.flags = flags; long millis; try { millis = Long.parseLong(name); } catch (NumberFormatException e) { millis = 0; } this.blocks = (int) ((file.length() + blockSize - 1) / blockSize); this.tag = TextUtils.safeIntern(tag); this.flags = flags; this.timestampMillis = millis; } Loading @@ -625,9 +660,50 @@ public final class DropBoxManagerService extends SystemService { this.tag = null; this.timestampMillis = millis; this.flags = DropBoxManager.IS_EMPTY; this.file = null; this.blocks = 0; } /** * @return whether an entry actually has a backing file, or it's an empty "tombstone" * entry. */ public boolean hasFile() { return tag != null; } /** @return File extension for the flags. */ private String getExtension() { if ((flags & DropBoxManager.IS_EMPTY) != 0) { return ".lost"; } return ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") + ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : ""); } /** * @return filename for this entry without the pathname. */ public String getFilename() { return hasFile() ? Uri.encode(tag) + "@" + timestampMillis + getExtension() : null; } /** * Get a full-path {@link File} representing this entry. * @param dir Parent directly. The caller needs to pass it because {@link EntryFile}s don't * know in which directory they're stored. */ public File getFile(File dir) { return hasFile() ? new File(dir, getFilename()) : null; } /** * If an entry has a backing file, remove it. */ public void deleteFile(File dir) { if (hasFile()) { getFile(dir).delete(); } } } /////////////////////////////////////////////////////////////////////////// Loading @@ -651,7 +727,7 @@ public final class DropBoxManagerService extends SystemService { if (files == null) throw new IOException("Can't list files: " + mDropBoxDir); mAllFiles = new FileList(); mFilesByTag = new HashMap<String, FileList>(); mFilesByTag = new ArrayMap<>(); // Scan pre-existing files. for (File file : files) { Loading @@ -662,19 +738,15 @@ public final class DropBoxManagerService extends SystemService { } EntryFile entry = new EntryFile(file, mBlockSize); if (entry.tag == null) { Slog.w(TAG, "Unrecognized file: " + file); continue; } else if (entry.timestampMillis == 0) { Slog.w(TAG, "Invalid filename: " + file); file.delete(); continue; } if (entry.hasFile()) { // Enroll only when the filename is valid. Otherwise the above constructor // has removed the file already. enrollEntry(entry); } } } } /** Adds a disk log file to in-memory tracking for accounting and enumeration. */ private synchronized void enrollEntry(EntryFile entry) { Loading @@ -684,11 +756,11 @@ public final class DropBoxManagerService extends SystemService { // mFilesByTag is used for trimming, so don't list empty files. // (Zero-length/lost files are trimmed by date from mAllFiles.) if (entry.tag != null && entry.file != null && entry.blocks > 0) { if (entry.hasFile() && entry.blocks > 0) { FileList tagFiles = mFilesByTag.get(entry.tag); if (tagFiles == null) { tagFiles = new FileList(); mFilesByTag.put(entry.tag, tagFiles); mFilesByTag.put(TextUtils.safeIntern(entry.tag), tagFiles); } tagFiles.contents.add(entry); tagFiles.blocks += entry.blocks; Loading Loading @@ -722,8 +794,8 @@ public final class DropBoxManagerService extends SystemService { tagFiles.blocks -= late.blocks; } if ((late.flags & DropBoxManager.IS_EMPTY) == 0) { enrollEntry(new EntryFile( late.file, mDropBoxDir, late.tag, t++, late.flags, mBlockSize)); enrollEntry(new EntryFile(late.getFile(mDropBoxDir), mDropBoxDir, late.tag, t++, late.flags, mBlockSize)); } else { enrollEntry(new EntryFile(mDropBoxDir, late.tag, t++)); } Loading Loading @@ -757,7 +829,7 @@ public final class DropBoxManagerService extends SystemService { FileList tag = mFilesByTag.get(entry.tag); if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks; if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks; if (entry.file != null) entry.file.delete(); entry.deleteFile(mDropBoxDir); } // Compute overall quota (a fraction of available free space) in blocks. Loading Loading @@ -823,7 +895,7 @@ public final class DropBoxManagerService extends SystemService { if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks; try { if (entry.file != null) entry.file.delete(); entry.deleteFile(mDropBoxDir); enrollEntry(new EntryFile(mDropBoxDir, entry.tag, entry.timestampMillis)); } catch (IOException e) { Slog.e(TAG, "Can't write tombstone file", e); Loading
services/tests/servicestests/src/com/android/server/DropBoxTest.java +278 −19 File changed.Preview size limit exceeded, changes collapsed. Show changes