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
17package com.android.internal.os;
18
19import android.os.FileUtils;
20import android.util.Log;
21
22import java.io.File;
23import java.io.FileInputStream;
24import java.io.FileNotFoundException;
25import java.io.FileOutputStream;
26import 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.
31 * <p>
32 * Atomic file guarantees file integrity by ensuring that a file has
33 * been completely written and sync'd to disk before removing its backup.
34 * As long as the backup file exists, the original file is considered
35 * to be invalid (left over from a previous attempt to write the file).
36 * </p><p>
37 * Atomic file does not confer any file locking semantics.
38 * Do not use this class when the file may be accessed or modified concurrently
39 * by multiple threads or processes.  The caller is responsible for ensuring
40 * appropriate mutual exclusion invariants whenever it accesses the file.
41 * </p>
42 */
43public class AtomicFile {
44    private final File mBaseName;
45    private final File mBackupName;
46
47    public AtomicFile(File baseName) {
48        mBaseName = baseName;
49        mBackupName = new File(baseName.getPath() + ".bak");
50    }
51
52    public File getBaseFile() {
53        return mBaseName;
54    }
55
56    public FileOutputStream startWrite() throws IOException {
57        // Rename the current file so it may be used as a backup during the next read
58        if (mBaseName.exists()) {
59            if (!mBackupName.exists()) {
60                if (!mBaseName.renameTo(mBackupName)) {
61                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
62                            + " to backup file " + mBackupName);
63                }
64            } else {
65                mBaseName.delete();
66            }
67        }
68        FileOutputStream str = null;
69        try {
70            str = new FileOutputStream(mBaseName);
71        } catch (FileNotFoundException e) {
72            File parent = mBaseName.getParentFile();
73            if (!parent.mkdir()) {
74                throw new IOException("Couldn't create directory " + mBaseName);
75            }
76            FileUtils.setPermissions(
77                parent.getPath(),
78                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
79                -1, -1);
80            try {
81                str = new FileOutputStream(mBaseName);
82            } catch (FileNotFoundException e2) {
83                throw new IOException("Couldn't create " + mBaseName);
84            }
85        }
86        return str;
87    }
88
89    public void finishWrite(FileOutputStream str) {
90        if (str != null) {
91            FileUtils.sync(str);
92            try {
93                str.close();
94                mBackupName.delete();
95            } catch (IOException e) {
96                Log.w("AtomicFile", "finishWrite: Got exception:", e);
97            }
98        }
99    }
100
101    public void failWrite(FileOutputStream str) {
102        if (str != null) {
103            FileUtils.sync(str);
104            try {
105                str.close();
106                mBaseName.delete();
107                mBackupName.renameTo(mBaseName);
108            } catch (IOException e) {
109                Log.w("AtomicFile", "failWrite: Got exception:", e);
110            }
111        }
112    }
113
114    public FileOutputStream openAppend() throws IOException {
115        try {
116            return new FileOutputStream(mBaseName, true);
117        } catch (FileNotFoundException e) {
118            throw new IOException("Couldn't append " + mBaseName);
119        }
120    }
121
122    public void truncate() throws IOException {
123        try {
124            FileOutputStream fos = new FileOutputStream(mBaseName);
125            FileUtils.sync(fos);
126            fos.close();
127        } catch (FileNotFoundException e) {
128            throw new IOException("Couldn't append " + mBaseName);
129        } catch (IOException e) {
130        }
131    }
132
133    public FileInputStream openRead() throws FileNotFoundException {
134        if (mBackupName.exists()) {
135            mBaseName.delete();
136            mBackupName.renameTo(mBaseName);
137        }
138        return new FileInputStream(mBaseName);
139    }
140
141    public byte[] readFully() throws IOException {
142        FileInputStream stream = openRead();
143        try {
144            int pos = 0;
145            int avail = stream.available();
146            byte[] data = new byte[avail];
147            while (true) {
148                int amt = stream.read(data, pos, data.length-pos);
149                //Log.i("foo", "Read " + amt + " bytes at " + pos
150                //        + " of avail " + data.length);
151                if (amt <= 0) {
152                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
153                    //        + " len=" + data.length);
154                    return data;
155                }
156                pos += amt;
157                avail = stream.available();
158                if (avail > data.length-pos) {
159                    byte[] newData = new byte[pos+avail];
160                    System.arraycopy(data, 0, newData, 0, pos);
161                    data = newData;
162                }
163            }
164        } finally {
165            stream.close();
166        }
167    }
168}
169