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 */
32public class AtomicFile {
33    private final File mBaseName;
34    private final File mBackupName;
35
36    public AtomicFile(File baseName) {
37        mBaseName = baseName;
38        mBackupName = new File(baseName.getPath() + ".bak");
39    }
40
41    public File getBaseFile() {
42        return mBaseName;
43    }
44
45    public FileOutputStream startWrite() throws IOException {
46        // Rename the current file so it may be used as a backup during the next read
47        if (mBaseName.exists()) {
48            if (!mBackupName.exists()) {
49                if (!mBaseName.renameTo(mBackupName)) {
50                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
51                            + " to backup file " + mBackupName);
52                }
53            } else {
54                mBaseName.delete();
55            }
56        }
57        FileOutputStream str = null;
58        try {
59            str = new FileOutputStream(mBaseName);
60        } catch (FileNotFoundException e) {
61            File parent = mBaseName.getParentFile();
62            if (!parent.mkdir()) {
63                throw new IOException("Couldn't create directory " + mBaseName);
64            }
65            FileUtils.setPermissions(
66                parent.getPath(),
67                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
68                -1, -1);
69            try {
70                str = new FileOutputStream(mBaseName);
71            } catch (FileNotFoundException e2) {
72                throw new IOException("Couldn't create " + mBaseName);
73            }
74        }
75        return str;
76    }
77
78    public void finishWrite(FileOutputStream str) {
79        if (str != null) {
80            FileUtils.sync(str);
81            try {
82                str.close();
83                mBackupName.delete();
84            } catch (IOException e) {
85                Log.w("AtomicFile", "finishWrite: Got exception:", e);
86            }
87        }
88    }
89
90    public void failWrite(FileOutputStream str) {
91        if (str != null) {
92            FileUtils.sync(str);
93            try {
94                str.close();
95                mBaseName.delete();
96                mBackupName.renameTo(mBaseName);
97            } catch (IOException e) {
98                Log.w("AtomicFile", "failWrite: Got exception:", e);
99            }
100        }
101    }
102
103    public FileOutputStream openAppend() throws IOException {
104        try {
105            return new FileOutputStream(mBaseName, true);
106        } catch (FileNotFoundException e) {
107            throw new IOException("Couldn't append " + mBaseName);
108        }
109    }
110
111    public void truncate() throws IOException {
112        try {
113            FileOutputStream fos = new FileOutputStream(mBaseName);
114            FileUtils.sync(fos);
115            fos.close();
116        } catch (FileNotFoundException e) {
117            throw new IOException("Couldn't append " + mBaseName);
118        } catch (IOException e) {
119        }
120    }
121
122    public FileInputStream openRead() throws FileNotFoundException {
123        if (mBackupName.exists()) {
124            mBaseName.delete();
125            mBackupName.renameTo(mBaseName);
126        }
127        return new FileInputStream(mBaseName);
128    }
129
130    public byte[] readFully() throws IOException {
131        FileInputStream stream = openRead();
132        try {
133            int pos = 0;
134            int avail = stream.available();
135            byte[] data = new byte[avail];
136            while (true) {
137                int amt = stream.read(data, pos, data.length-pos);
138                //Log.i("foo", "Read " + amt + " bytes at " + pos
139                //        + " of avail " + data.length);
140                if (amt <= 0) {
141                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
142                    //        + " len=" + data.length);
143                    return data;
144                }
145                pos += amt;
146                avail = stream.available();
147                if (avail > data.length-pos) {
148                    byte[] newData = new byte[pos+avail];
149                    System.arraycopy(data, 0, newData, 0, pos);
150                    data = newData;
151                }
152            }
153        } finally {
154            stream.close();
155        }
156    }
157}
158