1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.mvstore; |
7 | |
8 | import java.io.IOException; |
9 | import java.nio.ByteBuffer; |
10 | import java.nio.channels.FileChannel; |
11 | import java.nio.channels.FileLock; |
12 | import java.nio.channels.OverlappingFileLockException; |
13 | |
14 | import org.h2.mvstore.cache.FilePathCache; |
15 | import org.h2.store.fs.FilePath; |
16 | import org.h2.store.fs.FilePathDisk; |
17 | import org.h2.store.fs.FilePathEncrypt; |
18 | import org.h2.store.fs.FilePathNio; |
19 | |
20 | /** |
21 | * The default storage mechanism of the MVStore. This implementation persists |
22 | * data to a file. The file store is responsible to persist data and for free |
23 | * space management. |
24 | */ |
25 | public class FileStore { |
26 | |
27 | /** |
28 | * The number of read operations. |
29 | */ |
30 | protected long readCount; |
31 | |
32 | /** |
33 | * The number of read bytes. |
34 | */ |
35 | protected long readBytes; |
36 | |
37 | /** |
38 | * The number of write operations. |
39 | */ |
40 | protected long writeCount; |
41 | |
42 | /** |
43 | * The number of written bytes. |
44 | */ |
45 | protected long writeBytes; |
46 | |
47 | /** |
48 | * The free spaces between the chunks. The first block to use is block 2 |
49 | * (the first two blocks are the store header). |
50 | */ |
51 | protected final FreeSpaceBitSet freeSpace = |
52 | new FreeSpaceBitSet(2, MVStore.BLOCK_SIZE); |
53 | |
54 | /** |
55 | * The file name. |
56 | */ |
57 | protected String fileName; |
58 | |
59 | /** |
60 | * Whether this store is read-only. |
61 | */ |
62 | protected boolean readOnly; |
63 | |
64 | /** |
65 | * The file size (cached). |
66 | */ |
67 | protected long fileSize; |
68 | |
69 | /** |
70 | * The file. |
71 | */ |
72 | protected FileChannel file; |
73 | |
74 | /** |
75 | * The encrypted file (if encryption is used). |
76 | */ |
77 | protected FileChannel encryptedFile; |
78 | |
79 | /** |
80 | * The file lock. |
81 | */ |
82 | protected FileLock fileLock; |
83 | |
84 | @Override |
85 | public String toString() { |
86 | return fileName; |
87 | } |
88 | |
89 | /** |
90 | * Read from the file. |
91 | * |
92 | * @param pos the write position |
93 | * @param len the number of bytes to read |
94 | * @return the byte buffer |
95 | */ |
96 | public ByteBuffer readFully(long pos, int len) { |
97 | ByteBuffer dst = ByteBuffer.allocate(len); |
98 | DataUtils.readFully(file, pos, dst); |
99 | readCount++; |
100 | readBytes += len; |
101 | return dst; |
102 | } |
103 | |
104 | /** |
105 | * Write to the file. |
106 | * |
107 | * @param pos the write position |
108 | * @param src the source buffer |
109 | */ |
110 | public void writeFully(long pos, ByteBuffer src) { |
111 | int len = src.remaining(); |
112 | fileSize = Math.max(fileSize, pos + len); |
113 | DataUtils.writeFully(file, pos, src); |
114 | writeCount++; |
115 | writeBytes += len; |
116 | } |
117 | |
118 | /** |
119 | * Try to open the file. |
120 | * |
121 | * @param fileName the file name |
122 | * @param readOnly whether the file should only be opened in read-only mode, |
123 | * even if the file is writable |
124 | * @param encryptionKey the encryption key, or null if encryption is not |
125 | * used |
126 | */ |
127 | public void open(String fileName, boolean readOnly, char[] encryptionKey) { |
128 | if (file != null) { |
129 | return; |
130 | } |
131 | if (fileName != null) { |
132 | if (FilePath.get(fileName) instanceof FilePathDisk) { |
133 | // NIO is used, unless a different file system is specified the |
134 | // following line is to ensure the NIO file system is compiled |
135 | FilePathNio.class.getName(); |
136 | fileName = "nio:" + fileName; |
137 | } |
138 | } |
139 | this.fileName = fileName; |
140 | FilePath f = FilePath.get(fileName); |
141 | FilePath parent = f.getParent(); |
142 | if (parent != null && !parent.exists()) { |
143 | throw DataUtils.newIllegalArgumentException( |
144 | "Directory does not exist: {0}", parent); |
145 | } |
146 | if (f.exists() && !f.canWrite()) { |
147 | readOnly = true; |
148 | } |
149 | this.readOnly = readOnly; |
150 | try { |
151 | file = f.open(readOnly ? "r" : "rw"); |
152 | if (encryptionKey != null) { |
153 | byte[] key = FilePathEncrypt.getPasswordBytes(encryptionKey); |
154 | encryptedFile = file; |
155 | file = new FilePathEncrypt.FileEncrypt(fileName, key, file); |
156 | } |
157 | file = FilePathCache.wrap(file); |
158 | try { |
159 | if (readOnly) { |
160 | fileLock = file.tryLock(0, Long.MAX_VALUE, true); |
161 | } else { |
162 | fileLock = file.tryLock(); |
163 | } |
164 | } catch (OverlappingFileLockException e) { |
165 | throw DataUtils.newIllegalStateException( |
166 | DataUtils.ERROR_FILE_LOCKED, |
167 | "The file is locked: {0}", fileName, e); |
168 | } |
169 | if (fileLock == null) { |
170 | throw DataUtils.newIllegalStateException( |
171 | DataUtils.ERROR_FILE_LOCKED, |
172 | "The file is locked: {0}", fileName); |
173 | } |
174 | fileSize = file.size(); |
175 | } catch (IOException e) { |
176 | throw DataUtils.newIllegalStateException( |
177 | DataUtils.ERROR_READING_FAILED, |
178 | "Could not open file {0}", fileName, e); |
179 | } |
180 | } |
181 | |
182 | /** |
183 | * Close this store. |
184 | */ |
185 | public void close() { |
186 | try { |
187 | if (fileLock != null) { |
188 | fileLock.release(); |
189 | fileLock = null; |
190 | } |
191 | file.close(); |
192 | freeSpace.clear(); |
193 | } catch (Exception e) { |
194 | throw DataUtils.newIllegalStateException( |
195 | DataUtils.ERROR_WRITING_FAILED, |
196 | "Closing failed for file {0}", fileName, e); |
197 | } finally { |
198 | file = null; |
199 | } |
200 | } |
201 | |
202 | /** |
203 | * Flush all changes. |
204 | */ |
205 | public void sync() { |
206 | try { |
207 | file.force(true); |
208 | } catch (IOException e) { |
209 | throw DataUtils.newIllegalStateException( |
210 | DataUtils.ERROR_WRITING_FAILED, |
211 | "Could not sync file {0}", fileName, e); |
212 | } |
213 | } |
214 | |
215 | /** |
216 | * Get the file size. |
217 | * |
218 | * @return the file size |
219 | */ |
220 | public long size() { |
221 | return fileSize; |
222 | } |
223 | |
224 | /** |
225 | * Truncate the file. |
226 | * |
227 | * @param size the new file size |
228 | */ |
229 | public void truncate(long size) { |
230 | try { |
231 | writeCount++; |
232 | file.truncate(size); |
233 | fileSize = Math.min(fileSize, size); |
234 | } catch (IOException e) { |
235 | throw DataUtils.newIllegalStateException( |
236 | DataUtils.ERROR_WRITING_FAILED, |
237 | "Could not truncate file {0} to size {1}", |
238 | fileName, size, e); |
239 | } |
240 | } |
241 | |
242 | /** |
243 | * Get the file instance in use. |
244 | * <p> |
245 | * The application may read from the file (for example for online backup), |
246 | * but not write to it or truncate it. |
247 | * |
248 | * @return the file |
249 | */ |
250 | public FileChannel getFile() { |
251 | return file; |
252 | } |
253 | |
254 | /** |
255 | * Get the encrypted file instance, if encryption is used. |
256 | * <p> |
257 | * The application may read from the file (for example for online backup), |
258 | * but not write to it or truncate it. |
259 | * |
260 | * @return the encrypted file, or null if encryption is not used |
261 | */ |
262 | public FileChannel getEncryptedFile() { |
263 | return encryptedFile; |
264 | } |
265 | |
266 | /** |
267 | * Get the number of write operations since this store was opened. |
268 | * For file based stores, this is the number of file write operations. |
269 | * |
270 | * @return the number of write operations |
271 | */ |
272 | public long getWriteCount() { |
273 | return writeCount; |
274 | } |
275 | |
276 | /** |
277 | * Get the number of written bytes since this store was opened. |
278 | * |
279 | * @return the number of write operations |
280 | */ |
281 | public long getWriteBytes() { |
282 | return writeBytes; |
283 | } |
284 | |
285 | /** |
286 | * Get the number of read operations since this store was opened. |
287 | * For file based stores, this is the number of file read operations. |
288 | * |
289 | * @return the number of read operations |
290 | */ |
291 | public long getReadCount() { |
292 | return readCount; |
293 | } |
294 | |
295 | /** |
296 | * Get the number of read bytes since this store was opened. |
297 | * |
298 | * @return the number of write operations |
299 | */ |
300 | public long getReadBytes() { |
301 | return readBytes; |
302 | } |
303 | |
304 | public boolean isReadOnly() { |
305 | return readOnly; |
306 | } |
307 | |
308 | /** |
309 | * Get the default retention time for this store in milliseconds. |
310 | * |
311 | * @return the retention time |
312 | */ |
313 | public int getDefaultRetentionTime() { |
314 | return 45000; |
315 | } |
316 | |
317 | /** |
318 | * Mark the space as in use. |
319 | * |
320 | * @param pos the position in bytes |
321 | * @param length the number of bytes |
322 | */ |
323 | public void markUsed(long pos, int length) { |
324 | freeSpace.markUsed(pos, length); |
325 | } |
326 | |
327 | /** |
328 | * Allocate a number of blocks and mark them as used. |
329 | * |
330 | * @param length the number of bytes to allocate |
331 | * @return the start position in bytes |
332 | */ |
333 | public long allocate(int length) { |
334 | return freeSpace.allocate(length); |
335 | } |
336 | |
337 | /** |
338 | * Mark the space as free. |
339 | * |
340 | * @param pos the position in bytes |
341 | * @param length the number of bytes |
342 | */ |
343 | public void free(long pos, int length) { |
344 | freeSpace.free(pos, length); |
345 | } |
346 | |
347 | public int getFillRate() { |
348 | return freeSpace.getFillRate(); |
349 | } |
350 | |
351 | long getFirstFree() { |
352 | return freeSpace.getFirstFree(); |
353 | } |
354 | |
355 | /** |
356 | * Mark the file as empty. |
357 | */ |
358 | public void clear() { |
359 | freeSpace.clear(); |
360 | } |
361 | |
362 | /** |
363 | * Get the file name. |
364 | * |
365 | * @return the file name |
366 | */ |
367 | public String getFileName() { |
368 | return fileName; |
369 | } |
370 | |
371 | } |