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 final 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 boolean exists() { 134 return mBaseName.exists() || mBackupName.exists(); 135 } 136 137 public void delete() { 138 mBaseName.delete(); 139 mBackupName.delete(); 140 } 141 142 public FileInputStream openRead() throws FileNotFoundException { 143 if (mBackupName.exists()) { 144 mBaseName.delete(); 145 mBackupName.renameTo(mBaseName); 146 } 147 return new FileInputStream(mBaseName); 148 } 149 150 public byte[] readFully() throws IOException { 151 FileInputStream stream = openRead(); 152 try { 153 int pos = 0; 154 int avail = stream.available(); 155 byte[] data = new byte[avail]; 156 while (true) { 157 int amt = stream.read(data, pos, data.length-pos); 158 //Log.i("foo", "Read " + amt + " bytes at " + pos 159 // + " of avail " + data.length); 160 if (amt <= 0) { 161 //Log.i("foo", "**** FINISHED READING: pos=" + pos 162 // + " len=" + data.length); 163 return data; 164 } 165 pos += amt; 166 avail = stream.available(); 167 if (avail > data.length-pos) { 168 byte[] newData = new byte[pos+avail]; 169 System.arraycopy(data, 0, newData, 0, pos); 170 data = newData; 171 } 172 } 173 } finally { 174 stream.close(); 175 } 176 } 177} 178