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