blob: a72a2f5e1be37619be8999b61a3ebdc626b38fd7 [file] [log] [blame]
Dianne Hackborn231cc602009-04-27 17:10:36 -07001/*
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
17package com.android.internal.os;
18
Mathew Inwoodc185f082018-08-20 14:28:54 +010019import android.annotation.UnsupportedAppUsage;
Mathew Inwood55418ea2018-12-20 15:30:45 +000020import android.os.Build;
Dianne Hackborn231cc602009-04-27 17:10:36 -070021import android.os.FileUtils;
22import android.util.Log;
23
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.FileNotFoundException;
27import java.io.FileOutputStream;
28import java.io.IOException;
29
30/**
31 * Helper class for performing atomic operations on a file, by creating a
32 * backup file until a write has successfully completed.
Jeff Brown763631c2011-11-07 18:03:55 -080033 * <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>
Dianne Hackborn231cc602009-04-27 17:10:36 -070044 */
Dianne Hackborn0068d3dc2014-08-06 19:20:25 -070045public final class AtomicFile {
Dianne Hackborn231cc602009-04-27 17:10:36 -070046 private final File mBaseName;
47 private final File mBackupName;
48
Mathew Inwoodc185f082018-08-20 14:28:54 +010049 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -070050 public AtomicFile(File baseName) {
51 mBaseName = baseName;
52 mBackupName = new File(baseName.getPath() + ".bak");
53 }
54
Mathew Inwood55418ea2018-12-20 15:30:45 +000055 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Dianne Hackborn231cc602009-04-27 17:10:36 -070056 public File getBaseFile() {
57 return mBaseName;
58 }
59
Mathew Inwoodc185f082018-08-20 14:28:54 +010060 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -070061 public FileOutputStream startWrite() throws IOException {
62 // Rename the current file so it may be used as a backup during the next read
63 if (mBaseName.exists()) {
Dianne Hackborn1afd1c92010-03-18 22:47:17 -070064 if (!mBackupName.exists()) {
Dianne Hackborn231cc602009-04-27 17:10:36 -070065 if (!mBaseName.renameTo(mBackupName)) {
66 Log.w("AtomicFile", "Couldn't rename file " + mBaseName
67 + " to backup file " + mBackupName);
68 }
Dianne Hackborn1afd1c92010-03-18 22:47:17 -070069 } else {
70 mBaseName.delete();
Dianne Hackborn231cc602009-04-27 17:10:36 -070071 }
72 }
73 FileOutputStream str = null;
74 try {
75 str = new FileOutputStream(mBaseName);
76 } catch (FileNotFoundException e) {
77 File parent = mBaseName.getParentFile();
78 if (!parent.mkdir()) {
79 throw new IOException("Couldn't create directory " + mBaseName);
80 }
81 FileUtils.setPermissions(
82 parent.getPath(),
83 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
84 -1, -1);
85 try {
86 str = new FileOutputStream(mBaseName);
87 } catch (FileNotFoundException e2) {
88 throw new IOException("Couldn't create " + mBaseName);
89 }
90 }
91 return str;
92 }
93
Mathew Inwoodc185f082018-08-20 14:28:54 +010094 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -070095 public void finishWrite(FileOutputStream str) {
96 if (str != null) {
Dianne Hackborn8bdf5932010-10-15 12:54:40 -070097 FileUtils.sync(str);
Dianne Hackborn231cc602009-04-27 17:10:36 -070098 try {
99 str.close();
100 mBackupName.delete();
101 } catch (IOException e) {
102 Log.w("AtomicFile", "finishWrite: Got exception:", e);
103 }
104 }
105 }
106
Mathew Inwoodc185f082018-08-20 14:28:54 +0100107 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -0700108 public void failWrite(FileOutputStream str) {
109 if (str != null) {
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700110 FileUtils.sync(str);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700111 try {
112 str.close();
113 mBaseName.delete();
114 mBackupName.renameTo(mBaseName);
115 } catch (IOException e) {
116 Log.w("AtomicFile", "failWrite: Got exception:", e);
117 }
118 }
119 }
120
Mathew Inwoodc185f082018-08-20 14:28:54 +0100121 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -0700122 public FileOutputStream openAppend() throws IOException {
123 try {
124 return new FileOutputStream(mBaseName, true);
125 } catch (FileNotFoundException e) {
126 throw new IOException("Couldn't append " + mBaseName);
127 }
128 }
129
Mathew Inwoodc185f082018-08-20 14:28:54 +0100130 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -0700131 public void truncate() throws IOException {
132 try {
133 FileOutputStream fos = new FileOutputStream(mBaseName);
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700134 FileUtils.sync(fos);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700135 fos.close();
136 } catch (FileNotFoundException e) {
137 throw new IOException("Couldn't append " + mBaseName);
138 } catch (IOException e) {
139 }
140 }
Dianne Hackborn0068d3dc2014-08-06 19:20:25 -0700141
142 public boolean exists() {
143 return mBaseName.exists() || mBackupName.exists();
144 }
145
146 public void delete() {
147 mBaseName.delete();
148 mBackupName.delete();
149 }
150
Mathew Inwoodc185f082018-08-20 14:28:54 +0100151 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -0700152 public FileInputStream openRead() throws FileNotFoundException {
153 if (mBackupName.exists()) {
154 mBaseName.delete();
155 mBackupName.renameTo(mBaseName);
156 }
157 return new FileInputStream(mBaseName);
158 }
159
Mathew Inwoodc185f082018-08-20 14:28:54 +0100160 @UnsupportedAppUsage
Dianne Hackborn231cc602009-04-27 17:10:36 -0700161 public byte[] readFully() throws IOException {
162 FileInputStream stream = openRead();
163 try {
164 int pos = 0;
165 int avail = stream.available();
166 byte[] data = new byte[avail];
167 while (true) {
168 int amt = stream.read(data, pos, data.length-pos);
169 //Log.i("foo", "Read " + amt + " bytes at " + pos
170 // + " of avail " + data.length);
171 if (amt <= 0) {
172 //Log.i("foo", "**** FINISHED READING: pos=" + pos
173 // + " len=" + data.length);
174 return data;
175 }
176 pos += amt;
177 avail = stream.available();
178 if (avail > data.length-pos) {
179 byte[] newData = new byte[pos+avail];
180 System.arraycopy(data, 0, newData, 0, pos);
181 data = newData;
182 }
183 }
184 } finally {
185 stream.close();
186 }
187 }
188}