--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.raw.data.EncryptData\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.store.raw.data;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.context.ContextManager;\r
+import org.apache.derby.iapi.services.daemon.Serviceable;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.store.raw.data.RawContainerHandle;\r
+import org.apache.derby.iapi.store.raw.ContainerKey;\r
+import org.apache.derby.iapi.store.raw.LockingPolicy;\r
+import org.apache.derby.iapi.store.raw.Transaction;\r
+import org.apache.derby.iapi.store.raw.xact.RawTransaction;\r
+import org.apache.derby.iapi.store.raw.ContainerHandle;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+\r
+import org.apache.derby.io.StorageFactory;\r
+import org.apache.derby.io.StorageFile;\r
+import org.apache.derby.iapi.util.ReuseFactory;\r
+import java.security.AccessController;\r
+import java.security.PrivilegedAction;\r
+\r
+\r
+/**\r
+ * This class is used to encrypt all the containers in the data segment with a \r
+ * new encryption key when password/key is changed or when an existing database \r
+ * is reconfigured for encryption. \r
+ * \r
+ * Encryption of existing data in the data segments is done by doing the \r
+ * following:\r
+ * Find all the containers in data segment (seg0) and encrypt all of them\r
+ * with the new encryption key, the process for each container is:\r
+ * 1.Write a log record to indicate that the container is getting encrypted. \r
+ * 2.Read all the pages of the container through the page cache and\r
+ * encrypt each page with new encryption key and then write to a \r
+ * temporary file(n<cid>.dat) in the data segment itself.\r
+ * 3. Rename the current container file (c<cid>.dat) to \r
+ * another file (o<cid>.dat)\r
+ * 4. Rename the new encrypted version of the file (n<cid).dat) to be \r
+ * the current container file (c<cid>.dat).\r
+ * 5. All the old version of the container (o<cid>.dat) files are removed\r
+ * after a successful checkpoint with a new key or on a rollback.\r
+ * \r
+ */\r
+\r
+public class EncryptData implements PrivilegedAction {\r
+\r
+ private BaseDataFileFactory dataFactory;\r
+ private StorageFactory storageFactory;\r
+ private StorageFile[] oldFiles;\r
+ private int noOldFiles = 0; \r
+\r
+\r
+ /* privileged actions */\r
+ private static final int STORAGE_FILE_EXISTS_ACTION = 1;\r
+ private static final int STORAGE_FILE_DELETE_ACTION = 2;\r
+ private static final int STORAGE_FILE_RENAME_ACTION = 3;\r
+ private int actionCode;\r
+ private StorageFile actionStorageFile;\r
+ private StorageFile actionDestStorageFile;\r
+\r
+ public EncryptData(BaseDataFileFactory dataFactory) {\r
+ this.dataFactory = dataFactory;\r
+ this.storageFactory = dataFactory.getStorageFactory();\r
+ }\r
+\r
+\r
+ /*\r
+ * Find all the all the containers stored in the data directory and \r
+ * encrypt them.\r
+ * @param t the transaction that is used to configure the database \r
+ * with new encryption properties.\r
+ * @exception StandardException Standard Derby error policy\r
+ */\r
+ public void encryptAllContainers(RawTransaction t) \r
+ throws StandardException {\r
+\r
+ /*\r
+ * List of containers that needs to be encrypted are identified by \r
+ * simply reading the list of files in seg0. \r
+ */\r
+\r
+ String[] files = dataFactory.getContainerNames();\r
+ if (files != null) {\r
+ oldFiles = new StorageFile[files.length];\r
+ noOldFiles = 0;\r
+ long segmentId = 0;\r
+\r
+ // loop through all the files in seg0 and \r
+ // encrypt all valid containers.\r
+ for (int f = files.length-1; f >= 0 ; f--) {\r
+ long containerId;\r
+ try {\r
+ containerId = \r
+ Long.parseLong(files[f].substring(1, \r
+ (files[f].length() -4)), 16);\r
+ }\r
+ catch (Throwable th)\r
+ {\r
+ // ignore errors from parse, it just means \r
+ // that someone put a file in seg0 that we \r
+ // didn't expect. Continue with the next one.\r
+ continue;\r
+ }\r
+\r
+ ContainerKey ckey = new ContainerKey(segmentId, \r
+ containerId);\r
+ oldFiles[noOldFiles++] = encryptContainer(t, ckey);\r
+ }\r
+\r
+ // Old versions of the container files will\r
+ // be removed after the (re)encryption of database\r
+ // is completed. \r
+ } else\r
+ {\r
+ if (SanityManager.DEBUG) \r
+ SanityManager.THROWASSERT("encryption process is unable to" +\r
+ "read container names in seg0");\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /** Encrypt a container.\r
+ * @param t the transaction that is used to configure the database \r
+ * with new encryption properties.\r
+ * @param ckey the key of the container that is being encrypted.\r
+ * @return file handle to the old copy of the container.\r
+ * @exception StandardException Standard Derby error policy\r
+ */\r
+ private StorageFile encryptContainer(RawTransaction t, \r
+ ContainerKey ckey)\r
+ throws StandardException\r
+ {\r
+\r
+ LockingPolicy cl = \r
+ t.newLockingPolicy(\r
+ LockingPolicy.MODE_CONTAINER,\r
+ TransactionController.ISOLATION_SERIALIZABLE, \r
+ true);\r
+ \r
+ if (SanityManager.DEBUG )\r
+ SanityManager.ASSERT(cl != null);\r
+\r
+ RawContainerHandle containerHdl = (RawContainerHandle)\r
+ t.openContainer(ckey, cl, ContainerHandle.MODE_FORUPDATE);\r
+\r
+ if (SanityManager.DEBUG )\r
+ SanityManager.ASSERT(containerHdl != null);\r
+\r
+ EncryptContainerOperation lop = \r
+ new EncryptContainerOperation(containerHdl);\r
+ t.logAndDo(lop);\r
+ \r
+ // flush the log to reduce the window between where\r
+ // the encrypted container is created & synced and the \r
+ // log record for it makes it to disk. if we fail during \r
+ // encryption of the container, log record will make sure \r
+ // container is restored to the original state and \r
+ // any temporary files are cleaned up. \r
+ dataFactory.flush(t.getLastLogInstant());\r
+\r
+ // encrypt the container.\r
+ String newFilePath = getFilePath(ckey, false);\r
+ StorageFile newFile = storageFactory.newStorageFile(newFilePath);\r
+ containerHdl.encryptContainer(newFilePath);\r
+ containerHdl.close();\r
+\r
+ \r
+ /*\r
+ * Replace the current container file with the new container file after\r
+ * keeping a copy of the current container file, it will be removed on \r
+ * after a checkpoint with new key or on a rollback this copy will be \r
+ * replace the container file to bring the database back to the \r
+ * state before encryption process started. \r
+ */\r
+\r
+ // discard pages in the cache related to this container. \r
+ if (!dataFactory.getPageCache().discard(ckey)) {\r
+ if (SanityManager.DEBUG )\r
+ SanityManager.THROWASSERT("unable to discard pages releated to " + \r
+ "container " + ckey + \r
+ " from the page cache");\r
+ }\r
+\r
+\r
+ // get rid of the container entry from conatainer cache\r
+ if (!dataFactory.getContainerCache().discard(ckey)) {\r
+ if (SanityManager.DEBUG )\r
+ SanityManager.THROWASSERT("unable to discard a container " + \r
+ ckey + " from the container cache");\r
+ }\r
+\r
+ StorageFile currentFile = dataFactory.getContainerPath(ckey , false);\r
+ StorageFile oldFile = getFile(ckey, true);\r
+\r
+ if (!privRename(currentFile, oldFile)) {\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_RENAMING_FILE,\r
+ currentFile, oldFile);\r
+ }\r
+\r
+ // now replace current container file with the new file. \r
+ if (!privRename(newFile, currentFile)) {\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_RENAMING_FILE,\r
+ newFile, currentFile);\r
+ \r
+ }\r
+\r
+ return oldFile ;\r
+ }\r
+\r
+ \r
+ /**\r
+ * Get file handle to a container file that is used to keep \r
+ * temporary versions of the container file. \r
+ */\r
+ private StorageFile getFile(ContainerKey containerId, boolean old) {\r
+ String path = getFilePath(containerId, old);\r
+ return storageFactory.newStorageFile(getFilePath(containerId, \r
+ old));\r
+ }\r
+\r
+ /**\r
+ * Get path to a container file that is used to keep temporary versions of\r
+ * the container file. \r
+ */\r
+ private String getFilePath(ContainerKey containerId, boolean old) {\r
+ StringBuffer sb = new StringBuffer("seg");\r
+ sb.append(containerId.getSegmentId());\r
+ sb.append(storageFactory.getSeparator());\r
+ sb.append(old ? 'o' : 'n');\r
+ sb.append(Long.toHexString(containerId.getContainerId()));\r
+ sb.append(".dat");\r
+ return sb.toString();\r
+ }\r
+\r
+ private boolean isOldContainerFile(String fileName) \r
+ {\r
+ // all old versions of the conatainer files\r
+ // start with prefix "o" and ends with ".dat"\r
+ if (fileName.startsWith("o") && fileName.endsWith(".dat"))\r
+ return true;\r
+ else\r
+ return false;\r
+ }\r
+\r
+ private StorageFile getFile(String ctrFileName) \r
+ {\r
+ long segmentId = 0;\r
+ StringBuffer sb = new StringBuffer("seg");\r
+ sb.append(segmentId);\r
+ sb.append(storageFactory.getSeparator());\r
+ sb.append(ctrFileName);\r
+ return storageFactory.newStorageFile(sb.toString());\r
+ }\r
+\r
+ /* Restore the contaier to the state it was before \r
+ * it was encrypted with new encryption key. This function is \r
+ * called during undo of the EncryptContainerOperation log record \r
+ * incase of a error/crash before database was successfuly configured with\r
+ * new encryption properties.\r
+ * @param ckey the key of the container that needs to be restored.\r
+ * @exception StandardException Standard Derby error policy\r
+ */\r
+ void restoreContainer(ContainerKey containerId) \r
+ throws StandardException \r
+ {\r
+\r
+ // get rid of the container entry from conatainer cache,\r
+ // this will make sure there are no file opens on the current \r
+ // container file. \r
+ \r
+ if (!dataFactory.getContainerCache().discard(containerId)) {\r
+ if (SanityManager.DEBUG )\r
+ SanityManager.THROWASSERT(\r
+ "unable to discard container from cache:" + \r
+ containerId);\r
+ }\r
+\r
+ StorageFile currentFile = dataFactory.getContainerPath(containerId, \r
+ false);\r
+ StorageFile oldFile = getFile(containerId, true);\r
+ StorageFile newFile = getFile(containerId, false);\r
+ \r
+ // if backup of the original container file exists, replace the \r
+ // container with the backup copy.\r
+ if (privExists(oldFile)) {\r
+ if (privExists(currentFile)) {\r
+ // rename the current container file to be the new file.\r
+ if (!privRename(currentFile, newFile)) {\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_RENAMING_FILE,\r
+ currentFile, newFile);\r
+ }\r
+ }\r
+\r
+ if (!privRename(oldFile, currentFile)) {\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_RENAMING_FILE,\r
+ oldFile, currentFile);\r
+ }\r
+ }\r
+\r
+ // if the new copy of the container file exists, remove it.\r
+ if (privExists(newFile)) {\r
+\r
+ if (!privDelete(newFile))\r
+ throw StandardException.newException(\r
+ SQLState.UNABLE_TO_DELETE_FILE, \r
+ newFile);\r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ * Remove all the old version (encrypted with old key or \r
+ * un-encrypted) of the containers stored in the data directory .\r
+ *\r
+ * @param inRecovery <code> true </code>, if cleanup is \r
+ * happening during recovery.\r
+ * @exception StandardException Standard Derby Error Policy\r
+ */\r
+ public void removeOldVersionOfContainers(boolean inRecovery) \r
+ throws StandardException\r
+ {\r
+ \r
+ if (inRecovery) \r
+ {\r
+ // find the old version of the container files\r
+ // and delete them\r
+ String[] files = dataFactory.getContainerNames();\r
+ if (files != null) \r
+ {\r
+ // loop through all the files in seg0 and \r
+ // delete all old copies of the containers.\r
+ for (int i = files.length-1; i >= 0 ; i--) \r
+ {\r
+ // if it is a old version of the container file\r
+ // delete it. \r
+ if (isOldContainerFile(files[i]))\r
+ {\r
+ StorageFile oldFile = getFile(files[i]);\r
+ if (!privDelete(oldFile)) \r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.FILE_CANNOT_REMOVE_FILE,\r
+ oldFile);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }else \r
+ {\r
+ // delete all the old version of the containers. \r
+ for (int i = 0 ; i < noOldFiles ; i++) \r
+ {\r
+ if (!privDelete(oldFiles[i])) \r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.FILE_CANNOT_REMOVE_FILE, \r
+ oldFiles[i]);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ \r
+ private synchronized boolean privExists(StorageFile file)\r
+ {\r
+ actionCode = STORAGE_FILE_EXISTS_ACTION;\r
+ actionStorageFile = file;\r
+ Object ret = AccessController.doPrivileged(this);\r
+ actionStorageFile = null;\r
+ return ((Boolean) ret).booleanValue();\r
+\r
+ }\r
+\r
+ \r
+ private synchronized boolean privDelete(StorageFile file)\r
+ {\r
+ actionCode = STORAGE_FILE_DELETE_ACTION;\r
+ actionStorageFile = file;\r
+ Object ret = AccessController.doPrivileged(this);\r
+ actionStorageFile = null;\r
+ return ((Boolean) ret).booleanValue();\r
+ \r
+ }\r
+\r
+ private synchronized boolean privRename(StorageFile fromFile, \r
+ StorageFile destFile)\r
+ {\r
+ actionCode = STORAGE_FILE_RENAME_ACTION;\r
+ actionStorageFile = fromFile;\r
+ actionDestStorageFile = destFile;\r
+ Object ret = AccessController.doPrivileged(this);\r
+ actionStorageFile = null;\r
+ actionDestStorageFile = null;\r
+ return ((Boolean) ret).booleanValue();\r
+\r
+ }\r
+\r
+\r
+\r
+ // PrivilegedAction method\r
+ public Object run() \r
+ {\r
+ switch(actionCode)\r
+ {\r
+ case STORAGE_FILE_EXISTS_ACTION:\r
+ return ReuseFactory.getBoolean(actionStorageFile.exists());\r
+ case STORAGE_FILE_DELETE_ACTION:\r
+ return ReuseFactory.getBoolean(actionStorageFile.delete());\r
+ case STORAGE_FILE_RENAME_ACTION:\r
+ return ReuseFactory.getBoolean(\r
+ actionStorageFile.renameTo(actionDestStorageFile));\r
+ }\r
+\r
+ return null;\r
+ }\r
+}\r