Skip to content

Commit 39606a0

Browse files
author
Dianne Hackborn
committed
Make AtomicFile a public API. It's about time!
Change-Id: Ib34e294747405b7ab709cb0bbb2d9a0cc80ce86a
1 parent d913cf1 commit 39606a0

File tree

13 files changed

+263
-12
lines changed

13 files changed

+263
-12
lines changed

api/current.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22524,6 +22524,17 @@ package android.util {
2252422524
ctor public AndroidRuntimeException(java.lang.Exception);
2252522525
}
2252622526

22527+
public class AtomicFile {
22528+
ctor public AtomicFile(java.io.File);
22529+
method public void delete();
22530+
method public void failWrite(java.io.FileOutputStream);
22531+
method public void finishWrite(java.io.FileOutputStream);
22532+
method public java.io.File getBaseFile();
22533+
method public java.io.FileInputStream openRead() throws java.io.FileNotFoundException;
22534+
method public byte[] readFully() throws java.io.IOException;
22535+
method public java.io.FileOutputStream startWrite() throws java.io.IOException;
22536+
}
22537+
2252722538
public abstract interface AttributeSet {
2252822539
method public abstract boolean getAttributeBooleanValue(java.lang.String, java.lang.String, boolean);
2252922540
method public abstract boolean getAttributeBooleanValue(int, boolean);

core/java/android/content/SyncStorageEngine.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package android.content;
1818

19-
import com.android.internal.os.AtomicFile;
2019
import com.android.internal.util.ArrayUtils;
2120
import com.android.internal.util.FastXmlSerializer;
2221

@@ -37,7 +36,7 @@
3736
import android.os.Parcel;
3837
import android.os.RemoteCallbackList;
3938
import android.os.RemoteException;
40-
import android.os.SystemClock;
39+
import android.util.AtomicFile;
4140
import android.util.Log;
4241
import android.util.SparseArray;
4342
import android.util.Xml;

core/java/android/content/pm/RegisteredServicesCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import android.content.res.XmlResourceParser;
2727
import android.os.Environment;
2828
import android.os.Handler;
29+
import android.util.AtomicFile;
2930
import android.util.Log;
3031
import android.util.AttributeSet;
3132
import android.util.Xml;
@@ -44,7 +45,6 @@
4445
import java.io.IOException;
4546
import java.io.FileInputStream;
4647

47-
import com.android.internal.os.AtomicFile;
4848
import com.android.internal.util.FastXmlSerializer;
4949

5050
import com.google.android.collect.Maps;
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
* Copyright (C) 2009 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.util;
18+
19+
import android.os.FileUtils;
20+
import android.util.Log;
21+
22+
import java.io.File;
23+
import java.io.FileInputStream;
24+
import java.io.FileNotFoundException;
25+
import java.io.FileOutputStream;
26+
import java.io.IOException;
27+
28+
/**
29+
* Helper class for performing atomic operations on a file by creating a
30+
* backup file until a write has successfully completed. If you need this
31+
* on older versions of the platform you can use
32+
* {@link android.support.v4.util.AtomicFile} in the v4 support library.
33+
* <p>
34+
* Atomic file guarantees file integrity by ensuring that a file has
35+
* been completely written and sync'd to disk before removing its backup.
36+
* As long as the backup file exists, the original file is considered
37+
* to be invalid (left over from a previous attempt to write the file).
38+
* </p><p>
39+
* Atomic file does not confer any file locking semantics.
40+
* Do not use this class when the file may be accessed or modified concurrently
41+
* by multiple threads or processes. The caller is responsible for ensuring
42+
* appropriate mutual exclusion invariants whenever it accesses the file.
43+
* </p>
44+
*/
45+
public class AtomicFile {
46+
private final File mBaseName;
47+
private final File mBackupName;
48+
49+
/**
50+
* Create a new AtomicFile for a file located at the given File path.
51+
* The secondary backup file will be the same file path with ".bak" appended.
52+
*/
53+
public AtomicFile(File baseName) {
54+
mBaseName = baseName;
55+
mBackupName = new File(baseName.getPath() + ".bak");
56+
}
57+
58+
/**
59+
* Return the path to the base file. You should not generally use this,
60+
* as the data at that path may not be valid.
61+
*/
62+
public File getBaseFile() {
63+
return mBaseName;
64+
}
65+
66+
/**
67+
* Delete the atomic file. This deletes both the base and backup files.
68+
*/
69+
public void delete() {
70+
mBaseName.delete();
71+
mBackupName.delete();
72+
}
73+
74+
/**
75+
* Start a new write operation on the file. This returns a FileOutputStream
76+
* to which you can write the new file data. The existing file is replaced
77+
* with the new data. You <em>must not</em> directly close the given
78+
* FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)}
79+
* or {@link #failWrite(FileOutputStream)}.
80+
*
81+
* <p>Note that if another thread is currently performing
82+
* a write, this will simply replace whatever that thread is writing
83+
* with the new file being written by this thread, and when the other
84+
* thread finishes the write the new write operation will no longer be
85+
* safe (or will be lost). You must do your own threading protection for
86+
* access to AtomicFile.
87+
*/
88+
public FileOutputStream startWrite() throws IOException {
89+
// Rename the current file so it may be used as a backup during the next read
90+
if (mBaseName.exists()) {
91+
if (!mBackupName.exists()) {
92+
if (!mBaseName.renameTo(mBackupName)) {
93+
Log.w("AtomicFile", "Couldn't rename file " + mBaseName
94+
+ " to backup file " + mBackupName);
95+
}
96+
} else {
97+
mBaseName.delete();
98+
}
99+
}
100+
FileOutputStream str = null;
101+
try {
102+
str = new FileOutputStream(mBaseName);
103+
} catch (FileNotFoundException e) {
104+
File parent = mBaseName.getParentFile();
105+
if (!parent.mkdir()) {
106+
throw new IOException("Couldn't create directory " + mBaseName);
107+
}
108+
FileUtils.setPermissions(
109+
parent.getPath(),
110+
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
111+
-1, -1);
112+
try {
113+
str = new FileOutputStream(mBaseName);
114+
} catch (FileNotFoundException e2) {
115+
throw new IOException("Couldn't create " + mBaseName);
116+
}
117+
}
118+
return str;
119+
}
120+
121+
/**
122+
* Call when you have successfully finished writing to the stream
123+
* returned by {@link #startWrite()}. This will close, sync, and
124+
* commit the new data. The next attempt to read the atomic file
125+
* will return the new file stream.
126+
*/
127+
public void finishWrite(FileOutputStream str) {
128+
if (str != null) {
129+
FileUtils.sync(str);
130+
try {
131+
str.close();
132+
mBackupName.delete();
133+
} catch (IOException e) {
134+
Log.w("AtomicFile", "finishWrite: Got exception:", e);
135+
}
136+
}
137+
}
138+
139+
/**
140+
* Call when you have failed for some reason at writing to the stream
141+
* returned by {@link #startWrite()}. This will close the current
142+
* write stream, and roll back to the previous state of the file.
143+
*/
144+
public void failWrite(FileOutputStream str) {
145+
if (str != null) {
146+
FileUtils.sync(str);
147+
try {
148+
str.close();
149+
mBaseName.delete();
150+
mBackupName.renameTo(mBaseName);
151+
} catch (IOException e) {
152+
Log.w("AtomicFile", "failWrite: Got exception:", e);
153+
}
154+
}
155+
}
156+
157+
/** @hide
158+
* @deprecated This is not safe.
159+
*/
160+
@Deprecated public void truncate() throws IOException {
161+
try {
162+
FileOutputStream fos = new FileOutputStream(mBaseName);
163+
FileUtils.sync(fos);
164+
fos.close();
165+
} catch (FileNotFoundException e) {
166+
throw new IOException("Couldn't append " + mBaseName);
167+
} catch (IOException e) {
168+
}
169+
}
170+
171+
/** @hide
172+
* @deprecated This is not safe.
173+
*/
174+
@Deprecated public FileOutputStream openAppend() throws IOException {
175+
try {
176+
return new FileOutputStream(mBaseName, true);
177+
} catch (FileNotFoundException e) {
178+
throw new IOException("Couldn't append " + mBaseName);
179+
}
180+
}
181+
182+
/**
183+
* Open the atomic file for reading. If there previously was an
184+
* incomplete write, this will roll back to the last good data before
185+
* opening for read. You should call close() on the FileInputStream when
186+
* you are done reading from it.
187+
*
188+
* <p>Note that if another thread is currently performing
189+
* a write, this will incorrectly consider it to be in the state of a bad
190+
* write and roll back, causing the new data currently being written to
191+
* be dropped. You must do your own threading protection for access to
192+
* AtomicFile.
193+
*/
194+
public FileInputStream openRead() throws FileNotFoundException {
195+
if (mBackupName.exists()) {
196+
mBaseName.delete();
197+
mBackupName.renameTo(mBaseName);
198+
}
199+
return new FileInputStream(mBaseName);
200+
}
201+
202+
/**
203+
* A convenience for {@link #openRead()} that also reads all of the
204+
* file contents into a byte array which is returned.
205+
*/
206+
public byte[] readFully() throws IOException {
207+
FileInputStream stream = openRead();
208+
try {
209+
int pos = 0;
210+
int avail = stream.available();
211+
byte[] data = new byte[avail];
212+
while (true) {
213+
int amt = stream.read(data, pos, data.length-pos);
214+
//Log.i("foo", "Read " + amt + " bytes at " + pos
215+
// + " of avail " + data.length);
216+
if (amt <= 0) {
217+
//Log.i("foo", "**** FINISHED READING: pos=" + pos
218+
// + " len=" + data.length);
219+
return data;
220+
}
221+
pos += amt;
222+
avail = stream.available();
223+
if (avail > data.length-pos) {
224+
byte[] newData = new byte[pos+avail];
225+
System.arraycopy(data, 0, newData, 0, pos);
226+
data = newData;
227+
}
228+
}
229+
} finally {
230+
stream.close();
231+
}
232+
}
233+
}

core/java/com/android/internal/util/JournaledFile.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121

22+
/**
23+
* @Deprecated Use {@link com.android.internal.os.AtomicFile} instead. It would
24+
* be nice to update all existing uses of this to switch to AtomicFile, but since
25+
* their on-file semantics are slightly different that would run the risk of losing
26+
* data if at the point of the platform upgrade to the new code it would need to
27+
* roll back to the backup file. This can be solved... but is it worth it and
28+
* all of the testing needed to make sure it is correct?
29+
*/
30+
@Deprecated
2231
public class JournaledFile {
2332
File mReal;
2433
File mTemp;

services/java/com/android/server/AppWidgetServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import android.os.RemoteException;
4545
import android.os.SystemClock;
4646
import android.os.UserId;
47+
import android.util.AtomicFile;
4748
import android.util.AttributeSet;
4849
import android.util.Log;
4950
import android.util.Pair;
@@ -55,7 +56,6 @@
5556
import android.widget.RemoteViews;
5657

5758
import com.android.internal.appwidget.IAppWidgetHost;
58-
import com.android.internal.os.AtomicFile;
5959
import com.android.internal.util.FastXmlSerializer;
6060
import com.android.internal.widget.IRemoteViewsAdapterConnection;
6161
import com.android.internal.widget.IRemoteViewsFactory;

services/java/com/android/server/InputMethodManagerService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package com.android.server;
1717

1818
import com.android.internal.content.PackageMonitor;
19-
import com.android.internal.os.AtomicFile;
2019
import com.android.internal.os.HandlerCaller;
2120
import com.android.internal.util.FastXmlSerializer;
2221
import com.android.internal.view.IInputContext;
@@ -74,6 +73,7 @@
7473
import android.provider.Settings.SettingNotFoundException;
7574
import android.text.TextUtils;
7675
import android.text.style.SuggestionSpan;
76+
import android.util.AtomicFile;
7777
import android.util.EventLog;
7878
import android.util.LruCache;
7979
import android.util.Pair;

services/java/com/android/server/NotificationManagerService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import android.provider.Settings;
5454
import android.telephony.TelephonyManager;
5555
import android.text.TextUtils;
56+
import android.util.AtomicFile;
5657
import android.util.EventLog;
5758
import android.util.Log;
5859
import android.util.Slog;
@@ -61,7 +62,6 @@
6162
import android.view.accessibility.AccessibilityManager;
6263
import android.widget.Toast;
6364

64-
import com.android.internal.os.AtomicFile;
6565
import com.android.internal.statusbar.StatusBarNotification;
6666
import com.android.internal.util.FastXmlSerializer;
6767

services/java/com/android/server/am/CompatModePackages.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
import java.io.FileInputStream;
55
import java.io.FileOutputStream;
66
import java.util.HashMap;
7-
import java.util.HashSet;
87
import java.util.Iterator;
98
import java.util.Map;
109

1110
import org.xmlpull.v1.XmlPullParser;
1211
import org.xmlpull.v1.XmlPullParserException;
1312
import org.xmlpull.v1.XmlSerializer;
1413

15-
import com.android.internal.os.AtomicFile;
1614
import com.android.internal.util.FastXmlSerializer;
1715

1816
import android.app.ActivityManager;
@@ -24,6 +22,7 @@
2422
import android.os.Handler;
2523
import android.os.Message;
2624
import android.os.RemoteException;
25+
import android.util.AtomicFile;
2726
import android.util.Slog;
2827
import android.util.Xml;
2928

services/java/com/android/server/am/UsageStatsService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
import android.os.Process;
2828
import android.os.ServiceManager;
2929
import android.os.SystemClock;
30+
import android.util.AtomicFile;
3031
import android.util.Slog;
3132
import android.util.Xml;
3233

3334
import com.android.internal.app.IUsageStats;
3435
import com.android.internal.content.PackageMonitor;
35-
import com.android.internal.os.AtomicFile;
3636
import com.android.internal.os.PkgUsageStats;
3737
import com.android.internal.util.FastXmlSerializer;
3838

0 commit comments

Comments
 (0)