1 /* 2 * Copyright (C) 2010 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 17 package android.app; 18 19 import android.annotation.Nullable; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.SharedPreferences; 22 import android.os.FileUtils; 23 import android.os.Looper; 24 import android.system.ErrnoException; 25 import android.system.Os; 26 import android.system.StructStat; 27 import android.system.StructTimespec; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.util.ExponentiallyBucketedHistogram; 32 import com.android.internal.util.XmlUtils; 33 34 import dalvik.system.BlockGuard; 35 36 import libcore.io.IoUtils; 37 38 import org.xmlpull.v1.XmlPullParserException; 39 40 import java.io.BufferedInputStream; 41 import java.io.File; 42 import java.io.FileInputStream; 43 import java.io.FileNotFoundException; 44 import java.io.FileOutputStream; 45 import java.io.IOException; 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Set; 52 import java.util.WeakHashMap; 53 import java.util.concurrent.CountDownLatch; 54 55 final class SharedPreferencesImpl implements SharedPreferences { 56 private static final String TAG = "SharedPreferencesImpl"; 57 private static final boolean DEBUG = false; 58 private static final Object CONTENT = new Object(); 59 60 /** If a fsync takes more than {@value #MAX_FSYNC_DURATION_MILLIS} ms, warn */ 61 private static final long MAX_FSYNC_DURATION_MILLIS = 256; 62 63 // Lock ordering rules: 64 // - acquire SharedPreferencesImpl.mLock before EditorImpl.mLock 65 // - acquire mWritingToDiskLock before EditorImpl.mLock 66 67 @UnsupportedAppUsage 68 private final File mFile; 69 private final File mBackupFile; 70 private final int mMode; 71 private final Object mLock = new Object(); 72 private final Object mWritingToDiskLock = new Object(); 73 74 @GuardedBy("mLock") 75 private Map<String, Object> mMap; 76 @GuardedBy("mLock") 77 private Throwable mThrowable; 78 79 @GuardedBy("mLock") 80 private int mDiskWritesInFlight = 0; 81 82 @GuardedBy("mLock") 83 private boolean mLoaded = false; 84 85 @GuardedBy("mLock") 86 private StructTimespec mStatTimestamp; 87 88 @GuardedBy("mLock") 89 private long mStatSize; 90 91 @GuardedBy("mLock") 92 private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners = 93 new WeakHashMap<OnSharedPreferenceChangeListener, Object>(); 94 95 /** Current memory state (always increasing) */ 96 @GuardedBy("this") 97 private long mCurrentMemoryStateGeneration; 98 99 /** Latest memory state that was committed to disk */ 100 @GuardedBy("mWritingToDiskLock") 101 private long mDiskStateGeneration; 102 103 /** Time (and number of instances) of file-system sync requests */ 104 @GuardedBy("mWritingToDiskLock") 105 private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16); 106 private int mNumSync = 0; 107 108 @UnsupportedAppUsage SharedPreferencesImpl(File file, int mode)109 SharedPreferencesImpl(File file, int mode) { 110 mFile = file; 111 mBackupFile = makeBackupFile(file); 112 mMode = mode; 113 mLoaded = false; 114 mMap = null; 115 mThrowable = null; 116 startLoadFromDisk(); 117 } 118 119 @UnsupportedAppUsage startLoadFromDisk()120 private void startLoadFromDisk() { 121 synchronized (mLock) { 122 mLoaded = false; 123 } 124 new Thread("SharedPreferencesImpl-load") { 125 public void run() { 126 loadFromDisk(); 127 } 128 }.start(); 129 } 130 loadFromDisk()131 private void loadFromDisk() { 132 synchronized (mLock) { 133 if (mLoaded) { 134 return; 135 } 136 if (mBackupFile.exists()) { 137 mFile.delete(); 138 mBackupFile.renameTo(mFile); 139 } 140 } 141 142 // Debugging 143 if (mFile.exists() && !mFile.canRead()) { 144 Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission"); 145 } 146 147 Map<String, Object> map = null; 148 StructStat stat = null; 149 Throwable thrown = null; 150 try { 151 stat = Os.stat(mFile.getPath()); 152 if (mFile.canRead()) { 153 BufferedInputStream str = null; 154 try { 155 str = new BufferedInputStream( 156 new FileInputStream(mFile), 16 * 1024); 157 map = (Map<String, Object>) XmlUtils.readMapXml(str); 158 } catch (Exception e) { 159 Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); 160 } finally { 161 IoUtils.closeQuietly(str); 162 } 163 } 164 } catch (ErrnoException e) { 165 // An errno exception means the stat failed. Treat as empty/non-existing by 166 // ignoring. 167 } catch (Throwable t) { 168 thrown = t; 169 } 170 171 synchronized (mLock) { 172 mLoaded = true; 173 mThrowable = thrown; 174 175 // It's important that we always signal waiters, even if we'll make 176 // them fail with an exception. The try-finally is pretty wide, but 177 // better safe than sorry. 178 try { 179 if (thrown == null) { 180 if (map != null) { 181 mMap = map; 182 mStatTimestamp = stat.st_mtim; 183 mStatSize = stat.st_size; 184 } else { 185 mMap = new HashMap<>(); 186 } 187 } 188 // In case of a thrown exception, we retain the old map. That allows 189 // any open editors to commit and store updates. 190 } catch (Throwable t) { 191 mThrowable = t; 192 } finally { 193 mLock.notifyAll(); 194 } 195 } 196 } 197 makeBackupFile(File prefsFile)198 static File makeBackupFile(File prefsFile) { 199 return new File(prefsFile.getPath() + ".bak"); 200 } 201 202 @UnsupportedAppUsage startReloadIfChangedUnexpectedly()203 void startReloadIfChangedUnexpectedly() { 204 synchronized (mLock) { 205 // TODO: wait for any pending writes to disk? 206 if (!hasFileChangedUnexpectedly()) { 207 return; 208 } 209 startLoadFromDisk(); 210 } 211 } 212 213 // Has the file changed out from under us? i.e. writes that 214 // we didn't instigate. hasFileChangedUnexpectedly()215 private boolean hasFileChangedUnexpectedly() { 216 synchronized (mLock) { 217 if (mDiskWritesInFlight > 0) { 218 // If we know we caused it, it's not unexpected. 219 if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected."); 220 return false; 221 } 222 } 223 224 final StructStat stat; 225 try { 226 /* 227 * Metadata operations don't usually count as a block guard 228 * violation, but we explicitly want this one. 229 */ 230 BlockGuard.getThreadPolicy().onReadFromDisk(); 231 stat = Os.stat(mFile.getPath()); 232 } catch (ErrnoException e) { 233 return true; 234 } 235 236 synchronized (mLock) { 237 return !stat.st_mtim.equals(mStatTimestamp) || mStatSize != stat.st_size; 238 } 239 } 240 241 @Override registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)242 public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { 243 synchronized(mLock) { 244 mListeners.put(listener, CONTENT); 245 } 246 } 247 248 @Override unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)249 public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { 250 synchronized(mLock) { 251 mListeners.remove(listener); 252 } 253 } 254 255 @GuardedBy("mLock") awaitLoadedLocked()256 private void awaitLoadedLocked() { 257 if (!mLoaded) { 258 // Raise an explicit StrictMode onReadFromDisk for this 259 // thread, since the real read will be in a different 260 // thread and otherwise ignored by StrictMode. 261 BlockGuard.getThreadPolicy().onReadFromDisk(); 262 } 263 while (!mLoaded) { 264 try { 265 mLock.wait(); 266 } catch (InterruptedException unused) { 267 } 268 } 269 if (mThrowable != null) { 270 throw new IllegalStateException(mThrowable); 271 } 272 } 273 274 @Override getAll()275 public Map<String, ?> getAll() { 276 synchronized (mLock) { 277 awaitLoadedLocked(); 278 //noinspection unchecked 279 return new HashMap<String, Object>(mMap); 280 } 281 } 282 283 @Override 284 @Nullable getString(String key, @Nullable String defValue)285 public String getString(String key, @Nullable String defValue) { 286 synchronized (mLock) { 287 awaitLoadedLocked(); 288 String v = (String)mMap.get(key); 289 return v != null ? v : defValue; 290 } 291 } 292 293 @Override 294 @Nullable getStringSet(String key, @Nullable Set<String> defValues)295 public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { 296 synchronized (mLock) { 297 awaitLoadedLocked(); 298 Set<String> v = (Set<String>) mMap.get(key); 299 return v != null ? v : defValues; 300 } 301 } 302 303 @Override getInt(String key, int defValue)304 public int getInt(String key, int defValue) { 305 synchronized (mLock) { 306 awaitLoadedLocked(); 307 Integer v = (Integer)mMap.get(key); 308 return v != null ? v : defValue; 309 } 310 } 311 @Override getLong(String key, long defValue)312 public long getLong(String key, long defValue) { 313 synchronized (mLock) { 314 awaitLoadedLocked(); 315 Long v = (Long)mMap.get(key); 316 return v != null ? v : defValue; 317 } 318 } 319 @Override getFloat(String key, float defValue)320 public float getFloat(String key, float defValue) { 321 synchronized (mLock) { 322 awaitLoadedLocked(); 323 Float v = (Float)mMap.get(key); 324 return v != null ? v : defValue; 325 } 326 } 327 @Override getBoolean(String key, boolean defValue)328 public boolean getBoolean(String key, boolean defValue) { 329 synchronized (mLock) { 330 awaitLoadedLocked(); 331 Boolean v = (Boolean)mMap.get(key); 332 return v != null ? v : defValue; 333 } 334 } 335 336 @Override contains(String key)337 public boolean contains(String key) { 338 synchronized (mLock) { 339 awaitLoadedLocked(); 340 return mMap.containsKey(key); 341 } 342 } 343 344 @Override edit()345 public Editor edit() { 346 // TODO: remove the need to call awaitLoadedLocked() when 347 // requesting an editor. will require some work on the 348 // Editor, but then we should be able to do: 349 // 350 // context.getSharedPreferences(..).edit().putString(..).apply() 351 // 352 // ... all without blocking. 353 synchronized (mLock) { 354 awaitLoadedLocked(); 355 } 356 357 return new EditorImpl(); 358 } 359 360 // Return value from EditorImpl#commitToMemory() 361 private static class MemoryCommitResult { 362 final long memoryStateGeneration; 363 @Nullable final List<String> keysModified; 364 @Nullable final Set<OnSharedPreferenceChangeListener> listeners; 365 final Map<String, Object> mapToWriteToDisk; 366 final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); 367 368 @GuardedBy("mWritingToDiskLock") 369 volatile boolean writeToDiskResult = false; 370 boolean wasWritten = false; 371 MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified, @Nullable Set<OnSharedPreferenceChangeListener> listeners, Map<String, Object> mapToWriteToDisk)372 private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified, 373 @Nullable Set<OnSharedPreferenceChangeListener> listeners, 374 Map<String, Object> mapToWriteToDisk) { 375 this.memoryStateGeneration = memoryStateGeneration; 376 this.keysModified = keysModified; 377 this.listeners = listeners; 378 this.mapToWriteToDisk = mapToWriteToDisk; 379 } 380 setDiskWriteResult(boolean wasWritten, boolean result)381 void setDiskWriteResult(boolean wasWritten, boolean result) { 382 this.wasWritten = wasWritten; 383 writeToDiskResult = result; 384 writtenToDiskLatch.countDown(); 385 } 386 } 387 388 public final class EditorImpl implements Editor { 389 private final Object mEditorLock = new Object(); 390 391 @GuardedBy("mEditorLock") 392 private final Map<String, Object> mModified = new HashMap<>(); 393 394 @GuardedBy("mEditorLock") 395 private boolean mClear = false; 396 397 @Override putString(String key, @Nullable String value)398 public Editor putString(String key, @Nullable String value) { 399 synchronized (mEditorLock) { 400 mModified.put(key, value); 401 return this; 402 } 403 } 404 @Override putStringSet(String key, @Nullable Set<String> values)405 public Editor putStringSet(String key, @Nullable Set<String> values) { 406 synchronized (mEditorLock) { 407 mModified.put(key, 408 (values == null) ? null : new HashSet<String>(values)); 409 return this; 410 } 411 } 412 @Override putInt(String key, int value)413 public Editor putInt(String key, int value) { 414 synchronized (mEditorLock) { 415 mModified.put(key, value); 416 return this; 417 } 418 } 419 @Override putLong(String key, long value)420 public Editor putLong(String key, long value) { 421 synchronized (mEditorLock) { 422 mModified.put(key, value); 423 return this; 424 } 425 } 426 @Override putFloat(String key, float value)427 public Editor putFloat(String key, float value) { 428 synchronized (mEditorLock) { 429 mModified.put(key, value); 430 return this; 431 } 432 } 433 @Override putBoolean(String key, boolean value)434 public Editor putBoolean(String key, boolean value) { 435 synchronized (mEditorLock) { 436 mModified.put(key, value); 437 return this; 438 } 439 } 440 441 @Override remove(String key)442 public Editor remove(String key) { 443 synchronized (mEditorLock) { 444 mModified.put(key, this); 445 return this; 446 } 447 } 448 449 @Override clear()450 public Editor clear() { 451 synchronized (mEditorLock) { 452 mClear = true; 453 return this; 454 } 455 } 456 457 @Override apply()458 public void apply() { 459 final long startTime = System.currentTimeMillis(); 460 461 final MemoryCommitResult mcr = commitToMemory(); 462 final Runnable awaitCommit = new Runnable() { 463 @Override 464 public void run() { 465 try { 466 mcr.writtenToDiskLatch.await(); 467 } catch (InterruptedException ignored) { 468 } 469 470 if (DEBUG && mcr.wasWritten) { 471 Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration 472 + " applied after " + (System.currentTimeMillis() - startTime) 473 + " ms"); 474 } 475 } 476 }; 477 478 QueuedWork.addFinisher(awaitCommit); 479 480 Runnable postWriteRunnable = new Runnable() { 481 @Override 482 public void run() { 483 awaitCommit.run(); 484 QueuedWork.removeFinisher(awaitCommit); 485 } 486 }; 487 488 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); 489 490 // Okay to notify the listeners before it's hit disk 491 // because the listeners should always get the same 492 // SharedPreferences instance back, which has the 493 // changes reflected in memory. 494 notifyListeners(mcr); 495 } 496 497 // Returns true if any changes were made commitToMemory()498 private MemoryCommitResult commitToMemory() { 499 long memoryStateGeneration; 500 List<String> keysModified = null; 501 Set<OnSharedPreferenceChangeListener> listeners = null; 502 Map<String, Object> mapToWriteToDisk; 503 504 synchronized (SharedPreferencesImpl.this.mLock) { 505 // We optimistically don't make a deep copy until 506 // a memory commit comes in when we're already 507 // writing to disk. 508 if (mDiskWritesInFlight > 0) { 509 // We can't modify our mMap as a currently 510 // in-flight write owns it. Clone it before 511 // modifying it. 512 // noinspection unchecked 513 mMap = new HashMap<String, Object>(mMap); 514 } 515 mapToWriteToDisk = mMap; 516 mDiskWritesInFlight++; 517 518 boolean hasListeners = mListeners.size() > 0; 519 if (hasListeners) { 520 keysModified = new ArrayList<String>(); 521 listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); 522 } 523 524 synchronized (mEditorLock) { 525 boolean changesMade = false; 526 527 if (mClear) { 528 if (!mapToWriteToDisk.isEmpty()) { 529 changesMade = true; 530 mapToWriteToDisk.clear(); 531 } 532 mClear = false; 533 } 534 535 for (Map.Entry<String, Object> e : mModified.entrySet()) { 536 String k = e.getKey(); 537 Object v = e.getValue(); 538 // "this" is the magic value for a removal mutation. In addition, 539 // setting a value to "null" for a given key is specified to be 540 // equivalent to calling remove on that key. 541 if (v == this || v == null) { 542 if (!mapToWriteToDisk.containsKey(k)) { 543 continue; 544 } 545 mapToWriteToDisk.remove(k); 546 } else { 547 if (mapToWriteToDisk.containsKey(k)) { 548 Object existingValue = mapToWriteToDisk.get(k); 549 if (existingValue != null && existingValue.equals(v)) { 550 continue; 551 } 552 } 553 mapToWriteToDisk.put(k, v); 554 } 555 556 changesMade = true; 557 if (hasListeners) { 558 keysModified.add(k); 559 } 560 } 561 562 mModified.clear(); 563 564 if (changesMade) { 565 mCurrentMemoryStateGeneration++; 566 } 567 568 memoryStateGeneration = mCurrentMemoryStateGeneration; 569 } 570 } 571 return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, 572 mapToWriteToDisk); 573 } 574 575 @Override commit()576 public boolean commit() { 577 long startTime = 0; 578 579 if (DEBUG) { 580 startTime = System.currentTimeMillis(); 581 } 582 583 MemoryCommitResult mcr = commitToMemory(); 584 585 SharedPreferencesImpl.this.enqueueDiskWrite( 586 mcr, null /* sync write on this thread okay */); 587 try { 588 mcr.writtenToDiskLatch.await(); 589 } catch (InterruptedException e) { 590 return false; 591 } finally { 592 if (DEBUG) { 593 Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration 594 + " committed after " + (System.currentTimeMillis() - startTime) 595 + " ms"); 596 } 597 } 598 notifyListeners(mcr); 599 return mcr.writeToDiskResult; 600 } 601 notifyListeners(final MemoryCommitResult mcr)602 private void notifyListeners(final MemoryCommitResult mcr) { 603 if (mcr.listeners == null || mcr.keysModified == null || 604 mcr.keysModified.size() == 0) { 605 return; 606 } 607 if (Looper.myLooper() == Looper.getMainLooper()) { 608 for (int i = mcr.keysModified.size() - 1; i >= 0; i--) { 609 final String key = mcr.keysModified.get(i); 610 for (OnSharedPreferenceChangeListener listener : mcr.listeners) { 611 if (listener != null) { 612 listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key); 613 } 614 } 615 } 616 } else { 617 // Run this function on the main thread. 618 ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr)); 619 } 620 } 621 } 622 623 /** 624 * Enqueue an already-committed-to-memory result to be written 625 * to disk. 626 * 627 * They will be written to disk one-at-a-time in the order 628 * that they're enqueued. 629 * 630 * @param postWriteRunnable if non-null, we're being called 631 * from apply() and this is the runnable to run after 632 * the write proceeds. if null (from a regular commit()), 633 * then we're allowed to do this disk write on the main 634 * thread (which in addition to reducing allocations and 635 * creating a background thread, this has the advantage that 636 * we catch them in userdebug StrictMode reports to convert 637 * them where possible to apply() ...) 638 */ enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable)639 private void enqueueDiskWrite(final MemoryCommitResult mcr, 640 final Runnable postWriteRunnable) { 641 final boolean isFromSyncCommit = (postWriteRunnable == null); 642 643 final Runnable writeToDiskRunnable = new Runnable() { 644 @Override 645 public void run() { 646 synchronized (mWritingToDiskLock) { 647 writeToFile(mcr, isFromSyncCommit); 648 } 649 synchronized (mLock) { 650 mDiskWritesInFlight--; 651 } 652 if (postWriteRunnable != null) { 653 postWriteRunnable.run(); 654 } 655 } 656 }; 657 658 // Typical #commit() path with fewer allocations, doing a write on 659 // the current thread. 660 if (isFromSyncCommit) { 661 boolean wasEmpty = false; 662 synchronized (mLock) { 663 wasEmpty = mDiskWritesInFlight == 1; 664 } 665 if (wasEmpty) { 666 writeToDiskRunnable.run(); 667 return; 668 } 669 } 670 671 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); 672 } 673 createFileOutputStream(File file)674 private static FileOutputStream createFileOutputStream(File file) { 675 FileOutputStream str = null; 676 try { 677 str = new FileOutputStream(file); 678 } catch (FileNotFoundException e) { 679 File parent = file.getParentFile(); 680 if (!parent.mkdir()) { 681 Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file); 682 return null; 683 } 684 FileUtils.setPermissions( 685 parent.getPath(), 686 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, 687 -1, -1); 688 try { 689 str = new FileOutputStream(file); 690 } catch (FileNotFoundException e2) { 691 Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2); 692 } 693 } 694 return str; 695 } 696 697 @GuardedBy("mWritingToDiskLock") writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit)698 private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) { 699 long startTime = 0; 700 long existsTime = 0; 701 long backupExistsTime = 0; 702 long outputStreamCreateTime = 0; 703 long writeTime = 0; 704 long fsyncTime = 0; 705 long setPermTime = 0; 706 long fstatTime = 0; 707 long deleteTime = 0; 708 709 if (DEBUG) { 710 startTime = System.currentTimeMillis(); 711 } 712 713 boolean fileExists = mFile.exists(); 714 715 if (DEBUG) { 716 existsTime = System.currentTimeMillis(); 717 718 // Might not be set, hence init them to a default value 719 backupExistsTime = existsTime; 720 } 721 722 // Rename the current file so it may be used as a backup during the next read 723 if (fileExists) { 724 boolean needsWrite = false; 725 726 // Only need to write if the disk state is older than this commit 727 if (mDiskStateGeneration < mcr.memoryStateGeneration) { 728 if (isFromSyncCommit) { 729 needsWrite = true; 730 } else { 731 synchronized (mLock) { 732 // No need to persist intermediate states. Just wait for the latest state to 733 // be persisted. 734 if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) { 735 needsWrite = true; 736 } 737 } 738 } 739 } 740 741 if (!needsWrite) { 742 mcr.setDiskWriteResult(false, true); 743 return; 744 } 745 746 boolean backupFileExists = mBackupFile.exists(); 747 748 if (DEBUG) { 749 backupExistsTime = System.currentTimeMillis(); 750 } 751 752 if (!backupFileExists) { 753 if (!mFile.renameTo(mBackupFile)) { 754 Log.e(TAG, "Couldn't rename file " + mFile 755 + " to backup file " + mBackupFile); 756 mcr.setDiskWriteResult(false, false); 757 return; 758 } 759 } else { 760 mFile.delete(); 761 } 762 } 763 764 // Attempt to write the file, delete the backup and return true as atomically as 765 // possible. If any exception occurs, delete the new file; next time we will restore 766 // from the backup. 767 try { 768 FileOutputStream str = createFileOutputStream(mFile); 769 770 if (DEBUG) { 771 outputStreamCreateTime = System.currentTimeMillis(); 772 } 773 774 if (str == null) { 775 mcr.setDiskWriteResult(false, false); 776 return; 777 } 778 XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); 779 780 writeTime = System.currentTimeMillis(); 781 782 FileUtils.sync(str); 783 784 fsyncTime = System.currentTimeMillis(); 785 786 str.close(); 787 ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); 788 789 if (DEBUG) { 790 setPermTime = System.currentTimeMillis(); 791 } 792 793 try { 794 final StructStat stat = Os.stat(mFile.getPath()); 795 synchronized (mLock) { 796 mStatTimestamp = stat.st_mtim; 797 mStatSize = stat.st_size; 798 } 799 } catch (ErrnoException e) { 800 // Do nothing 801 } 802 803 if (DEBUG) { 804 fstatTime = System.currentTimeMillis(); 805 } 806 807 // Writing was successful, delete the backup file if there is one. 808 mBackupFile.delete(); 809 810 if (DEBUG) { 811 deleteTime = System.currentTimeMillis(); 812 } 813 814 mDiskStateGeneration = mcr.memoryStateGeneration; 815 816 mcr.setDiskWriteResult(true, true); 817 818 if (DEBUG) { 819 Log.d(TAG, "write: " + (existsTime - startTime) + "/" 820 + (backupExistsTime - startTime) + "/" 821 + (outputStreamCreateTime - startTime) + "/" 822 + (writeTime - startTime) + "/" 823 + (fsyncTime - startTime) + "/" 824 + (setPermTime - startTime) + "/" 825 + (fstatTime - startTime) + "/" 826 + (deleteTime - startTime)); 827 } 828 829 long fsyncDuration = fsyncTime - writeTime; 830 mSyncTimes.add((int) fsyncDuration); 831 mNumSync++; 832 833 if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) { 834 mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": "); 835 } 836 837 return; 838 } catch (XmlPullParserException e) { 839 Log.w(TAG, "writeToFile: Got exception:", e); 840 } catch (IOException e) { 841 Log.w(TAG, "writeToFile: Got exception:", e); 842 } 843 844 // Clean up an unsuccessfully written file 845 if (mFile.exists()) { 846 if (!mFile.delete()) { 847 Log.e(TAG, "Couldn't clean up partially-written file " + mFile); 848 } 849 } 850 mcr.setDiskWriteResult(false, false); 851 } 852 } 853