1231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn/*
2231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * Copyright (C) 2009 The Android Open Source Project
3231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn *
4231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * Licensed under the Apache License, Version 2.0 (the "License");
5231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * you may not use this file except in compliance with the License.
6231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * You may obtain a copy of the License at
7231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn *
8231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn *      http://www.apache.org/licenses/LICENSE-2.0
9231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn *
10231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * Unless required by applicable law or agreed to in writing, software
11231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * distributed under the License is distributed on an "AS IS" BASIS,
12231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * See the License for the specific language governing permissions and
14231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * limitations under the License.
15231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn */
16231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
17231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornpackage com.android.internal.os;
18231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
19231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornimport android.os.FileUtils;
20231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornimport android.util.Log;
21231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
22231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornimport java.io.File;
23231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornimport java.io.FileInputStream;
24231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornimport java.io.FileNotFoundException;
25231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornimport java.io.FileOutputStream;
26231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackbornimport java.io.IOException;
27231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
28231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn/**
29231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * Helper class for performing atomic operations on a file, by creating a
30231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn * backup file until a write has successfully completed.
31763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * <p>
32763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * Atomic file guarantees file integrity by ensuring that a file has
33763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * been completely written and sync'd to disk before removing its backup.
34763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * As long as the backup file exists, the original file is considered
35763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * to be invalid (left over from a previous attempt to write the file).
36763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * </p><p>
37763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * Atomic file does not confer any file locking semantics.
38763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * Do not use this class when the file may be accessed or modified concurrently
39763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * by multiple threads or processes.  The caller is responsible for ensuring
40763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * appropriate mutual exclusion invariants whenever it accesses the file.
41763631cd247e21b167063023e7dd1395e05ebbf6Jeff Brown * </p>
42231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn */
430068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackbornpublic final class AtomicFile {
44231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    private final File mBaseName;
45231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    private final File mBackupName;
46231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
47231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public AtomicFile(File baseName) {
48231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        mBaseName = baseName;
49231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        mBackupName = new File(baseName.getPath() + ".bak");
50231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
51231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
52231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public File getBaseFile() {
53231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        return mBaseName;
54231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
55231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
56231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public FileOutputStream startWrite() throws IOException {
57231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        // Rename the current file so it may be used as a backup during the next read
58231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        if (mBaseName.exists()) {
591afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn            if (!mBackupName.exists()) {
60231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                if (!mBaseName.renameTo(mBackupName)) {
61231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
62231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                            + " to backup file " + mBackupName);
63231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                }
641afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn            } else {
651afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn                mBaseName.delete();
66231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            }
67231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
68231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        FileOutputStream str = null;
69231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        try {
70231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            str = new FileOutputStream(mBaseName);
71231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        } catch (FileNotFoundException e) {
72231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            File parent = mBaseName.getParentFile();
73231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            if (!parent.mkdir()) {
74231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                throw new IOException("Couldn't create directory " + mBaseName);
75231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            }
76231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            FileUtils.setPermissions(
77231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                parent.getPath(),
78231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
79231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                -1, -1);
80231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            try {
81231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                str = new FileOutputStream(mBaseName);
82231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            } catch (FileNotFoundException e2) {
83231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                throw new IOException("Couldn't create " + mBaseName);
84231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            }
85231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
86231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        return str;
87231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
88231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
89231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public void finishWrite(FileOutputStream str) {
90231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        if (str != null) {
918bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            FileUtils.sync(str);
92231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            try {
93231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                str.close();
94231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                mBackupName.delete();
95231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            } catch (IOException e) {
96231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                Log.w("AtomicFile", "finishWrite: Got exception:", e);
97231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            }
98231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
99231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
100231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
101231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public void failWrite(FileOutputStream str) {
102231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        if (str != null) {
1038bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            FileUtils.sync(str);
104231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            try {
105231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                str.close();
106231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                mBaseName.delete();
107231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                mBackupName.renameTo(mBaseName);
108231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            } catch (IOException e) {
109231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                Log.w("AtomicFile", "failWrite: Got exception:", e);
110231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            }
111231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
112231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
113231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
114231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public FileOutputStream openAppend() throws IOException {
115231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        try {
116231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            return new FileOutputStream(mBaseName, true);
117231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        } catch (FileNotFoundException e) {
118231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            throw new IOException("Couldn't append " + mBaseName);
119231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
120231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
121231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
122231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public void truncate() throws IOException {
123231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        try {
124231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            FileOutputStream fos = new FileOutputStream(mBaseName);
1258bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            FileUtils.sync(fos);
126231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            fos.close();
127231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        } catch (FileNotFoundException e) {
128231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            throw new IOException("Couldn't append " + mBaseName);
129231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        } catch (IOException e) {
130231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
131231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
1320068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn
1330068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn    public boolean exists() {
1340068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn        return mBaseName.exists() || mBackupName.exists();
1350068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn    }
1360068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn
1370068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn    public void delete() {
1380068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn        mBaseName.delete();
1390068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn        mBackupName.delete();
1400068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn    }
1410068d3dcf1f1a804554a1a09e3b173ac12651786Dianne Hackborn
142231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public FileInputStream openRead() throws FileNotFoundException {
143231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        if (mBackupName.exists()) {
144231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            mBaseName.delete();
145231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            mBackupName.renameTo(mBaseName);
146231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
147231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        return new FileInputStream(mBaseName);
148231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
149231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn
150231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    public byte[] readFully() throws IOException {
151231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        FileInputStream stream = openRead();
152231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        try {
153231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            int pos = 0;
154231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            int avail = stream.available();
155231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            byte[] data = new byte[avail];
156231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            while (true) {
157231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                int amt = stream.read(data, pos, data.length-pos);
158231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                //Log.i("foo", "Read " + amt + " bytes at " + pos
159231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                //        + " of avail " + data.length);
160231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                if (amt <= 0) {
161231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
162231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                    //        + " len=" + data.length);
163231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                    return data;
164231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                }
165231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                pos += amt;
166231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                avail = stream.available();
167231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                if (avail > data.length-pos) {
168231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                    byte[] newData = new byte[pos+avail];
169231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                    System.arraycopy(data, 0, newData, 0, pos);
170231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                    data = newData;
171231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn                }
172231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            }
173231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        } finally {
174231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn            stream.close();
175231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn        }
176231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn    }
177231cc608d06ffc31c24bf8aa8c8275bdd2636581Dianne Hackborn}
178