001: package com.quadcap.sql.file;
002:
003: /* Copyright 1997 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.io.File;
042: import java.io.FileDescriptor;
043: import java.io.IOException;
044: import java.io.RandomAccessFile;
045:
046: import java.util.BitSet;
047:
048: import com.quadcap.util.Debug;
049: import com.quadcap.util.Util;
050:
051: import com.quadcap.sql.Version;
052:
053: /**
054: * Below the cache, this class handles the actual I/O to the underlying file.
055: * Block write operations are logged by this class.
056: *
057: * @author Stan Bailes
058: */
059: public class BlockStore {
060: File file;
061: FileRandomAccess fra;
062: boolean readOnly;
063: Log log;
064: int blockSize;
065: BitSet modified = new BitSet();
066: Object lock;
067: boolean encrypted = false;
068: boolean isTempFile = false;
069:
070: /**
071: * Create a new BlockStore object using the specified file and
072: * blocksize.
073: *
074: * @param file the underlying file.
075: * @param mode "r" for readonly access, otherwise "rw"
076: * @param blocksize the block size to use when creating the file.
077: * @param the synchronization object
078: */
079: public BlockStore() {
080: }
081:
082: public String getName() {
083: return file == null ? "null" : file.getName();
084: }
085:
086: public void init(File file, String mode, int blockSize, Object lock)
087: throws IOException {
088: this .file = file;
089: boolean exists = file.exists();
090: RandomAccessFile ra = new RandomAccessFile(file
091: .getAbsolutePath(), mode);
092: this .fra = new FileRandomAccess(ra,
093: 1024L * 1024L * 1024L * 1024L);
094: this .readOnly = mode.equalsIgnoreCase("r");
095: this .lock = lock;
096: if (!exists) {
097: createHeader(blockSize);
098: } else {
099: readHeader();
100: }
101: isTempFile = !(file.getPath().endsWith("datafile"));
102: }
103:
104: /**
105: * Specify the log used for recovery operations.
106: */
107: public void setLog(Log log) {
108: this .log = log;
109: }
110:
111: /**
112: * Return the logger
113: */
114: public Log getLog() {
115: return log;
116: }
117:
118: /**
119: * Return the block size of this store.
120: */
121: public int blockSize() {
122: return blockSize;
123: }
124:
125: /**
126: * Return the underlying file
127: */
128: public File getFile() {
129: return file;
130: }
131:
132: /**
133: * Read a block into a buffer. If the specified block is beyond the
134: * current end of file, then grow the file
135: *
136: * @param blockNum the number of the block to read.
137: * @param buf the buffer into which the data is read.
138: */
139: public void read(long blockNum, byte[] buf) throws IOException {
140: read(blockNum, buf, 0);
141: }
142:
143: public void read(long blockNum, byte[] buf, int off)
144: throws IOException {
145: synchronized (lock) {
146: readCount++;
147: fra.read(blockNum * blockSize, buf, off, blockSize);
148: }
149: }
150:
151: public static int writeCount = 0;
152: public static int readCount = 0;
153:
154: /**
155: * Write a block from a buffer into the file.
156: *
157: * @param blockNum the number of the block to write.
158: * @param buf the buffer from which the data is written.
159: * @exception IOException if an I/O error occurs.
160: */
161: public void write(long blockNum, byte[] buf) throws IOException {
162: synchronized (lock) {
163: writeCount++;
164: if (readOnly) {
165: throw new IOException(
166: "Attempt to write readonly file: "
167: + file.getName());
168: }
169: long pos = blockNum * blockSize;
170: if (log != null && !modified.get((int) blockNum)) {
171: modified.set((int) blockNum);
172: if (pos < fra.size()) {
173: log.saveBlock(blockNum);
174: }
175: }
176: //#ifdef DEBUG
177: if (Trace.bit(20)) {
178: Debug.println(toString() + ".write(" + blockNum + ","
179: + Block.signature(buf) + ") "
180: + Util.stackTrace());
181: }
182: //#endif
183: fra.write(blockNum * blockSize, buf, 0, buf.length);
184: }
185: }
186:
187: /**
188: * Restore a block image
189: */
190: public void restore(long blockNum, byte[] buf, int off)
191: throws IOException {
192: synchronized (lock) {
193: //#ifdef DEBUG
194: if (Trace.bit(20)) {
195: Debug.println(toString() + ".restore(" + blockNum + ","
196: + Block.signature(buf, off, blockSize) + ")");
197: }
198: //#endif
199: fra.write(blockNum * blockSize, buf, off, blockSize);
200: }
201:
202: }
203:
204: public void clearModified() throws IOException {
205: modified = new BitSet();
206: if (log != null)
207: log.resetBlocks();
208: }
209:
210: public void setLength(long length) throws IOException {
211: if (readOnly) {
212: throw new IOException("Attempt to write readonly file: "
213: + file.getName());
214: }
215: fra.resize(length);
216: }
217:
218: public boolean isEncrypted() {
219: return encrypted;
220: }
221:
222: private void readHeader() throws IOException {
223: byte[] buf = new byte[32];
224: fra.read(0, buf, 0, buf.length);
225: int v = ((buf[0] & 0xff) << 8) + (buf[1] & 0xff);
226: switch (v) {
227: case 0x040c: // QED.
228: encrypted = false;
229: break;
230: case 0x0e0c: // Encrypted QED.
231: encrypted = true;
232: break;
233: default:
234: throw new IOException("BlockFile: bad magic");
235: }
236: // XXX This is hokey. We've tied database versions to the
237: // XXX product version numbers somehow, so we complain about
238: // XXX incompatibility based on the wrong version. Need
239: // XXX BlockFile, Database, versions....
240: if (buf[2] != Version.majorVersion) {
241: throw new IOException("BlockFile: bad version ("
242: + (int) buf[2] + "." + (int) buf[3] + " vs "
243: + Version.majorVersion + "." + Version.minorVersion
244: + ")");
245: }
246: this .blockSize = ByteUtil.getInt(buf, BlockFile.oBLOCKSIZE);
247: }
248:
249: private void createHeader(int blockSize) throws IOException {
250: this .blockSize = blockSize;
251: byte[] buf = newHeader(blockSize, 2);
252: fra.write(0, buf, 0, buf.length);
253: }
254:
255: protected final byte[] newHeader(int blockSize, long lastBlock) {
256: byte[] buf = new byte[blockSize];
257: for (int i = 0; i < blockSize; i++)
258: buf[i] = (byte) 0;
259: buf[0] = 0x4;
260: buf[1] = 0xc; // magic
261: buf[2] = Version.majorVersion;
262: buf[3] = Version.minorVersion;
263: ByteUtil.putInt(buf, BlockFile.oBLOCKSIZE, blockSize);
264: ByteUtil.putLong(buf, BlockFile.oLASTBLOCK, lastBlock); // allocated one block
265: return buf;
266: }
267:
268: public void close() throws IOException {
269: try {
270: if (fra != null)
271: fra.close();
272: } finally {
273: fra = null;
274: }
275: }
276:
277: public void flush() throws IOException {
278: try {
279: fra.flush();
280: readCount = writeCount = 0;
281: } catch (Throwable t) {
282: }
283: }
284:
285: //#ifdef JDK14
286: public void setKey(com.quadcap.crypto.SymmetricKey key)
287: throws IOException {
288: throw new IOException("Not encrypted");
289: }
290:
291: //#endif
292:
293: //#ifdef DEBUG
294: public String toString() {
295: return "BlockStore(" + file.getPath() + ")";
296: }
297:
298: public static String rw() {
299: return "(r=" + BlockStore.readCount + " w="
300: + BlockStore.writeCount + ")";
301: }
302:
303: //#endif
304:
305: }
|