001: /*
002:
003: Derby - Class org.apache.derby.impl.store.raw.data.EncryptData
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.store.raw.data;
023:
024: import org.apache.derby.iapi.reference.SQLState;
025: import org.apache.derby.iapi.services.context.ContextManager;
026: import org.apache.derby.iapi.services.daemon.Serviceable;
027: import org.apache.derby.iapi.services.sanity.SanityManager;
028: import org.apache.derby.iapi.error.StandardException;
029: import org.apache.derby.iapi.store.raw.data.RawContainerHandle;
030: import org.apache.derby.iapi.store.raw.ContainerKey;
031: import org.apache.derby.iapi.store.raw.LockingPolicy;
032: import org.apache.derby.iapi.store.raw.Transaction;
033: import org.apache.derby.iapi.store.raw.xact.RawTransaction;
034: import org.apache.derby.iapi.store.raw.ContainerHandle;
035: import org.apache.derby.iapi.store.access.TransactionController;
036:
037: import org.apache.derby.io.StorageFactory;
038: import org.apache.derby.io.StorageFile;
039: import org.apache.derby.iapi.util.ReuseFactory;
040: import java.security.AccessController;
041: import java.security.PrivilegedAction;
042:
043: /**
044: * This class is used to encrypt all the containers in the data segment with a
045: * new encryption key when password/key is changed or when an existing database
046: * is reconfigured for encryption.
047: *
048: * Encryption of existing data in the data segments is done by doing the
049: * following:
050: * Find all the containers in data segment (seg0) and encrypt all of them
051: * with the new encryption key, the process for each container is:
052: * 1.Write a log record to indicate that the container is getting encrypted.
053: * 2.Read all the pages of the container through the page cache and
054: * encrypt each page with new encryption key and then write to a
055: * temporary file(n<cid>.dat) in the data segment itself.
056: * 3. Rename the current container file (c<cid>.dat) to
057: * another file (o<cid>.dat)
058: * 4. Rename the new encrypted version of the file (n<cid).dat) to be
059: * the current container file (c<cid>.dat).
060: * 5. All the old version of the container (o<cid>.dat) files are removed
061: * after a successful checkpoint with a new key or on a rollback.
062: *
063: * @author Suresh Thalamati
064: */
065:
066: public class EncryptData implements PrivilegedAction {
067:
068: private BaseDataFileFactory dataFactory;
069: private StorageFactory storageFactory;
070: private StorageFile[] oldFiles;
071: private int noOldFiles = 0;
072:
073: /* privileged actions */
074: private static final int STORAGE_FILE_EXISTS_ACTION = 1;
075: private static final int STORAGE_FILE_DELETE_ACTION = 2;
076: private static final int STORAGE_FILE_RENAME_ACTION = 3;
077: private int actionCode;
078: private StorageFile actionStorageFile;
079: private StorageFile actionDestStorageFile;
080:
081: public EncryptData(BaseDataFileFactory dataFactory) {
082: this .dataFactory = dataFactory;
083: this .storageFactory = dataFactory.getStorageFactory();
084: }
085:
086: /*
087: * Find all the all the containers stored in the data directory and
088: * encrypt them.
089: * @param t the transaction that is used to configure the database
090: * with new encryption properties.
091: * @exception StandardException Standard Derby error policy
092: */
093: public void encryptAllContainers(RawTransaction t)
094: throws StandardException {
095:
096: /*
097: * List of containers that needs to be encrypted are identified by
098: * simply reading the list of files in seg0.
099: */
100:
101: String[] files = dataFactory.getContainerNames();
102: if (files != null) {
103: oldFiles = new StorageFile[files.length];
104: noOldFiles = 0;
105: long segmentId = 0;
106:
107: // loop through all the files in seg0 and
108: // encrypt all valid containers.
109: for (int f = files.length - 1; f >= 0; f--) {
110: long containerId;
111: try {
112: containerId = Long.parseLong(files[f].substring(1,
113: (files[f].length() - 4)), 16);
114: } catch (Throwable th) {
115: // ignore errors from parse, it just means
116: // that someone put a file in seg0 that we
117: // didn't expect. Continue with the next one.
118: continue;
119: }
120:
121: ContainerKey ckey = new ContainerKey(segmentId,
122: containerId);
123: oldFiles[noOldFiles++] = encryptContainer(t, ckey);
124: }
125:
126: // Old versions of the container files will
127: // be removed after the (re)encryption of database
128: // is completed.
129: } else {
130: if (SanityManager.DEBUG)
131: SanityManager
132: .THROWASSERT("encryption process is unable to"
133: + "read container names in seg0");
134: }
135:
136: }
137:
138: /** Encrypt a container.
139: * @param t the transaction that is used to configure the database
140: * with new encryption properties.
141: * @param ckey the key of the container that is being encrypted.
142: * @return file handle to the old copy of the container.
143: * @exception StandardException Standard Derby error policy
144: */
145: private StorageFile encryptContainer(RawTransaction t,
146: ContainerKey ckey) throws StandardException {
147:
148: LockingPolicy cl = t.newLockingPolicy(
149: LockingPolicy.MODE_CONTAINER,
150: TransactionController.ISOLATION_SERIALIZABLE, true);
151:
152: if (SanityManager.DEBUG)
153: SanityManager.ASSERT(cl != null);
154:
155: RawContainerHandle containerHdl = (RawContainerHandle) t
156: .openContainer(ckey, cl, ContainerHandle.MODE_FORUPDATE);
157:
158: if (SanityManager.DEBUG)
159: SanityManager.ASSERT(containerHdl != null);
160:
161: EncryptContainerOperation lop = new EncryptContainerOperation(
162: containerHdl);
163: t.logAndDo(lop);
164:
165: // flush the log to reduce the window between where
166: // the encrypted container is created & synced and the
167: // log record for it makes it to disk. if we fail during
168: // encryption of the container, log record will make sure
169: // container is restored to the original state and
170: // any temporary files are cleaned up.
171: dataFactory.flush(t.getLastLogInstant());
172:
173: // encrypt the container.
174: String newFilePath = getFilePath(ckey, false);
175: StorageFile newFile = storageFactory
176: .newStorageFile(newFilePath);
177: containerHdl.encryptContainer(newFilePath);
178: containerHdl.close();
179:
180: /*
181: * Replace the current container file with the new container file after
182: * keeping a copy of the current container file, it will be removed on
183: * after a checkpoint with new key or on a rollback this copy will be
184: * replace the container file to bring the database back to the
185: * state before encryption process started.
186: */
187:
188: // discard pages in the cache related to this container.
189: if (!dataFactory.getPageCache().discard(ckey)) {
190: if (SanityManager.DEBUG)
191: SanityManager
192: .THROWASSERT("unable to discard pages releated to "
193: + "container "
194: + ckey
195: + " from the page cache");
196: }
197:
198: // get rid of the container entry from conatainer cache
199: if (!dataFactory.getContainerCache().discard(ckey)) {
200: if (SanityManager.DEBUG)
201: SanityManager
202: .THROWASSERT("unable to discard a container "
203: + ckey + " from the container cache");
204: }
205:
206: StorageFile currentFile = dataFactory.getContainerPath(ckey,
207: false);
208: StorageFile oldFile = getFile(ckey, true);
209:
210: if (!privRename(currentFile, oldFile)) {
211: throw StandardException.newException(
212: SQLState.RAWSTORE_ERROR_RENAMING_FILE, currentFile,
213: oldFile);
214: }
215:
216: // now replace current container file with the new file.
217: if (!privRename(newFile, currentFile)) {
218: throw StandardException.newException(
219: SQLState.RAWSTORE_ERROR_RENAMING_FILE, newFile,
220: currentFile);
221:
222: }
223:
224: return oldFile;
225: }
226:
227: /**
228: * Get file handle to a container file that is used to keep
229: * temporary versions of the container file.
230: */
231: private StorageFile getFile(ContainerKey containerId, boolean old) {
232: String path = getFilePath(containerId, old);
233: return storageFactory.newStorageFile(getFilePath(containerId,
234: old));
235: }
236:
237: /**
238: * Get path to a container file that is used to keep temporary versions of
239: * the container file.
240: */
241: private String getFilePath(ContainerKey containerId, boolean old) {
242: StringBuffer sb = new StringBuffer("seg");
243: sb.append(containerId.getSegmentId());
244: sb.append(storageFactory.getSeparator());
245: sb.append(old ? 'o' : 'n');
246: sb.append(Long.toHexString(containerId.getContainerId()));
247: sb.append(".dat");
248: return sb.toString();
249: }
250:
251: private boolean isOldContainerFile(String fileName) {
252: // all old versions of the conatainer files
253: // start with prefix "o" and ends with ".dat"
254: if (fileName.startsWith("o") && fileName.endsWith(".dat"))
255: return true;
256: else
257: return false;
258: }
259:
260: private StorageFile getFile(String ctrFileName) {
261: long segmentId = 0;
262: StringBuffer sb = new StringBuffer("seg");
263: sb.append(segmentId);
264: sb.append(storageFactory.getSeparator());
265: sb.append(ctrFileName);
266: return storageFactory.newStorageFile(sb.toString());
267: }
268:
269: /* Restore the contaier to the state it was before
270: * it was encrypted with new encryption key. This function is
271: * called during undo of the EncryptContainerOperation log record
272: * incase of a error/crash before database was successfuly configured with
273: * new encryption properties.
274: * @param ckey the key of the container that needs to be restored.
275: * @exception StandardException Standard Derby error policy
276: */
277: void restoreContainer(ContainerKey containerId)
278: throws StandardException {
279:
280: // get rid of the container entry from conatainer cache,
281: // this will make sure there are no file opens on the current
282: // container file.
283:
284: if (!dataFactory.getContainerCache().discard(containerId)) {
285: if (SanityManager.DEBUG)
286: SanityManager
287: .THROWASSERT("unable to discard container from cache:"
288: + containerId);
289: }
290:
291: StorageFile currentFile = dataFactory.getContainerPath(
292: containerId, false);
293: StorageFile oldFile = getFile(containerId, true);
294: StorageFile newFile = getFile(containerId, false);
295:
296: // if backup of the original container file exists, replace the
297: // container with the backup copy.
298: if (privExists(oldFile)) {
299: if (privExists(currentFile)) {
300: // rename the current container file to be the new file.
301: if (!privRename(currentFile, newFile)) {
302: throw StandardException.newException(
303: SQLState.RAWSTORE_ERROR_RENAMING_FILE,
304: currentFile, newFile);
305: }
306: }
307:
308: if (!privRename(oldFile, currentFile)) {
309: throw StandardException.newException(
310: SQLState.RAWSTORE_ERROR_RENAMING_FILE, oldFile,
311: currentFile);
312: }
313: }
314:
315: // if the new copy of the container file exists, remove it.
316: if (privExists(newFile)) {
317:
318: if (!privDelete(newFile))
319: throw StandardException.newException(
320: SQLState.UNABLE_TO_DELETE_FILE, newFile);
321: }
322: }
323:
324: /*
325: * Remove all the old version (encrypted with old key or
326: * un-encrypted) of the containers stored in the data directory .
327: *
328: * @param inRecovery <code> true </code>, if cleanup is
329: * happening during recovery.
330: * @exception StandardException Standard Derby Error Policy
331: */
332: public void removeOldVersionOfContainers(boolean inRecovery)
333: throws StandardException {
334:
335: if (inRecovery) {
336: // find the old version of the container files
337: // and delete them
338: String[] files = dataFactory.getContainerNames();
339: if (files != null) {
340: // loop through all the files in seg0 and
341: // delete all old copies of the containers.
342: for (int i = files.length - 1; i >= 0; i--) {
343: // if it is a old version of the container file
344: // delete it.
345: if (isOldContainerFile(files[i])) {
346: StorageFile oldFile = getFile(files[i]);
347: if (!privDelete(oldFile)) {
348: throw StandardException.newException(
349: SQLState.FILE_CANNOT_REMOVE_FILE,
350: oldFile);
351: }
352: }
353: }
354: }
355: } else {
356: // delete all the old version of the containers.
357: for (int i = 0; i < noOldFiles; i++) {
358: if (!privDelete(oldFiles[i])) {
359: throw StandardException.newException(
360: SQLState.FILE_CANNOT_REMOVE_FILE,
361: oldFiles[i]);
362: }
363: }
364: }
365: }
366:
367: private synchronized boolean privExists(StorageFile file) {
368: actionCode = STORAGE_FILE_EXISTS_ACTION;
369: actionStorageFile = file;
370: Object ret = AccessController.doPrivileged(this );
371: actionStorageFile = null;
372: return ((Boolean) ret).booleanValue();
373:
374: }
375:
376: private synchronized boolean privDelete(StorageFile file) {
377: actionCode = STORAGE_FILE_DELETE_ACTION;
378: actionStorageFile = file;
379: Object ret = AccessController.doPrivileged(this );
380: actionStorageFile = null;
381: return ((Boolean) ret).booleanValue();
382:
383: }
384:
385: private synchronized boolean privRename(StorageFile fromFile,
386: StorageFile destFile) {
387: actionCode = STORAGE_FILE_RENAME_ACTION;
388: actionStorageFile = fromFile;
389: actionDestStorageFile = destFile;
390: Object ret = AccessController.doPrivileged(this );
391: actionStorageFile = null;
392: actionDestStorageFile = null;
393: return ((Boolean) ret).booleanValue();
394:
395: }
396:
397: // PrivilegedAction method
398: public Object run() {
399: switch (actionCode) {
400: case STORAGE_FILE_EXISTS_ACTION:
401: return ReuseFactory.getBoolean(actionStorageFile.exists());
402: case STORAGE_FILE_DELETE_ACTION:
403: return ReuseFactory.getBoolean(actionStorageFile.delete());
404: case STORAGE_FILE_RENAME_ACTION:
405: return ReuseFactory.getBoolean(actionStorageFile
406: .renameTo(actionDestStorageFile));
407: }
408:
409: return null;
410: }
411: }
|