001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.store;
007:
008: import java.io.IOException;
009: import java.lang.ref.Reference;
010: import java.sql.SQLException;
011:
012: import org.h2.constant.ErrorCode;
013: import org.h2.constant.SysProperties;
014: import org.h2.engine.Constants;
015: import org.h2.message.Message;
016: import org.h2.security.SecureFileStore;
017: import org.h2.store.fs.FileObject;
018: import org.h2.store.fs.FileSystem;
019: import org.h2.util.ByteUtils;
020: import org.h2.util.TempFileDeleter;
021:
022: /**
023: * This class is an abstraction of a random access file.
024: * Each file contains a magic header, and reading / writing is done in blocks.
025: * See also {@link SecureFileStore}
026: */
027: public class FileStore {
028:
029: public static final int HEADER_LENGTH = 3 * Constants.FILE_BLOCK_SIZE;
030: protected static final byte[] EMPTY = new byte[16 * 1024];
031:
032: protected String name;
033: protected DataHandler handler;
034: private byte[] magic;
035: private FileObject file;
036: private long filePos;
037: private long fileLength;
038: private Reference autoDeleteReference;
039: private boolean checkedWriting = true;
040: private boolean synchronousMode;
041: private String mode;
042:
043: /**
044: * Open a non encrypted file store with the given settings.
045: *
046: * @param handler the data handler
047: * @param name the file name
048: * @param mode the access mode (r, rw, rws, rwd)
049: * @param magic the file header magic bytes
050: * @return the created object
051: */
052: public static FileStore open(DataHandler handler, String name,
053: String mode, byte[] magic) throws SQLException {
054: return open(handler, name, mode, magic, null, null, 0);
055: }
056:
057: /**
058: * Open an encrypted file store with the given settings.
059: *
060: * @param handler the data handler
061: * @param name the file name
062: * @param mode the access mode (r, rw, rws, rwd)
063: * @param magic the file header magic bytes
064: * @param cipher the name of the cipher algorithm
065: * @param key the encryption key
066: * @return the created object
067: */
068: public static FileStore open(DataHandler handler, String name,
069: String mode, byte[] magic, String cipher, byte[] key)
070: throws SQLException {
071: return open(handler, name, mode, magic, cipher, key,
072: Constants.ENCRYPTION_KEY_HASH_ITERATIONS);
073: }
074:
075: /**
076: * Open an encrypted file store with the given settings.
077: *
078: * @param handler the data handler
079: * @param name the file name
080: * @param mode the access mode (r, rw, rws, rwd)
081: * @param magic the file header magic bytes
082: * @param cipher the name of the cipher algorithm
083: * @param key the encryption key
084: * @param keyIterations the number of iterations the key should be hashed
085: * @return the created object
086: */
087: public static FileStore open(DataHandler handler, String name,
088: String mode, byte[] magic, String cipher, byte[] key,
089: int keyIterations) throws SQLException {
090: FileStore store;
091: if (cipher == null) {
092: store = new FileStore(handler, name, mode, magic);
093: } else {
094: store = new SecureFileStore(handler, name, mode, magic,
095: cipher, key, keyIterations);
096: }
097: return store;
098: }
099:
100: protected FileStore(DataHandler handler, String name, String mode,
101: byte[] magic) throws SQLException {
102: FileSystem fs = FileSystem.getInstance(name);
103: this .handler = handler;
104: this .name = name;
105: this .magic = magic;
106: this .mode = mode;
107: try {
108: fs.createDirs(name);
109: if (fs.exists(name) && !fs.canWrite(name)) {
110: mode = "r";
111: this .mode = mode;
112: }
113: file = fs.openFileObject(name, mode);
114: if (mode.length() > 2) {
115: synchronousMode = true;
116: }
117: fileLength = file.length();
118: } catch (IOException e) {
119: throw Message.convertIOException(e, "name: " + name
120: + " mode: " + mode);
121: }
122: }
123:
124: protected FileStore(DataHandler handler, byte[] magic) {
125: this .handler = handler;
126: this .magic = magic;
127: }
128:
129: protected byte[] generateSalt() {
130: return magic;
131: }
132:
133: protected void initKey(byte[] salt) {
134: // do nothing
135: }
136:
137: public void setCheckedWriting(boolean value) {
138: this .checkedWriting = value;
139: }
140:
141: protected void checkWritingAllowed() throws SQLException {
142: if (handler != null && checkedWriting) {
143: handler.checkWritingAllowed();
144: }
145: }
146:
147: protected void checkPowerOff() throws SQLException {
148: if (handler != null) {
149: handler.checkPowerOff();
150: }
151: }
152:
153: public void init() throws SQLException {
154: int len = Constants.FILE_BLOCK_SIZE;
155: byte[] salt;
156: if (length() < HEADER_LENGTH) {
157: // write unencrypted
158: checkedWriting = false;
159: writeDirect(magic, 0, len);
160: salt = generateSalt();
161: writeDirect(salt, 0, len);
162: initKey(salt);
163: // write (maybe) encrypted
164: write(magic, 0, len);
165: checkedWriting = true;
166: } else {
167: // read unencrypted
168: seek(0);
169: byte[] buff = new byte[len];
170: readFullyDirect(buff, 0, len);
171: if (ByteUtils.compareNotNull(buff, magic) != 0) {
172: throw Message.getSQLException(
173: ErrorCode.FILE_VERSION_ERROR_1, name);
174: }
175: salt = new byte[len];
176: readFullyDirect(salt, 0, len);
177: initKey(salt);
178: // read (maybe) encrypted
179: readFully(buff, 0, Constants.FILE_BLOCK_SIZE);
180: if (ByteUtils.compareNotNull(buff, magic) != 0) {
181: throw Message.getSQLException(
182: ErrorCode.FILE_ENCRYPTION_ERROR_1, name);
183: }
184: }
185: }
186:
187: public void close() throws IOException {
188: if (file != null) {
189: try {
190: trace("close", name, file);
191: file.close();
192: } finally {
193: file = null;
194: }
195: }
196: }
197:
198: public void closeSilently() {
199: try {
200: close();
201: } catch (IOException e) {
202: // ignore
203: }
204: }
205:
206: public void closeAndDeleteSilently() {
207: if (file != null) {
208: closeSilently();
209: TempFileDeleter.deleteFile(autoDeleteReference, name);
210: name = null;
211: }
212: }
213:
214: protected void readFullyDirect(byte[] b, int off, int len)
215: throws SQLException {
216: readFully(b, off, len);
217: }
218:
219: public void readFully(byte[] b, int off, int len)
220: throws SQLException {
221: if (SysProperties.CHECK && len < 0) {
222: throw Message.getInternalError("read len " + len);
223: }
224: if (SysProperties.CHECK && len % Constants.FILE_BLOCK_SIZE != 0) {
225: throw Message.getInternalError("unaligned read " + name
226: + " len " + len);
227: }
228: checkPowerOff();
229: try {
230: file.readFully(b, off, len);
231: } catch (IOException e) {
232: throw Message.convertIOException(e, name);
233: }
234: filePos += len;
235: }
236:
237: public void seek(long pos) throws SQLException {
238: if (SysProperties.CHECK && pos % Constants.FILE_BLOCK_SIZE != 0) {
239: throw Message.getInternalError("unaligned seek " + name
240: + " pos " + pos);
241: }
242: try {
243: if (pos != filePos) {
244: file.seek(pos);
245: filePos = pos;
246: }
247: } catch (IOException e) {
248: throw Message.convertIOException(e, name);
249: }
250: }
251:
252: protected void writeDirect(byte[] b, int off, int len)
253: throws SQLException {
254: write(b, off, len);
255: }
256:
257: public void write(byte[] b, int off, int len) throws SQLException {
258: if (SysProperties.CHECK && len < 0) {
259: throw Message.getInternalError("read len " + len);
260: }
261: if (SysProperties.CHECK && len % Constants.FILE_BLOCK_SIZE != 0) {
262: throw Message.getInternalError("unaligned write " + name
263: + " len " + len);
264: }
265: checkWritingAllowed();
266: checkPowerOff();
267: try {
268: file.write(b, off, len);
269: } catch (IOException e) {
270: if (freeUpDiskSpace()) {
271: try {
272: file.write(b, off, len);
273: } catch (IOException e2) {
274: throw Message.convertIOException(e2, name);
275: }
276: } else {
277: throw Message.convertIOException(e, name);
278: }
279: }
280: filePos += len;
281: fileLength = Math.max(filePos, fileLength);
282: }
283:
284: private boolean freeUpDiskSpace() throws SQLException {
285: if (handler == null) {
286: return false;
287: }
288: handler.freeUpDiskSpace();
289: return true;
290: }
291:
292: private void extendByWriting(long newLength) throws IOException {
293: long pos = filePos;
294: file.seek(fileLength);
295: byte[] empty = EMPTY;
296: while (true) {
297: int p = (int) Math
298: .min(newLength - fileLength, EMPTY.length);
299: if (p <= 0) {
300: break;
301: }
302: file.write(empty, 0, p);
303: fileLength += p;
304: }
305: file.seek(pos);
306: }
307:
308: public void setLength(long newLength) throws SQLException {
309: if (SysProperties.CHECK
310: && newLength % Constants.FILE_BLOCK_SIZE != 0) {
311: throw Message.getInternalError("unaligned setLength "
312: + name + " pos " + newLength);
313: }
314: checkPowerOff();
315: checkWritingAllowed();
316: try {
317: if (synchronousMode && newLength > fileLength) {
318: extendByWriting(newLength);
319: } else {
320: file.setFileLength(newLength);
321: }
322: fileLength = newLength;
323: } catch (IOException e) {
324: if (freeUpDiskSpace()) {
325: try {
326: file.setFileLength(newLength);
327: } catch (IOException e2) {
328: throw Message.convertIOException(e2, name);
329: }
330: } else {
331: throw Message.convertIOException(e, name);
332: }
333: }
334: }
335:
336: public long length() throws SQLException {
337: try {
338: long len = fileLength;
339: if (SysProperties.CHECK2) {
340: len = file.length();
341: if (len != fileLength) {
342: throw Message.getInternalError("file " + name
343: + " length " + len + " expected "
344: + fileLength);
345: }
346: }
347: if (SysProperties.CHECK2
348: && len % Constants.FILE_BLOCK_SIZE != 0) {
349: long newLength = len + Constants.FILE_BLOCK_SIZE
350: - (len % Constants.FILE_BLOCK_SIZE);
351: file.setFileLength(newLength);
352: fileLength = newLength;
353: throw Message.getInternalError("unaligned file length "
354: + name + " len " + len);
355: }
356: return len;
357: } catch (IOException e) {
358: throw Message.convertIOException(e, name);
359: }
360: }
361:
362: public long getFilePointer() throws SQLException {
363: if (SysProperties.CHECK2) {
364: try {
365: if (file.getFilePointer() != filePos) {
366: throw Message.getInternalError();
367: }
368: } catch (IOException e) {
369: throw Message.convertIOException(e, name);
370: }
371: }
372: return filePos;
373: }
374:
375: public void sync() {
376: try {
377: file.sync();
378: } catch (IOException e) {
379: // TODO log exception
380: }
381: }
382:
383: public void autoDelete() {
384: autoDeleteReference = TempFileDeleter.addFile(name, this );
385: }
386:
387: public void stopAutoDelete() {
388: TempFileDeleter.stopAutoDelete(autoDeleteReference, name);
389: autoDeleteReference = null;
390: }
391:
392: public boolean isEncrypted() {
393: return false;
394: }
395:
396: public void closeFile() throws IOException {
397: file.close();
398: file = null;
399: }
400:
401: public void openFile() throws IOException {
402: if (file == null) {
403: file = FileSystem.getInstance(name).openFileObject(name,
404: mode);
405: file.seek(filePos);
406: }
407: }
408:
409: private static void trace(String method, String fileName, Object o) {
410: if (SysProperties.TRACE_IO) {
411: System.out.println("FileStore." + method + " " + fileName
412: + " " + o);
413: }
414: }
415:
416: }
|