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