--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.raw.RawStore\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;\r
+\r
+import org.apache.derby.iapi.services.daemon.DaemonFactory;\r
+import org.apache.derby.iapi.services.daemon.DaemonService;\r
+import org.apache.derby.iapi.services.context.ContextManager;\r
+import org.apache.derby.iapi.services.context.ContextService;\r
+import org.apache.derby.iapi.services.crypto.CipherFactoryBuilder;\r
+import org.apache.derby.iapi.services.crypto.CipherFactory;\r
+import org.apache.derby.iapi.services.crypto.CipherProvider;\r
+import org.apache.derby.iapi.services.locks.CompatibilitySpace;\r
+import org.apache.derby.iapi.services.locks.LockFactory;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.monitor.ModuleControl;\r
+import org.apache.derby.iapi.services.monitor.ModuleSupportable;\r
+import org.apache.derby.iapi.services.monitor.PersistentService;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+\r
+import org.apache.derby.iapi.services.property.PersistentSet;\r
+import org.apache.derby.iapi.store.access.TransactionInfo;\r
+import org.apache.derby.iapi.store.access.AccessFactoryGlobals;\r
+import org.apache.derby.iapi.store.access.FileResource;\r
+import org.apache.derby.iapi.store.raw.ScanHandle;\r
+import org.apache.derby.iapi.store.raw.RawStoreFactory;\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.xact.TransactionFactory;\r
+import org.apache.derby.iapi.store.raw.data.DataFactory;\r
+import org.apache.derby.iapi.store.raw.log.LogFactory;\r
+import org.apache.derby.iapi.store.raw.log.LogInstant;\r
+import org.apache.derby.impl.services.monitor.UpdateServiceProperties;\r
+\r
+import org.apache.derby.io.StorageFactory;\r
+import org.apache.derby.io.WritableStorageFactory;\r
+import org.apache.derby.io.StorageFile;\r
+import org.apache.derby.iapi.store.access.DatabaseInstant;\r
+import org.apache.derby.catalog.UUID;\r
+import org.apache.derby.iapi.services.property.PropertyUtil;\r
+import org.apache.derby.iapi.services.io.FileUtil;\r
+import org.apache.derby.iapi.util.ReuseFactory;\r
+import org.apache.derby.iapi.util.StringUtil;\r
+import org.apache.derby.iapi.reference.Attribute;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.reference.MessageId;\r
+import org.apache.derby.iapi.reference.Property;\r
+\r
+import java.security.AccessController;\r
+import java.security.PrivilegedActionException;\r
+import java.security.PrivilegedExceptionAction;\r
+import java.security.SecureRandom;\r
+\r
+import java.util.Date;\r
+import java.util.Properties;\r
+import java.io.Serializable;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.FileNotFoundException;\r
+import java.io.OutputStreamWriter;\r
+\r
+import java.net.MalformedURLException;\r
+import java.net.URL;\r
+\r
+import java.security.PrivilegedExceptionAction;\r
+import java.lang.SecurityException;\r
+\r
+\r
+/**\r
+ A Raw store that implements the RawStoreFactory module by delegating all the\r
+ work to the lower modules TransactionFactory, LogFactory and DataFactory.\r
+ <PRE>\r
+ String TransactionFactoryId=<moduleIdentifier>\r
+ </PRE>\r
+ \r
+ <P>\r
+ Class is final as it has methods with privilege blocks\r
+ and implements PrivilegedExceptionAction.\r
+*/\r
+\r
+public final class RawStore implements RawStoreFactory, ModuleControl, ModuleSupportable, PrivilegedExceptionAction\r
+{\r
+ private static final String BACKUP_HISTORY = "BACKUP.HISTORY";\r
+ protected TransactionFactory xactFactory;\r
+ protected DataFactory dataFactory;\r
+ protected LogFactory logFactory;\r
+ private StorageFactory storageFactory;\r
+\r
+ private SecureRandom random;\r
+ private boolean databaseEncrypted;\r
+ private boolean encryptDatabase;\r
+ private CipherProvider encryptionEngine;\r
+ private CipherProvider decryptionEngine;\r
+ private CipherProvider newEncryptionEngine;\r
+ private CipherProvider newDecryptionEngine;\r
+ private CipherFactory currentCipherFactory;\r
+ private CipherFactory newCipherFactory = null;\r
+ private int counter_encrypt;\r
+ private int counter_decrypt;\r
+ private int encryptionBlockSize = RawStoreFactory.DEFAULT_ENCRYPTION_BLOCKSIZE;\r
+\r
+ String dataDirectory; // where files are stored \r
+\r
+ // this daemon takes care of all daemon work for this raw store\r
+ protected DaemonService rawStoreDaemon;\r
+\r
+ private int actionCode;\r
+ private static final int FILE_WRITER_ACTION = 1;\r
+ private StorageFile actionStorageFile;\r
+ private StorageFile actionToStorageFile;\r
+ private boolean actionAppend;\r
+ private static final int REGULAR_FILE_EXISTS_ACTION = 2;\r
+ private File actionRegularFile;\r
+ private static final int STORAGE_FILE_EXISTS_ACTION = 3;\r
+ private static final int REGULAR_FILE_DELETE_ACTION = 4;\r
+ private static final int REGULAR_FILE_MKDIRS_ACTION = 5;\r
+ private static final int REGULAR_FILE_IS_DIRECTORY_ACTION = 6;\r
+ private static final int REGULAR_FILE_REMOVE_DIRECTORY_ACTION = 7;\r
+ private static final int REGULAR_FILE_RENAME_TO_ACTION = 8;\r
+ private File actionRegularFile2;\r
+ private static final int COPY_STORAGE_DIRECTORY_TO_REGULAR_ACTION = 9;\r
+ private byte[] actionBuffer;\r
+ private String[] actionFilter;\r
+ private boolean actionCopySubDirs;\r
+ private static final int COPY_REGULAR_DIRECTORY_TO_STORAGE_ACTION = 10;\r
+ private static final int COPY_REGULAR_FILE_TO_STORAGE_ACTION = 11;\r
+ private static final int REGULAR_FILE_LIST_DIRECTORY_ACTION = 12;\r
+ private static final int STORAGE_FILE_LIST_DIRECTORY_ACTION = 13;\r
+ private static final int COPY_STORAGE_FILE_TO_REGULAR_ACTION = 14;\r
+ private static final int REGULAR_FILE_GET_CANONICALPATH_ACTION = 15;\r
+ private static final int STORAGE_FILE_GET_CANONICALPATH_ACTION = 16;\r
+ private static final int COPY_STORAGE_FILE_TO_STORAGE_ACTION = 17;\r
+ private static final int STORAGE_FILE_DELETE_ACTION = 18;\r
+\r
+ public RawStore() {\r
+ }\r
+\r
+ /*\r
+ ** Methods of ModuleControl\r
+ */\r
+\r
+ /**\r
+ We use this RawStore for all databases.\r
+ */\r
+ public boolean canSupport(Properties startParams) {\r
+ return true;\r
+ }\r
+\r
+ public void boot(boolean create, Properties properties)\r
+ throws StandardException\r
+ {\r
+ dataDirectory = properties.getProperty(PersistentService.ROOT);\r
+ DaemonFactory daemonFactory =\r
+ (DaemonFactory)Monitor.startSystemModule(org.apache.derby.iapi.reference.Module.DaemonFactory);\r
+ //Commented by Jeff Huang\r
+ //rawStoreDaemon = daemonFactory.createNewDaemon("rawStoreDaemon");\r
+ xactFactory = (TransactionFactory)\r
+ Monitor.bootServiceModule(\r
+ create, this, getTransactionFactoryModule(), properties);\r
+\r
+ dataFactory = (DataFactory)\r
+ Monitor.bootServiceModule(\r
+ create, this, getDataFactoryModule(), properties);\r
+ storageFactory = dataFactory.getStorageFactory();\r
+\r
+ String restoreFromBackup = null;\r
+\r
+ if (properties != null)\r
+ {\r
+ // check if this is a restore from a backup copy. \r
+ restoreFromBackup = properties.getProperty(Attribute.CREATE_FROM);\r
+ if(restoreFromBackup == null)\r
+ restoreFromBackup = properties.getProperty(Attribute.RESTORE_FROM);\r
+ if(restoreFromBackup == null)\r
+ restoreFromBackup =\r
+ properties.getProperty(Attribute.ROLL_FORWARD_RECOVERY_FROM);\r
+\r
+ }\r
+\r
+ // setup database encryption engines.\r
+ if (create) \r
+ setupEncryptionEngines(create, properties);\r
+\r
+\r
+ // let everyone knows who their rawStoreFactory is and they can use it\r
+ // to get to other modules\r
+ // pass in create and properties to dataFactory so it can boot the log\r
+ // factory\r
+\r
+ dataFactory.setRawStoreFactory(this, create, properties);\r
+ xactFactory.setRawStoreFactory(this);\r
+\r
+ if( properties instanceof UpdateServiceProperties)\r
+ {\r
+ if( storageFactory instanceof WritableStorageFactory)\r
+ ((UpdateServiceProperties)properties).setStorageFactory( (WritableStorageFactory) storageFactory);\r
+ }\r
+ \r
+ // log factory is booted by the data factory\r
+ logFactory =(LogFactory) Monitor.findServiceModule(this, getLogFactoryModule());\r
+\r
+ // if this is a restore from backup, restore the jar files.\r
+ if(restoreFromBackup !=null)\r
+ {\r
+ restoreRemainingFromBackup(restoreFromBackup);\r
+ }\r
+\r
+ // If the log is at another location, make sure service.properties\r
+ // file has it.\r
+ String logDevice = properties.getProperty(Attribute.LOG_DEVICE);\r
+ if (logDevice !=null)\r
+ {\r
+ if (!isReadOnly() // We do not care about log location if read only\r
+ && (create \r
+ || !logDevice.equals(logFactory.getCanonicalLogPath()) \r
+ || restoreFromBackup!=null))\r
+ {\r
+ // get the real location from the log factory\r
+ properties.put(Attribute.LOG_DEVICE, logFactory.getCanonicalLogPath());\r
+ //make the log device param stored in backup is same as current log device.\r
+ properties.put(Property.LOG_DEVICE_AT_BACKUP, logFactory.getCanonicalLogPath());\r
+ }\r
+ \r
+ }else{\r
+ //when we restore from a backup logDevice param does not exists \r
+ //in service.properties to support restore using OS commands to work. \r
+ //Instead of logDevice, we user logDeviceWhenBackedUp parameter to\r
+ //identify the log location while restoring createFrom/restoreFrom/rollForwardRecoveryFrom\r
+ //attribute , following make sures the logDevice parameter gets \r
+ //into service.propertues in such cases.\r
+ if(restoreFromBackup!=null && logFactory.getCanonicalLogPath()!=null)\r
+ {\r
+ //logdevice might have got changed because of backup restore. \r
+ properties.put(Attribute.LOG_DEVICE, logFactory.getCanonicalLogPath());\r
+ }\r
+ else{\r
+ //might have been OS copy restore. We default log to db home\r
+ properties.remove(Property.LOG_DEVICE_AT_BACKUP);\r
+ }\r
+ }\r
+\r
+ \r
+ // save the service properties to a file if we are doing a \r
+ // restore from. This marks the end of restore from backup.\r
+ if (restoreFromBackup !=null)\r
+ {\r
+ ((UpdateServiceProperties)properties).saveServiceProperties();\r
+ }\r
+\r
+\r
+ /**\r
+ * Note: service.properties file acts as flags to indicate\r
+ * that the copy from backup is successful.\r
+ * If we reached so far while restoring from backup means\r
+ * we copied all the necessary data from backup. Only thing\r
+ * that remains is roll forwarding the logs. Incase if we crash at this\r
+ * point and user re boots the datbase again without any restore flags\r
+ * it shoud boot without any problem.\r
+ **/\r
+\r
+\r
+ // setup database encryption engine\r
+ if (!create) \r
+ {\r
+ // check if the engine crashed while re-encrypting an \r
+ // encrypted database or while encryption and \r
+ // existing database.\r
+ if(properties.getProperty(\r
+ RawStoreFactory.DB_ENCRYPTION_STATUS) !=null) \r
+ { \r
+ handleIncompleteDatabaseEncryption(properties);\r
+ }\r
+\r
+ setupEncryptionEngines(create, properties);\r
+ }\r
+\r
+ if (databaseEncrypted) {\r
+ // let log factory know if the database is encrypted . \r
+ logFactory.setDatabaseEncrypted(false);\r
+ // let data factory know if the database is encrypted. \r
+ dataFactory.setDatabaseEncrypted();\r
+ }\r
+\r
+ // no need to tell log factory which raw store factory it belongs to\r
+ // since this is passed into the log factory for recovery\r
+ // after the factories are loaded, recover the database\r
+ logFactory.recover(this, dataFactory, xactFactory);\r
+\r
+ // if user requested to encrpty an unecrypted database or encrypt with\r
+ // new alogorithm then do that now. \r
+ if (encryptDatabase) {\r
+ configureDatabaseForEncryption(properties, \r
+ newCipherFactory);\r
+ }\r
+ }\r
+\r
+ public void stop() {\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (databaseEncrypted)\r
+ SanityManager.DEBUG_PRINT("encryption statistics",\r
+ "Encryption called " + counter_encrypt + " times, " +\r
+ "decryption called " + counter_decrypt + " times");\r
+ }\r
+\r
+ if (rawStoreDaemon != null)\r
+ rawStoreDaemon.stop();\r
+\r
+ if (logFactory == null)\r
+ return;\r
+\r
+ try {\r
+\r
+ if (logFactory.checkpoint(this, dataFactory, xactFactory, false))\r
+ {\r
+ if (dataFactory != null)\r
+ dataFactory.removeStubsOK();\r
+ }\r
+\r
+ } catch (StandardException se) {\r
+ // checkpoint failed, stop all factory from shutting down normally\r
+ markCorrupt(se);\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** Methods of RawStoreFactory\r
+ */\r
+\r
+ /**\r
+ Is the store read-only.\r
+ @see RawStoreFactory#isReadOnly\r
+ */\r
+ public boolean isReadOnly() {\r
+ return dataFactory.isReadOnly();\r
+ }\r
+\r
+ public LockFactory getLockFactory() {\r
+ return xactFactory.getLockFactory();\r
+ }\r
+\r
+ \r
+ /**\r
+ Get the Transaction Factory to use with this store.\r
+ */\r
+ public TransactionFactory getXactFactory() {\r
+ return xactFactory; \r
+ }\r
+\r
+ /*\r
+ * Return the module providing XAresource interface to the transaction\r
+ * table.\r
+ *\r
+ * @exception StandardException Standard Derby exception policy.\r
+ */\r
+ public /* XAResourceManager */ Object getXAResourceManager()\r
+ throws StandardException\r
+ {\r
+ return(xactFactory.getXAResourceManager());\r
+ }\r
+\r
+\r
+ public Transaction startGlobalTransaction(\r
+ ContextManager contextMgr,\r
+ int format_id,\r
+ byte[] global_id,\r
+ byte[] branch_id)\r
+ throws StandardException\r
+ {\r
+ return xactFactory.startGlobalTransaction(\r
+ this, contextMgr, format_id, global_id, branch_id);\r
+ }\r
+\r
+ public Transaction startTransaction(ContextManager contextMgr, String transName)\r
+ throws StandardException\r
+ {\r
+ return xactFactory.startTransaction(this, contextMgr, transName);\r
+ }\r
+\r
+ public Transaction startNestedReadOnlyUserTransaction(\r
+ CompatibilitySpace compatibilitySpace,\r
+ ContextManager contextMgr,\r
+ String transName)\r
+ throws StandardException\r
+ {\r
+ return(\r
+ xactFactory.startNestedReadOnlyUserTransaction(\r
+ this, compatibilitySpace, contextMgr, transName));\r
+ }\r
+\r
+ public Transaction startNestedUpdateUserTransaction(\r
+ ContextManager contextMgr,\r
+ String transName)\r
+ throws StandardException\r
+ {\r
+ return(\r
+ xactFactory.startNestedUpdateUserTransaction(\r
+ this, contextMgr, transName));\r
+ }\r
+\r
+ public Transaction findUserTransaction(\r
+ ContextManager contextMgr,\r
+ String transName)\r
+ throws StandardException\r
+ {\r
+ return xactFactory.findUserTransaction(this, contextMgr, transName);\r
+ }\r
+\r
+\r
+ public Transaction startInternalTransaction(ContextManager contextMgr) throws StandardException {\r
+\r
+ return xactFactory.startInternalTransaction(this, contextMgr);\r
+ }\r
+\r
+ public void checkpoint() throws StandardException\r
+ {\r
+ logFactory.checkpoint(this, dataFactory, xactFactory, false);\r
+ }\r
+\r
+ public void freeze() throws StandardException\r
+ {\r
+ logFactory.checkpoint(this, dataFactory, xactFactory, true);\r
+ dataFactory.freezePersistentStore();\r
+ logFactory.freezePersistentStore();\r
+ }\r
+\r
+ public void unfreeze() throws StandardException\r
+ {\r
+ logFactory.unfreezePersistentStore();\r
+ dataFactory.unfreezePersistentStore();\r
+ }\r
+\r
+ /**\r
+ * Backup the database to a backup directory.\r
+ *\r
+ * @param backupDir the name of the directory where the backup should be\r
+ * stored. This directory will be created if it \r
+ * does not exist.\r
+ * @param wait if <tt>true</tt>, waits for all the backup blocking \r
+ * operations in progress to finish.\r
+ * @exception StandardException thrown on error\r
+ */\r
+ public void backup(String backupDir, boolean wait) \r
+ throws StandardException \r
+ {\r
+ if (backupDir == null || backupDir.equals(""))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_CREATE_BACKUP_DIRECTORY, (File)null);\r
+ }\r
+\r
+ // in case this is an URL form\r
+ String backupDirURL = null;\r
+ try {\r
+ URL url = new URL(backupDir);\r
+ backupDirURL = url.getFile();\r
+ } catch (MalformedURLException ex) {}\r
+\r
+ if (backupDirURL != null)\r
+ backupDir = backupDirURL;\r
+\r
+\r
+ // find the user transaction, it is necessary for online backup \r
+ // to open the container through page cache\r
+ RawTransaction t = \r
+ xactFactory.findUserTransaction(this,\r
+ ContextService.getFactory().getCurrentContextManager(), \r
+ AccessFactoryGlobals.USER_TRANS_NAME);\r
+\r
+ try {\r
+\r
+ // check if any backup blocking operations are in progress\r
+ // in the same transaction backup is being executed? Backup is \r
+ // not allowed if the transaction has uncommitted\r
+ // unlogged operations that are blocking the backup.\r
+ \r
+ if (t.isBlockingBackup())\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BACKUP_OPERATIONS_NOT_ALLOWED); \r
+ }\r
+\r
+\r
+ // check if any backup blocking operations are in progress\r
+ // and stop new ones from starting until the backup is completed.\r
+ if (!xactFactory.blockBackupBlockingOperations(wait))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BACKUP_BLOCKING_OPERATIONS_IN_PROGRESS); \r
+ }\r
+\r
+ // perform backup\r
+ backup(t, new File(backupDir));\r
+ }finally {\r
+ // let the xactfatory know that backup is done, so that\r
+ // it can allow backup blocking operations. \r
+ xactFactory.unblockBackupBlockingOperations();\r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ * Backup the database.\r
+ * Online backup copies all the database files (log, seg0 ...Etc) to the\r
+ * specified backup location without blocking any user operation for the \r
+ * duration of the backup. Stable copy is made of each page using \r
+ * page level latches and in some cases with the help of monitors. \r
+ * Transaction log is also backed up, this is used to bring the database to \r
+ * the consistent state on restore.\r
+ * \r
+ * <P> MT- only one thread is allowed to perform backup at any given time. \r
+ * Synchronized on this. Parallel backups are not supported. \r
+ */\r
+ public synchronized void backup(Transaction t, File backupDir) \r
+ throws StandardException\r
+ {\r
+ if (!privExists(backupDir))\r
+ {\r
+ // if backup dir does not exist, go ahead and create it.\r
+\r
+ if (!privMkdirs(backupDir))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_CREATE_BACKUP_DIRECTORY,\r
+ (File) backupDir);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // entity with backup name exists, make sure it is a directory.\r
+\r
+ if (!privIsDirectory(backupDir))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_BACKUP_TO_NONDIRECTORY,\r
+ (File) backupDir);\r
+ }\r
+\r
+ // check if a user has given the backup as a database directory by\r
+ // mistake, backup path can not be a derby database directory. \r
+ // If a directory contains PersistentService.PROPERTIES_NAME, it \r
+ // is assumed to be a derby database directory because derby \r
+ // databases always have this file. \r
+ \r
+ if (privExists(\r
+ new File(backupDir, PersistentService.PROPERTIES_NAME))) \r
+ { \r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_BACKUP_INTO_DATABASE_DIRECTORY,\r
+ (File) backupDir); \r
+ }\r
+ }\r
+ \r
+ boolean error = true;\r
+ boolean renamed = false;\r
+ boolean renameFailed = false;\r
+ File oldbackup = null;\r
+ File backupcopy = null;\r
+ OutputStreamWriter historyFile = null;\r
+ StorageFile dbHistoryFile = null;\r
+ File backupHistoryFile = null;\r
+ LogInstant backupInstant = logFactory.getFirstUnflushedInstant();\r
+ \r
+ try\r
+ {\r
+ // get name of the current db, ie. database directory of current db.\r
+ StorageFile dbase = storageFactory.newStorageFile(null); \r
+ String canonicalDbName = storageFactory.getCanonicalName();\r
+ int lastSep = \r
+ canonicalDbName.lastIndexOf(storageFactory.getSeparator());\r
+ String dbname = \r
+ canonicalDbName.substring(lastSep + 1);\r
+\r
+ // append to end of history file\r
+ historyFile = \r
+ privFileWriter(\r
+ storageFactory.newStorageFile(BACKUP_HISTORY), true);\r
+ \r
+ backupcopy = new File(backupDir, dbname);\r
+\r
+ logHistory(\r
+ historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_BACKUP_STARTED, \r
+ canonicalDbName, \r
+ getFilePath(backupcopy)));\r
+\r
+ \r
+ // check if a backup copy of this database already exists,\r
+ if (privExists(backupcopy))\r
+ {\r
+ // first make a backup of the backup\r
+ oldbackup = new File(backupDir, dbname+".OLD");\r
+ if (privExists(oldbackup))\r
+ {\r
+ if (privIsDirectory(oldbackup))\r
+ privRemoveDirectory(oldbackup);\r
+ else\r
+ privDelete(oldbackup);\r
+ }\r
+\r
+ if (!privRenameTo(backupcopy,oldbackup))\r
+ {\r
+ renameFailed = true;\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_RENAMING_FILE,\r
+ backupcopy, oldbackup);\r
+ }\r
+ else\r
+ {\r
+ logHistory(\r
+ historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_MOVED_BACKUP,\r
+ getFilePath(backupcopy),\r
+ getFilePath(oldbackup)));\r
+ renamed = true;\r
+ }\r
+ }\r
+\r
+ // create the backup database directory\r
+ if (!privMkdirs(backupcopy))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_CREATE_BACKUP_DIRECTORY,\r
+ (File) backupcopy);\r
+ }\r
+\r
+ dbHistoryFile = storageFactory.newStorageFile(BACKUP_HISTORY);\r
+ backupHistoryFile = new File(backupcopy, BACKUP_HISTORY); \r
+\r
+ // copy the history file into the backup. \r
+ if(!privCopyFile(dbHistoryFile, backupHistoryFile))\r
+ throw StandardException. \r
+ newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,\r
+ dbHistoryFile, backupHistoryFile); \r
+\r
+\r
+ // if they are any jar file stored in the database, copy them into\r
+ // the backup. \r
+ StorageFile jarDir = \r
+ storageFactory.newStorageFile(FileResource.JAR_DIRECTORY_NAME);\r
+\r
+ if (privExists(jarDir)) \r
+ {\r
+ // find the list of schema directories under the jar dir and\r
+ // then copy only the plain files under those directories. One \r
+ // could just use the recursive copy of directory to copy all \r
+ // the files under the jar dir, but the problem with that is if\r
+ // a user gives jar directory as the backup path by mistake, \r
+ // copy will fail while copying the backup dir onto itself in \r
+ // recursion\r
+\r
+ String [] jarSchemaList = privList(jarDir);\r
+ File backupJarDir = new File(backupcopy, \r
+ FileResource.JAR_DIRECTORY_NAME);\r
+ // Create the backup jar directory\r
+ if (!privMkdirs(backupJarDir))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_CREATE_BACKUP_DIRECTORY,\r
+ (File) backupJarDir);\r
+ }\r
+\r
+ for (int i = 0; i < jarSchemaList.length; i++)\r
+ {\r
+ StorageFile jarSchemaDir = \r
+ storageFactory.newStorageFile(jarDir, jarSchemaList[i]);\r
+ File backupJarSchemaDir = \r
+ new File(backupJarDir, jarSchemaList[i]);\r
+\r
+ if (!privCopyDirectory(jarSchemaDir, backupJarSchemaDir, \r
+ (byte[])null, null, false)) \r
+ {\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,\r
+ jarSchemaDir, backupJarSchemaDir); \r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ // save service properties into the backup, Read in property \r
+ // from service.properties file, remove logDevice from it, \r
+ // then write it to the backup.\r
+\r
+ StorageFile logdir = logFactory.getLogDirectory();\r
+ \r
+ try \r
+ {\r
+ String name = Monitor.getMonitor().getServiceName(this);\r
+ PersistentService ps = \r
+ Monitor.getMonitor().getServiceType(this);\r
+ String fullName = ps.getCanonicalServiceName(name);\r
+ Properties prop = \r
+ ps.getServiceProperties(fullName, (Properties)null);\r
+\r
+ StorageFile defaultLogDir = \r
+ storageFactory.newStorageFile(\r
+ LogFactory.LOG_DIRECTORY_NAME);\r
+\r
+ if (!logdir.equals(defaultLogDir)) \r
+ {\r
+ prop.remove(Attribute.LOG_DEVICE);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ prop.getProperty(Attribute.LOG_DEVICE) == null,\r
+ "cannot get rid of logDevice property");\r
+ }\r
+\r
+ logHistory(historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_EDITED_SERVICEPROPS));\r
+ }\r
+ \r
+ // save the service properties into the backup.\r
+ ps.saveServiceProperties(backupcopy.getPath(), prop, false);\r
+\r
+ }\r
+ catch(StandardException se) \r
+ {\r
+ logHistory(\r
+ historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_ERROR_EDIT_SERVICEPROPS) + se);\r
+\r
+ return; // skip the rest and let finally block clean up\r
+ }\r
+\r
+ // Incase of encrypted database and the key is an external \r
+ // encryption key, there is an extra file with name \r
+ // Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE, this file should be\r
+ // copied in to the backup.\r
+ StorageFile verifyKeyFile = \r
+ storageFactory.newStorageFile(\r
+ Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);\r
+ if (privExists(verifyKeyFile)) \r
+ {\r
+ File backupVerifyKeyFile = \r
+ new File(\r
+ backupcopy, Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);\r
+\r
+ if(!privCopyFile(verifyKeyFile, backupVerifyKeyFile))\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,\r
+ verifyKeyFile, backupVerifyKeyFile); \r
+ }\r
+ \r
+ File logBackup = \r
+ new File(backupcopy, LogFactory.LOG_DIRECTORY_NAME);\r
+\r
+ // this is wierd, delete it\r
+ if (privExists(logBackup))\r
+ {\r
+ privRemoveDirectory(logBackup);\r
+ }\r
+\r
+ // Create the log directory\r
+ if (!privMkdirs(logBackup))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_CREATE_BACKUP_DIRECTORY,\r
+ (File) logBackup);\r
+ }\r
+\r
+ // do a checkpoint to get the persistent store up to date.\r
+ logFactory.checkpoint(this, dataFactory, xactFactory, true);\r
+ \r
+ // start the transaction log backup. \r
+ logFactory.startLogBackup(logBackup);\r
+\r
+ File segBackup = new File(backupcopy, "seg0");\r
+ \r
+ // Create the data segment directory\r
+ if (!privMkdirs(segBackup))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_CANNOT_CREATE_BACKUP_DIRECTORY,\r
+ (File) segBackup);\r
+ }\r
+\r
+ // backup all the information in the data segment.\r
+ dataFactory.backupDataFiles(t, segBackup);\r
+\r
+ logHistory(historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_DATA_SEG_BACKUP_COMPLETED,\r
+ getFilePath(segBackup)));\r
+\r
+\r
+ // copy the log that got generated after the backup started to\r
+ // backup location and tell the logfactory that backup has come \r
+ // to end.\r
+ logFactory.endLogBackup(logBackup);\r
+ \r
+ logHistory(historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_COPIED_LOG,\r
+ getFilePath(logdir),\r
+ getFilePath(logBackup)));\r
+\r
+ error = false;\r
+ }\r
+ catch (IOException ioe)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_UNEXPECTED_EXCEPTION, ioe);\r
+ }\r
+ finally\r
+ {\r
+\r
+ try\r
+ {\r
+ if (error)\r
+ {\r
+ \r
+ // Abort all activity related to backup in the log factory.\r
+ logFactory.abortLogBackup();\r
+\r
+ // remove the half backed up copy\r
+ // unless the error occured during rename process;\r
+ // inwhich case 'backupcopy' refers to the previous backup\r
+ // not an half backed one.\r
+ if(!renameFailed)\r
+ privRemoveDirectory(backupcopy);\r
+\r
+ if (renamed)\r
+ // recover the old backup\r
+ privRenameTo(oldbackup,backupcopy);\r
+\r
+ logHistory(historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_BACKUP_ABORTED));\r
+\r
+ }\r
+ else\r
+ {\r
+ // success, remove the old backup copy\r
+ if (renamed && privExists(oldbackup))\r
+ {\r
+ // get rid of the old backup\r
+ privRemoveDirectory(oldbackup);\r
+ logHistory(historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_REMOVED_BACKUP,\r
+ getFilePath(oldbackup)));\r
+ }\r
+ logHistory(historyFile,\r
+ MessageService.getTextMessage(\r
+ MessageId.STORE_BACKUP_COMPLETED,\r
+ backupInstant));\r
+\r
+ // copy the updated version of history file with current\r
+ // backup information into the backup.\r
+ if(!privCopyFile(dbHistoryFile, backupHistoryFile))\r
+ throw StandardException. \r
+ newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,\r
+ dbHistoryFile, backupHistoryFile); \r
+ }\r
+\r
+ historyFile.close();\r
+ }\r
+ catch (IOException ioe)\r
+ {\r
+ try\r
+ {\r
+ historyFile.close();\r
+ }\r
+ catch (IOException ioe2){};\r
+ throw StandardException.newException(\r
+ SQLState.RAWSTORE_UNEXPECTED_EXCEPTION, ioe);\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Backup the database to a backup directory and enable the log archive\r
+ * mode that will keep the archived log files required for roll-forward\r
+ * from this version backup.\r
+ *\r
+ * @param backupDir the name of the directory where the backup should be\r
+ * stored. This directory will be created if it \r
+ * does not exist. \r
+ *\r
+ * @param deleteOnlineArchivedLogFiles \r
+ * If true deletes online archived \r
+ * log files that exist before this backup, delete \r
+ * will occur only after the backup is complete.\r
+ *\r
+ * @param wait if <tt>true</tt>, waits for all the backup blocking \r
+ * operations in progress to finish.\r
+ *\r
+ * @exception StandardException thrown on error.\r
+ */\r
+ public void backupAndEnableLogArchiveMode(\r
+ String backupDir,\r
+ boolean deleteOnlineArchivedLogFiles,\r
+ boolean wait) \r
+ throws StandardException\r
+ {\r
+ boolean enabledLogArchive = false;\r
+ try {\r
+ // Enable the log archive mode, if it is not already enabled.\r
+ if(!logFactory.logArchived()) {\r
+ logFactory.enableLogArchiveMode();\r
+ enabledLogArchive = true ;\r
+ }\r
+\r
+ backup(backupDir, wait);\r
+ \r
+ // After successful backup delete the archived log files\r
+ // that are not necessary to do a roll-forward recovery\r
+ // from this backup if requested.\r
+ if (deleteOnlineArchivedLogFiles)\r
+ {\r
+ logFactory.deleteOnlineArchivedLogFiles();\r
+ }\r
+ }catch (Throwable error) {\r
+ // On any errors , disable the log archive, if it \r
+ // is enabled on this call. \r
+ if (enabledLogArchive)\r
+ logFactory.disableLogArchiveMode();\r
+ throw StandardException.plainWrapException(error);\r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ * Disable the log archive mode and delete the archived log files \r
+ * if requested. \r
+ *\r
+ * @param deleteOnlineArchivedLogFiles \r
+ * If true deletes online archived \r
+ * log files that exist before this backup, delete \r
+ * will occur only after the backup is complete.\r
+ * @exception StandardException thrown on error.\r
+ */\r
+ public void disableLogArchiveMode(boolean deleteOnlineArchivedLogFiles)\r
+ throws StandardException\r
+ {\r
+ logFactory.disableLogArchiveMode();\r
+ if(deleteOnlineArchivedLogFiles)\r
+ {\r
+ logFactory.deleteOnlineArchivedLogFiles();\r
+ }\r
+ }\r
+\r
+ \r
+ /*\r
+ * Restore any remaining files from backup that are not \r
+ * restored by the individual factories. \r
+ * 1) copy jar files from backup..\r
+ * 2) copy backup history file. \r
+ */\r
+ private void restoreRemainingFromBackup(String backupPath) \r
+ throws StandardException \r
+ {\r
+ \r
+ // if they are any jar files in the backup copy, \r
+ // copy them into the database directory, if they\r
+ // are not already there. \r
+\r
+ File backupJarDir = new File(backupPath, \r
+ FileResource.JAR_DIRECTORY_NAME);\r
+\r
+ StorageFile dbJarDir = \r
+ storageFactory.newStorageFile(FileResource.JAR_DIRECTORY_NAME);\r
+ \r
+ if (!privExists(dbJarDir) && privExists(backupJarDir)) \r
+ {\r
+ if (!privCopyDirectory(backupJarDir, dbJarDir)) {\r
+ throw StandardException.newException(\r
+ SQLState.UNABLE_TO_COPY_FILE_FROM_BACKUP, \r
+ backupJarDir, dbJarDir);\r
+ }\r
+ }\r
+\r
+ // copy the backup history file from the backup. \r
+ StorageFile dbHistoryFile = \r
+ storageFactory.newStorageFile(BACKUP_HISTORY);\r
+ File backupHistoryFile = new File(backupPath, BACKUP_HISTORY);\r
+ \r
+ // if this is a roll-forward recovery, backup history file \r
+ // will already there in the database and will be the latest \r
+ // copy; if it exists, do not copy from backup.\r
+ if (!privExists(dbHistoryFile))\r
+ if (!privCopyFile(backupHistoryFile, dbHistoryFile))\r
+ throw StandardException. \r
+ newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,\r
+ backupHistoryFile, dbHistoryFile); \r
+ }\r
+\r
+\r
+ public void idle() throws StandardException {\r
+ dataFactory.idle();\r
+ }\r
+\r
+\r
+ public TransactionInfo[] getTransactionInfo()\r
+ {\r
+ return xactFactory.getTransactionInfo();\r
+ }\r
+\r
+\r
+ public ScanHandle openFlushedScan(DatabaseInstant start, int groupsIWant)\r
+ throws StandardException\r
+ {\r
+ return logFactory.openFlushedScan(start,groupsIWant);\r
+ }\r
+\r
+ public DaemonService getDaemon()\r
+ {\r
+ return rawStoreDaemon;\r
+ }\r
+\r
+ public void createFinished() throws StandardException\r
+ {\r
+ xactFactory.createFinished();\r
+ dataFactory.createFinished();\r
+ }\r
+\r
+ /**\r
+ * Get JBMS properties relavent to raw store\r
+ * @exception StandardException Standard Derby Error Policy\r
+ */\r
+ public void getRawStoreProperties(PersistentSet set)\r
+ throws StandardException\r
+ {\r
+ logFactory.getLogFactoryProperties(set);\r
+ }\r
+\r
+\r
+ /*\r
+ ** backup restore\r
+ */\r
+ /**\r
+ Freeze persistent store. Reads can still happen, only cannot write.\r
+ @exception StandardException Standard Derby Error Policy\r
+ */\r
+ public void freezePersistentStore() throws StandardException\r
+ {\r
+ // do a checkpoint to get the persistent store up to date.\r
+ logFactory.checkpoint(this, dataFactory, xactFactory,true);\r
+ logFactory.freezePersistentStore();\r
+\r
+ }\r
+\r
+ /**\r
+ Freeze persistent store. Reads can still happen, only cannot write.\r
+ @exception StandardException Standard Derby Error Policy\r
+ */\r
+ public void unfreezePersistentStore() throws StandardException\r
+ {\r
+ logFactory.unfreezePersistentStore();\r
+ }\r
+\r
+\r
+ /*\r
+ ** data encryption/decryption support\r
+ */\r
+\r
+\r
+ /*\r
+ * Setup Encryption Engines. \r
+ */\r
+ private void setupEncryptionEngines(boolean create, Properties properties)\r
+ throws StandardException\r
+ {\r
+ \r
+ // check if user has requested to encrypt the database or it is an\r
+ // encrypted database.\r
+\r
+ String dataEncryption = \r
+ properties.getProperty(Attribute.DATA_ENCRYPTION);\r
+ databaseEncrypted = Boolean.valueOf(dataEncryption).booleanValue(); \r
+\r
+ boolean reEncrypt = false;\r
+\r
+ if (!create) {\r
+ // check if database is already encrypted, by directly peeking at the\r
+ // database service propertes instead of the properties passed \r
+ // to this method. By looking at properties to the boot method ,\r
+ // one can not differentiate if user is requesting for database\r
+ // encryption or the database is already encrypted because \r
+ // Attribute.DATA_ENCRYPTION is used to store in the \r
+ // service properties to indicate that database\r
+ // is encrypted and also users can specify it as URL attribute \r
+ // to encrypt and existing database. \r
+ \r
+ String name = Monitor.getMonitor().getServiceName(this);\r
+ PersistentService ps = Monitor.getMonitor().getServiceType(this);\r
+ String canonicalName = ps.getCanonicalServiceName(name);\r
+ Properties serviceprops = ps.getServiceProperties(canonicalName, \r
+ (Properties)null);\r
+ dataEncryption = serviceprops.getProperty(Attribute.DATA_ENCRYPTION);\r
+ boolean encryptedDatabase = Boolean.valueOf(dataEncryption).booleanValue();\r
+\r
+ if (!encryptedDatabase && databaseEncrypted) {\r
+ // it it not an encrypted database, user is asking to \r
+ // encrypt an un-encrypted database. \r
+ encryptDatabase = true;\r
+ // set database as un-encrypted, we will set it as encrypted \r
+ // after encrypting the existing data. \r
+ databaseEncrypted = false;\r
+ } else {\r
+ // check if the user has requested to renecrypt an\r
+ // encrypted datbase with new encryption password/key.\r
+ if (encryptedDatabase) {\r
+ if (properties.getProperty(\r
+ Attribute.NEW_BOOT_PASSWORD) != null) {\r
+ reEncrypt = true;\r
+ }\r
+ else if (properties.getProperty(\r
+ Attribute.NEW_CRYPTO_EXTERNAL_KEY) != null){\r
+ reEncrypt = true;\r
+ };\r
+ encryptDatabase = reEncrypt;\r
+ }\r
+\r
+ }\r
+ \r
+ \r
+ // NOTE: if user specifies Attribute.DATA_ENCRYPTION on the\r
+ // connection URL by mistake on an already encrypted database, \r
+ // it is ignored.\r
+\r
+\r
+ // prevent attempt to (re)encrypt of a read-only database\r
+ if (encryptDatabase) \r
+ {\r
+ if (isReadOnly()) \r
+ {\r
+ if (reEncrypt) \r
+ throw StandardException.newException(\r
+ SQLState.CANNOT_REENCRYPT_READONLY_DATABASE);\r
+ else\r
+ throw StandardException.newException(\r
+ SQLState.CANNOT_ENCRYPT_READONLY_DATABASE);\r
+ }\r
+ }\r
+ }\r
+\r
+ // setup encryption engines. \r
+ if (databaseEncrypted || encryptDatabase)\r
+ {\r
+ // check if database is configured for encryption, during\r
+ // configuration some of the properties database; so that\r
+ // user does not have to specify them on the URL everytime.\r
+ // Incase of re-encryption of an already of encrypted database\r
+ // only some information needs to updated; it is not treated \r
+ // like the configuring the database for encryption first time. \r
+ boolean setupEncryption = create || (encryptDatabase && !reEncrypt);\r
+\r
+ // start the cipher factory module, that is is used to create \r
+ // instances of the cipher factory with specific enctyption \r
+ // properties. \r
+\r
+ CipherFactoryBuilder cb = (CipherFactoryBuilder)\r
+ Monitor.startSystemModule(org.apache.derby.iapi.reference.Module.CipherFactoryBuilder);\r
+\r
+ // create instance of the cipher factory with the \r
+ // specified encryption properties. \r
+ currentCipherFactory = cb.createCipherFactory(setupEncryption, \r
+ properties, \r
+ false);\r
+\r
+ // The database can be encrypted using an encryption key that is given at\r
+ // connection url. For security reasons, this key is not made persistent\r
+ // in the database. But it is necessary to verify the encryption key \r
+ // whenever booting the database if it is similar to the key that was used\r
+ // during creation time. This needs to happen before we access the data/logs to \r
+ // avoid the risk of corrupting the database because of a wrong encryption key.\r
+ \r
+ // Please note this verification process does not provide any added security\r
+ // but is intended to allow to fail gracefully if a wrong encryption key \r
+ // is used during boot time\r
+ \r
+\r
+ currentCipherFactory.verifyKey(setupEncryption, storageFactory, properties);\r
+\r
+ // Initializes the encryption and decryption engines\r
+ encryptionEngine = currentCipherFactory.\r
+ createNewCipher(CipherFactory.ENCRYPT);\r
+ \r
+ // At creation time of an encrypted database, store the encryption block size\r
+ // for the algorithm. Store this value as property given by \r
+ // RawStoreFactory.ENCRYPTION_BLOCKSIZE. This value\r
+ // is made persistent by storing it in service.properties\r
+ // To connect to an existing database, retrieve the value and use it for\r
+ // appropriate padding.\r
+ // The default value of encryption block size is 8,\r
+ // to allow for downgrade issues\r
+ // Before support for AES (beetle6023), default encryption block size supported\r
+ // was 8\r
+\r
+ if(setupEncryption) \r
+ {\r
+ encryptionBlockSize = encryptionEngine.getEncryptionBlockSize();\r
+ // in case of database create, store the encryption block\r
+ // size. Incase of reconfiguring the existing datbase, this\r
+ // will be saved after encrypting the exisiting data. \r
+ if (create)\r
+ properties.put(RawStoreFactory.ENCRYPTION_BLOCKSIZE,\r
+ String.valueOf(encryptionBlockSize));\r
+ }\r
+ else\r
+ {\r
+ if(properties.getProperty(RawStoreFactory.ENCRYPTION_BLOCKSIZE) != null)\r
+ encryptionBlockSize = Integer.parseInt(properties.getProperty\r
+ (RawStoreFactory.ENCRYPTION_BLOCKSIZE));\r
+ else\r
+ encryptionBlockSize = encryptionEngine.getEncryptionBlockSize();\r
+ } \r
+\r
+ decryptionEngine = currentCipherFactory.\r
+ createNewCipher(CipherFactory.DECRYPT);\r
+\r
+ random = currentCipherFactory.getSecureRandom();\r
+ \r
+ if (encryptDatabase) {\r
+\r
+ if (reEncrypt) {\r
+ // create new cipher factory with the new encrytpion\r
+ // properties specified by the user. This cipher factory\r
+ // is used to create the new encryption/decryption\r
+ // engines to reencrypt the database with the new\r
+ // encryption keys. \r
+ newCipherFactory = \r
+ cb.createCipherFactory(setupEncryption, \r
+ properties, \r
+ true);\r
+ newDecryptionEngine = \r
+ newCipherFactory.createNewCipher(CipherFactory.DECRYPT);\r
+ newEncryptionEngine = \r
+ newCipherFactory.createNewCipher(CipherFactory.ENCRYPT);\r
+ } else {\r
+ // there is only one engine when configuring an \r
+ // unencrypted database for encryption \r
+ newDecryptionEngine = decryptionEngine;\r
+ newEncryptionEngine = encryptionEngine;\r
+\r
+ }\r
+ }\r
+\r
+ // save the encryption properties if encryption is enabled \r
+ // at database creation time. \r
+ if(create)\r
+ currentCipherFactory.saveProperties(properties) ;\r
+ }\r
+ }\r
+ \r
+\r
+ /**\r
+ Encrypt cleartext into ciphertext.\r
+\r
+ @see CipherProvider#encrypt\r
+\r
+ @exception StandardException Standard Derby Error Policy\r
+ */\r
+ public int encrypt(byte[] cleartext, int offset, int length,\r
+ byte[] ciphertext, int outputOffset, \r
+ boolean newEngine)\r
+ throws StandardException\r
+ {\r
+ if ((databaseEncrypted == false && encryptDatabase == false) || \r
+ (encryptionEngine == null && newEncryptionEngine == null))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.STORE_FEATURE_NOT_IMPLEMENTED);\r
+ }\r
+\r
+ counter_encrypt++;\r
+\r
+ if (newEngine) {\r
+ return newEncryptionEngine.encrypt(cleartext, offset, length,\r
+ ciphertext, outputOffset);\r
+ } else {\r
+ return encryptionEngine.encrypt(cleartext, offset, length,\r
+ ciphertext, outputOffset);\r
+ }\r
+ }\r
+\r
+ /**\r
+ Decrypt cleartext from ciphertext.\r
+\r
+ @see CipherProvider#decrypt\r
+\r
+ @exception StandardException Standard Derby Error Policy\r
+ */\r
+ public int decrypt(byte[] ciphertext, int offset, int length,\r
+ byte[] cleartext, int outputOffset) \r
+ throws StandardException\r
+ {\r
+ if (databaseEncrypted == false || decryptionEngine == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.STORE_FEATURE_NOT_IMPLEMENTED);\r
+ }\r
+\r
+ counter_decrypt++;\r
+ return decryptionEngine.decrypt(ciphertext, offset, length,\r
+ cleartext, outputOffset);\r
+ }\r
+\r
+ /**\r
+ Returns the encryption block size used by the algorithm at time of\r
+ creation of an encrypted database\r
+ */\r
+ public int getEncryptionBlockSize()\r
+ {\r
+ return encryptionBlockSize;\r
+ }\r
+\r
+ public int random()\r
+ {\r
+ // don't synchronize it, the more random the better.\r
+ return databaseEncrypted ? random.nextInt() : 0;\r
+ }\r
+\r
+ public Serializable changeBootPassword(Properties properties, Serializable changePassword)\r
+ throws StandardException\r
+ {\r
+ if (isReadOnly())\r
+ throw StandardException.newException(SQLState.DATABASE_READ_ONLY);\r
+\r
+ if (!databaseEncrypted)\r
+ throw StandardException.newException(SQLState.DATABASE_NOT_ENCRYPTED);\r
+\r
+ if (changePassword == null)\r
+ throw StandardException.newException(SQLState.NULL_BOOT_PASSWORD);\r
+\r
+ if (!(changePassword instanceof String))\r
+ throw StandardException.newException(SQLState.NON_STRING_BP);\r
+\r
+ // the new bootPassword is expected to be of the form\r
+ // oldkey , newkey.\r
+ String changeString = (String)changePassword;\r
+\r
+ return currentCipherFactory.changeBootPassword((String)changePassword, properties, encryptionEngine);\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * (re) encryption testing debug flags that are used to \r
+ * simulate error/crash conditions for testing purposes.\r
+ * When any one of the following flags are set to true\r
+ * in the debug mode, re-encryption will fail at that point.\r
+ */\r
+\r
+ public static final String TEST_REENCRYPT_CRASH_BEFORE_COMMT = \r
+ SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_BEFORE_COMMT" : null ;\r
+ public static final String TEST_REENCRYPT_CRASH_AFTER_COMMT = \r
+ SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_COMMT" : null ;\r
+ public static final String TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY = \r
+ SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY" : null ;\r
+ public static final String TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT = \r
+ SanityManager.DEBUG ? "TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT" : null ;\r
+ public static final String \r
+ TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE =\r
+ SanityManager.DEBUG ?\r
+ "TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE" : null;\r
+ public static final String \r
+ TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY =\r
+ SanityManager.DEBUG ?\r
+ "TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY" : null;\r
+ public static final String \r
+ TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP =\r
+ SanityManager.DEBUG ?\r
+ "TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP" : null;\r
+ \r
+ \r
+\r
+ /** \r
+ * when the input debug flag is set, an expception \r
+ * is throw when run in the debug mode.\r
+ */\r
+ private void crashOnDebugFlag(String debugFlag, \r
+ boolean reEncrypt) \r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // if the test debug flag is set, throw an \r
+ // exception to simulate error cases.\r
+ if (SanityManager.DEBUG_ON(debugFlag))\r
+ {\r
+ StandardException se = StandardException.newException(\r
+ (reEncrypt ? SQLState.DATABASE_REENCRYPTION_FAILED :\r
+ SQLState.DATABASE_ENCRYPTION_FAILED),\r
+ debugFlag);\r
+ markCorrupt(se);\r
+ throw se;\r
+ }\r
+ }\r
+ }\r
+\r
+ /*\r
+ * Configure the database for encryption, with the specified \r
+ * encryption properties.\r
+ *\r
+ * Basic idea is to encrypt all the containers with new password/key \r
+ * specified by the user and keep old versions of the data to \r
+ * rollback the database to the state before the configuration of database \r
+ * with new encryption attributes. Users can configure the database with \r
+ * new encryption attributes at boot time only; advantage of this approach\r
+ * is that there will not be any concurrency issues to handle because\r
+ * no users will be modifying the data. \r
+ *\r
+ * First step is to encrypt the existing data with new encryption \r
+ * attributes and then update the encryption properties for \r
+ * the database. Configuring an un-encrypted database for \r
+ * encryption problem is a minor variation of re-encrypting an \r
+ * encrypted database with new encryption key. The database \r
+ * reconfiguration with new encryption attributes is done under one\r
+ * transaction, if there is a crash/error before it is committed, \r
+ * then it is rolled back and the database will be brought back to the\r
+ * state it was before the encryption. \r
+ *\r
+ * One trickey case in (re) encrypion of database is \r
+ * unlike standard protocol transaction commit means all done, \r
+ * database (re) encryption process has to perform a checkpoint\r
+ * with a newly generated key then only database (re) encrption \r
+ * is complete, Otherwise the problem is recovery has to deal \r
+ * with transaction log that is encrypted with old encryption key and \r
+ * the new encryption key. This probelm is avoided writing COMMIT\r
+ * and new CHECKPOINT log record to a new log file and encrypt the \r
+ * with a new key, if there is crash before checkpoint records \r
+ * are updated , then on next boot the log file after the checkpoint \r
+ * is deleted before reovery, which will be the one that is \r
+ * written with new encryption key and also contains COMMIT record, \r
+ * so the COMMIT record is also gone when log file is deleted. \r
+ * Recovery will not see the commit , so it will rollback the (re)\r
+ * encryption and revert all the containers to the \r
+ * original versions. \r
+ * \r
+ * Old container versions are deleted only when the check point \r
+ * with new encryption key is successful, not on post-commit. \r
+ *\r
+ * @param properties properties related to this database.\r
+ * @exception StandardException Standard Derby Error Policy\r
+ */\r
+ public void configureDatabaseForEncryption(Properties properties,\r
+ CipherFactory newCipherFactory) \r
+ throws StandardException \r
+ {\r
+\r
+ boolean reEncrypt = (databaseEncrypted && encryptDatabase);\r
+\r
+ // check if the database can be encrypted.\r
+ canEncryptDatabase(reEncrypt);\r
+\r
+ boolean externalKeyEncryption = false;\r
+ if (properties.getProperty(Attribute.CRYPTO_EXTERNAL_KEY) != null)\r
+ {\r
+ externalKeyEncryption = true;\r
+ }\r
+\r
+ // check point the datase, so that encryption does not have\r
+ // to encrypt the existing transactions logs. \r
+ \r
+ logFactory.checkpoint(this, dataFactory, xactFactory, true);\r
+\r
+ // start a transaction that is to be used for encryting the database\r
+ RawTransaction transaction =\r
+ xactFactory.startTransaction(\r
+ this,\r
+ ContextService.getFactory().getCurrentContextManager(),\r
+ AccessFactoryGlobals.USER_TRANS_NAME);\r
+\r
+ try \r
+ {\r
+ \r
+ dataFactory.encryptAllContainers(transaction);\r
+\r
+ // all the containers are (re) encrypted, now mark the database as\r
+ // encrypted if a plain database is getting configured for encryption\r
+ // or update the encryption the properties, in the \r
+ // service.properties ..etc.\r
+\r
+ \r
+ if (SanityManager.DEBUG) {\r
+ crashOnDebugFlag(TEST_REENCRYPT_CRASH_BEFORE_COMMT, reEncrypt);\r
+ }\r
+\r
+ // check if the checkpoint is currently in the last log file, \r
+ // otherwise force a checkpoint and then do a log switch, \r
+ // after setting up a new encryption key\r
+ if (!logFactory.isCheckpointInLastLogFile()) \r
+ {\r
+ // perfrom a checkpoint, this is a reference checkpoint \r
+ // to find if the re(encryption) is complete. \r
+ logFactory.checkpoint(this, dataFactory, xactFactory, true);\r
+ }\r
+ \r
+\r
+ encryptDatabase = false;\r
+\r
+ // let the log factory know that database is \r
+ // (re) encrypted and ask it to flush the log, \r
+ // before enabling encryption of the log with \r
+ // the new key.\r
+ logFactory.setDatabaseEncrypted(true);\r
+ \r
+ // let the log factory and data factory know that \r
+ // database is encrypted.\r
+ if (!reEncrypt) {\r
+ // mark in the raw store that the database is \r
+ // encrypted. \r
+ databaseEncrypted = true;\r
+ dataFactory.setDatabaseEncrypted();\r
+ } else {\r
+ // switch the encryption/decryption engine to the new ones.\r
+ decryptionEngine = newDecryptionEngine; \r
+ encryptionEngine = newEncryptionEngine;\r
+ currentCipherFactory = newCipherFactory;\r
+ }\r
+\r
+ \r
+ // make the log factory ready to encrypt\r
+ // the transaction log with the new encryption \r
+ // key by switching to a new log file. \r
+ // If re-encryption is aborted for any reason, \r
+ // this new log file will be deleted, during\r
+ // recovery.\r
+\r
+ logFactory.startNewLogFile();\r
+\r
+ // mark that re-encryption is in progress in the \r
+ // service.properties, so that (re) encryption \r
+ // changes that can not be undone using the transaction \r
+ // log can be un-done before recovery starts.\r
+ // (like the changes to service.properties and \r
+ // any log files the can not be understood by the\r
+ // old encryption key), incase engine crashes\r
+ // after this point. \r
+\r
+ // if the crash occurs before this point, recovery\r
+ // will rollback the changes using the transaction \r
+ // log.\r
+\r
+ properties.put(RawStoreFactory.DB_ENCRYPTION_STATUS,\r
+ String.valueOf(\r
+ RawStoreFactory.DB_ENCRYPTION_IN_PROGRESS));\r
+\r
+ if (reEncrypt) \r
+ {\r
+ // incase re-encryption, save the old \r
+ // encryption related properties, before\r
+ // doing updates with new values.\r
+\r
+ if (externalKeyEncryption) \r
+ {\r
+ // save the current copy of verify key file.\r
+ StorageFile verifyKeyFile = \r
+ storageFactory.newStorageFile(\r
+ Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);\r
+ StorageFile oldVerifyKeyFile = \r
+ storageFactory.newStorageFile(\r
+ RawStoreFactory.CRYPTO_OLD_EXTERNAL_KEY_VERIFY_FILE);\r
+\r
+ if(!privCopyFile(verifyKeyFile, oldVerifyKeyFile))\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,\r
+ verifyKeyFile, oldVerifyKeyFile); \r
+\r
+ // update the verify key file with the new key info.\r
+ currentCipherFactory.verifyKey(reEncrypt, \r
+ storageFactory, \r
+ properties);\r
+ } else \r
+ {\r
+ // save the current generated encryption key \r
+ String keyString = \r
+ properties.getProperty(\r
+ RawStoreFactory.ENCRYPTED_KEY);\r
+ if (keyString != null)\r
+ properties.put(RawStoreFactory.OLD_ENCRYPTED_KEY,\r
+ keyString);\r
+ }\r
+ } else \r
+ {\r
+ // save the encryption block size;\r
+ properties.put(RawStoreFactory.ENCRYPTION_BLOCKSIZE,\r
+ String.valueOf(encryptionBlockSize));\r
+ }\r
+\r
+ // save the new encryption properties into service.properties\r
+ currentCipherFactory.saveProperties(properties) ;\r
+ \r
+ if (SanityManager.DEBUG) {\r
+ crashOnDebugFlag(\r
+ TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY,\r
+ reEncrypt);\r
+ }\r
+\r
+ // commit the transaction that is used to \r
+ // (re) encrypt the database. Note that \r
+ // this will be logged with newly generated \r
+ // encryption key in the new log file created \r
+ // above.\r
+ transaction.commit();\r
+\r
+ if (SanityManager.DEBUG) {\r
+ crashOnDebugFlag(TEST_REENCRYPT_CRASH_AFTER_COMMT, \r
+ reEncrypt);\r
+ }\r
+\r
+ // force the checkpoint with new encryption key.\r
+ logFactory.checkpoint(this, dataFactory, xactFactory, true);\r
+\r
+ if (SanityManager.DEBUG) {\r
+ crashOnDebugFlag(TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT, \r
+ reEncrypt);\r
+ }\r
+\r
+ // once the checkpont makes it to the log, re-encrption \r
+ // is complete. only cleanup is remaining ; update the \r
+ // re-encryption status flag to cleanup. \r
+ properties.put(RawStoreFactory.DB_ENCRYPTION_STATUS,\r
+ String.valueOf(\r
+ RawStoreFactory.DB_ENCRYPTION_IN_CLEANUP));\r
+\r
+ // database is (re)encrypted successfuly, \r
+ // remove the old version of the container files.\r
+ dataFactory.removeOldVersionOfContainers(false);\r
+ \r
+ if (reEncrypt) \r
+ {\r
+ if (externalKeyEncryption)\r
+ {\r
+ // remove the saved copy of the verify.key file\r
+ StorageFile oldVerifyKeyFile = \r
+ storageFactory.newStorageFile(\r
+ RawStoreFactory.CRYPTO_OLD_EXTERNAL_KEY_VERIFY_FILE);\r
+ if (!privDelete(oldVerifyKeyFile))\r
+ throw StandardException.newException(\r
+ SQLState.UNABLE_TO_DELETE_FILE, \r
+ oldVerifyKeyFile);\r
+ } else \r
+ {\r
+ // remove the old encryption key property.\r
+ properties.remove(RawStoreFactory.OLD_ENCRYPTED_KEY);\r
+ }\r
+ }\r
+\r
+ // (re) encrypion is done, remove the (re) \r
+ // encryption status property. \r
+\r
+ properties.remove(RawStoreFactory.DB_ENCRYPTION_STATUS);\r
+\r
+ // close the transaction. \r
+ transaction.close(); \r
+\r
+ } catch (StandardException se) {\r
+\r
+ throw StandardException.newException(\r
+ (reEncrypt ? SQLState.DATABASE_REENCRYPTION_FAILED :\r
+ SQLState.DATABASE_ENCRYPTION_FAILED),\r
+ se,\r
+ se.getMessage()); \r
+ } finally {\r
+ // clear the new encryption engines.\r
+ newDecryptionEngine = null; \r
+ newEncryptionEngine = null;\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Engine might have crashed during encryption of un-encrypted datbase\r
+ * or while re-encryptin an already encrypted database with a new key\r
+ * after all the containers or (re) encrypted. If crash has occured\r
+ * before all containers are encrypted, recovery wil un-do re-encryption\r
+ * using the transaction log, nothing to be done here.\r
+ *\r
+ * If crash has occured after database encryption status flag \r
+ * (RawStoreFactory.DB_ENCRYPTION_STATUS) is set, this method \r
+ * will do any cleanup necessary for the recovery to correctly\r
+ * perform the rollback if required. \r
+ *\r
+ *\r
+ *\r
+ * @param properties properties related to this database.\r
+ * @exception StandardException Standard Derby Error Policy\r
+ *\r
+ */\r
+ public void handleIncompleteDatabaseEncryption(Properties properties) \r
+ throws StandardException\r
+ {\r
+ // find what was the encryption status before database crashed. \r
+ int dbEncryptionStatus = 0; \r
+ String dbEncryptionStatusStr = \r
+ properties.getProperty(RawStoreFactory.DB_ENCRYPTION_STATUS);\r
+ if ( dbEncryptionStatusStr != null) \r
+ dbEncryptionStatus = Integer.parseInt(dbEncryptionStatusStr);\r
+\r
+ boolean reEncryption = false;\r
+ // check if engine crashed when (re) encryption was in progress.\r
+ if (dbEncryptionStatus == RawStoreFactory.DB_ENCRYPTION_IN_PROGRESS)\r
+ {\r
+\r
+ // check if it crashed immediately after completion or\r
+ // before. if the checkpoint is in the last log file \r
+ // encrypted with new encryption key, it is as good \r
+ // as complete. In this case just cleanup any uncleared\r
+ // flags and mark that database is encrypted.\r
+\r
+ if(logFactory.isCheckpointInLastLogFile()) \r
+ {\r
+ // database (re)encryption was successful, only \r
+ // cleanup is remaining. change the status to cleanup. \r
+ dbEncryptionStatus = RawStoreFactory.DB_ENCRYPTION_IN_CLEANUP;\r
+ }else {\r
+\r
+ // crash occured before re-encrytion was completed. \r
+ // update the db re-encryption status and write to \r
+ // the service.properties that re-encryption \r
+ // needs to be undone. The reason this status need \r
+ // to be made persistent, it will help to correctly \r
+ // handle a crash in this routine after the log file \r
+ // encrypted with new key is deleted. If this flag\r
+ // is not set, on next reboot, above check \r
+ // will find checkpoint in the last log file and \r
+ // incorrecly assume (re) encryption is\r
+ // successful.\r
+\r
+ dbEncryptionStatus = RawStoreFactory.DB_ENCRYPTION_IN_UNDO;\r
+ properties.put(RawStoreFactory.DB_ENCRYPTION_STATUS,\r
+ String.valueOf(dbEncryptionStatus));\r
+ }\r
+ }\r
+\r
+ \r
+ if (dbEncryptionStatus == RawStoreFactory.DB_ENCRYPTION_IN_UNDO)\r
+ {\r
+ // delete the log file after the log file that has the checkpoint , \r
+ // it has the data encrypted with the new key, including the commit\r
+ // record for the transaction that was used to (re)encrypt \r
+ // the database. By Deleting the log file, we are forcing the\r
+ // recovery to rollback the (re)encryption of the database. \r
+\r
+ logFactory.deleteLogFileAfterCheckpointLogFile();\r
+ \r
+ if (SanityManager.DEBUG) {\r
+ crashOnDebugFlag(\r
+ TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE, \r
+ reEncryption);\r
+ }\r
+\r
+ // Note : If a crash occurs at this point, then on reboot \r
+ // it will again be in the DB_ENRYPTION_IN__UNDO state, \r
+ // there will not be a file after the checkpoint log file, \r
+ // so no file will be deleted. \r
+\r
+ // check if this is a external key encryption and \r
+ // if it replace the current verify key file with \r
+ // the old copy. \r
+\r
+ StorageFile verifyKeyFile = \r
+ storageFactory.newStorageFile(\r
+ Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);\r
+ \r
+ if (privExists(verifyKeyFile))\r
+ {\r
+ StorageFile oldVerifyKeyFile = \r
+ storageFactory.newStorageFile(\r
+ RawStoreFactory.CRYPTO_OLD_EXTERNAL_KEY_VERIFY_FILE);\r
+ \r
+ if (privExists(oldVerifyKeyFile)) \r
+ {\r
+ if(!privCopyFile(oldVerifyKeyFile, verifyKeyFile))\r
+ throw StandardException.\r
+ newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,\r
+ oldVerifyKeyFile, verifyKeyFile); \r
+ \r
+ // only incase of re-encryption there should\r
+ // be old verify key file. \r
+ reEncryption = true;\r
+ }else \r
+ {\r
+ // remove the verify key file. \r
+ if (!privDelete(verifyKeyFile))\r
+ throw StandardException.newException(\r
+ SQLState.UNABLE_TO_DELETE_FILE, \r
+ verifyKeyFile);\r
+ }\r
+\r
+ } else \r
+ {\r
+ // database enrypted with boot password. \r
+ \r
+ // replace the current encryption key with the old key\r
+ // in the service.properties file. \r
+ // retreive the old encryption key \r
+\r
+ String OldKeyString = \r
+ properties.getProperty(RawStoreFactory.OLD_ENCRYPTED_KEY);\r
+\r
+ if (OldKeyString != null) {\r
+ // set the current encrypted key to the old one. \r
+ properties.put(RawStoreFactory.ENCRYPTED_KEY,\r
+ OldKeyString);\r
+ \r
+ // only incase of re-encryption there should\r
+ // be old encryted key . \r
+ reEncryption = true;\r
+ }\r
+ }\r
+\r
+ if (!reEncryption) {\r
+ // crash occured when database was getting reconfigured \r
+ // for encryption , all encryption properties should be \r
+ // removed from service.properties\r
+ \r
+ // common props for external key or password.\r
+ properties.remove(Attribute.DATA_ENCRYPTION);\r
+ properties.remove(RawStoreFactory.LOG_ENCRYPT_ALGORITHM_VERSION);\r
+ properties.remove(RawStoreFactory.DATA_ENCRYPT_ALGORITHM_VERSION);\r
+ properties.remove(RawStoreFactory.ENCRYPTION_BLOCKSIZE);\r
+\r
+ // properties specific to password based encryption.\r
+ properties.remove(Attribute.CRYPTO_KEY_LENGTH);\r
+ properties.remove(Attribute.CRYPTO_PROVIDER);\r
+ properties.remove(Attribute.CRYPTO_ALGORITHM);\r
+ properties.remove(RawStoreFactory.ENCRYPTED_KEY);\r
+\r
+ }\r
+\r
+ if (SanityManager.DEBUG) {\r
+ crashOnDebugFlag(\r
+ TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY, \r
+ reEncryption);\r
+ }\r
+\r
+ } // end of UNDO\r
+\r
+\r
+ if (dbEncryptionStatus == RawStoreFactory.DB_ENCRYPTION_IN_CLEANUP)\r
+ {\r
+ // remove all the old versions of the containers. \r
+ dataFactory.removeOldVersionOfContainers(true);\r
+ }\r
+ \r
+ if (SanityManager.DEBUG) {\r
+ crashOnDebugFlag(\r
+ TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP, \r
+ reEncryption);\r
+ }\r
+\r
+ // either the (re) encryption was complete , \r
+ // or undone (except for rollback that needs to be \r
+ // done by the recovery). Remove re-encryption specific\r
+ // flags from the service.properties and old copy \r
+ // of the verify key file.\r
+ \r
+ // delete the old verify key file , if it exists. \r
+ StorageFile oldVerifyKeyFile = \r
+ storageFactory.newStorageFile(\r
+ RawStoreFactory.CRYPTO_OLD_EXTERNAL_KEY_VERIFY_FILE);\r
+ if (privExists(oldVerifyKeyFile)) \r
+ {\r
+ if (!privDelete(oldVerifyKeyFile))\r
+ throw StandardException.newException(\r
+ SQLState.UNABLE_TO_DELETE_FILE, \r
+ oldVerifyKeyFile);\r
+ } else \r
+ {\r
+ // remove the old encryption key property.\r
+ properties.remove(RawStoreFactory.OLD_ENCRYPTED_KEY);\r
+ }\r
+\r
+ // remove the re-encryptin status flag. \r
+ properties.remove(RawStoreFactory.DB_ENCRYPTION_STATUS);\r
+ }\r
+\r
+\r
+\r
+\r
+ /**\r
+ * checks if the database is in the right state to (re)encrypt it.\r
+ *\r
+ * @param reEncrypt true if the database getting encrypted \r
+ * with new password/key.\r
+ * @exception StandardException \r
+ * if there is global transaction in the prepared state or\r
+ * if the database is not at the version 10.2 or above, this\r
+ * feature is not supported or \r
+ * if the log is archived for the database.\r
+ */\r
+ private void canEncryptDatabase(boolean reEncrypt) \r
+ throws StandardException \r
+ {\r
+\r
+ String feature = (reEncrypt ? \r
+ "newBootPassword/newEncryptionKey attribute" : \r
+ "dataEncryption attribute on an existing database");\r
+\r
+ // check if the database version is at 10.2 or above.\r
+ // encrytpion or re-encryption of the database \r
+ // is supported only in version 10.2 or above. \r
+ logFactory.checkVersion(\r
+ RawStoreFactory.DERBY_STORE_MAJOR_VERSION_10, \r
+ RawStoreFactory.DERBY_STORE_MINOR_VERSION_2, \r
+ feature);\r
+\r
+ // database can not be (re)encrypted if there \r
+ // are any global transactions in the prepared state \r
+ // after the recovery. The reason for this restriction \r
+ // is that any transaction log before the encryption can not \r
+ // be read once database is reconfigure with new encryption \r
+ // key.\r
+ if (xactFactory.hasPreparedXact()) {\r
+ if(reEncrypt) \r
+ throw StandardException.newException(\r
+ SQLState.REENCRYPTION_PREPARED_XACT_EXIST);\r
+ else \r
+ throw StandardException.newException(\r
+ SQLState.ENCRYPTION_PREPARED_XACT_EXIST);\r
+ }\r
+\r
+\r
+ // check if the database has the log archived. \r
+ // database can not be congured of encryption or\r
+ // or re-encrypt it with a new key when the database \r
+ // log is being archived. The reason for this restriction is \r
+ // it will create a scenarion where users will \r
+ // have some logs encrypted with new key and some with old key \r
+ // when rollforward recovery is performed. \r
+ \r
+ if (logFactory.logArchived()) \r
+ {\r
+ if(reEncrypt) \r
+ throw StandardException.newException(\r
+ SQLState.CANNOT_REENCRYPT_LOG_ARCHIVED_DATABASE);\r
+ else \r
+ throw StandardException.newException(\r
+ SQLState.CANNOT_ENCRYPT_LOG_ARCHIVED_DATABASE);\r
+ \r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ **\r
+ */\r
+\r
+ public StandardException markCorrupt(StandardException originalError) {\r
+\r
+ logFactory.markCorrupt(originalError);\r
+ dataFactory.markCorrupt(originalError);\r
+ xactFactory.markCorrupt(originalError);\r
+\r
+ return originalError;\r
+ }\r
+\r
+ /*\r
+ * class specific methods\r
+ */\r
+\r
+ /* subclass can override this method to load different submodules */\r
+ public String getTransactionFactoryModule()\r
+ {\r
+ return TransactionFactory.MODULE;\r
+ }\r
+\r
+ public String getDataFactoryModule()\r
+ {\r
+ return DataFactory.MODULE;\r
+ }\r
+\r
+ public String getLogFactoryModule()\r
+ {\r
+ return LogFactory.MODULE;\r
+ }\r
+\r
+\r
+ private void logHistory(OutputStreamWriter historyFile, String msg) throws IOException\r
+ {\r
+ Date d = new Date();\r
+ historyFile.write(d.toString() + ":" + msg + "\n");\r
+ historyFile.flush();\r
+ }\r
+\r
+ /*\r
+ * Get the file path. If the canonical path can be obtained then return the \r
+ * canonical path, otherwise just return the abstract path. Typically if\r
+ * there are no permission to read user.dir when running under security\r
+ * manager canonical path can not be obtained.\r
+ *\r
+ * This method is used to a write path name to error/status log file, where it\r
+ * would be nice to print full paths but not esstential that the user \r
+ * grant permissions to read user.dir property.\r
+ */\r
+ private String getFilePath(StorageFile file) {\r
+ String path = privGetCanonicalPath(file);\r
+ if(path != null ) {\r
+ return path;\r
+ }else {\r
+ //can not get the canoncal path, \r
+ // return the abstract path\r
+ return file.getPath();\r
+ }\r
+ }\r
+\r
+ /*\r
+ * Get the file path. If the canonical path can be obtained then return the \r
+ * canonical path, otherwise just return the abstract path. Typically if\r
+ * there are no permission to read user.dir when running under security\r
+ * manager canonical path can not be obtained.\r
+ *\r
+ * This method is used to a write a file path name to error/status log file, \r
+ * where it would be nice to print full paths but not esstential that the user\r
+ * grant permissions to read user.dir property.\r
+ *\r
+ */\r
+ private String getFilePath(File file) {\r
+ String path = privGetCanonicalPath(file);\r
+ if(path != null ) {\r
+ return path;\r
+ }else {\r
+ // can not get the canoncal path, \r
+ // return the abstract path\r
+ return file.getPath();\r
+ }\r
+ }\r
+\r
+ protected boolean privCopyDirectory(StorageFile from, File to)\r
+ {\r
+ return privCopyDirectory(from, to, (byte[])null, \r
+ (String[])null, true);\r
+ }\r
+\r
+ protected boolean privCopyDirectory(File from, StorageFile to)\r
+ {\r
+ return privCopyDirectory(from, to, (byte[])null, (String[])null);\r
+ }\r
+\r
+ /**\r
+ * Return an id which can be used to create a container.\r
+ * <p>\r
+ * Return an id number with is greater than any existing container\r
+ * in the current database. Caller will use this to allocate future\r
+ * container numbers - most likely caching the value and then incrementing\r
+ * it as it is used.\r
+ * <p>\r
+ *\r
+ * @return The an id which can be used to create a container.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public long getMaxContainerId()\r
+ throws StandardException\r
+ {\r
+ return(dataFactory.getMaxContainerId());\r
+ }\r
+\r
+ /**\r
+ * Check to see if a database has been upgraded to the required\r
+ * level in order to use a store feature.\r
+ *\r
+ * @param requiredMajorVersion required database Engine major version\r
+ * @param requiredMinorVersion required database Engine minor version\r
+ * @param feature Non-null to throw an exception, null to \r
+ * return the state of the version match.\r
+ *\r
+ * @return <code> true </code> if the database has been upgraded to \r
+ * the required level, <code> false </code> otherwise.\r
+ *\r
+ * @exception StandardException \r
+ * if the database is not at the require version \r
+ * when <code>feature</code> feature is \r
+ * not <code> null </code>. \r
+ */\r
+ public boolean checkVersion(\r
+ int requiredMajorVersion, \r
+ int requiredMinorVersion, \r
+ String feature) \r
+ throws StandardException\r
+ {\r
+ return(\r
+ logFactory.checkVersion(\r
+ requiredMajorVersion, requiredMinorVersion, feature));\r
+ }\r
+\r
+ \r
+ /*\r
+ These methods require Priv Blocks when run under a security manager.\r
+ */\r
+\r
+ private synchronized OutputStreamWriter privFileWriter( StorageFile fileName, boolean append) throws IOException\r
+ {\r
+ actionCode = FILE_WRITER_ACTION;\r
+ actionStorageFile = fileName;\r
+ actionAppend = append;\r
+ try{\r
+ return (OutputStreamWriter) java.security.AccessController.doPrivileged( this);\r
+ }catch (java.security.PrivilegedActionException pae)\r
+ {\r
+ throw (IOException) pae.getException();\r
+ }\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ }\r
+ }\r
+\r
+ private synchronized boolean privExists( File file)\r
+ {\r
+ actionCode = REGULAR_FILE_EXISTS_ACTION;\r
+ actionRegularFile = file;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+ private synchronized boolean privExists(final StorageFile file)\r
+ {\r
+ actionCode = STORAGE_FILE_EXISTS_ACTION;\r
+ actionStorageFile = file;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ }\r
+ }\r
+\r
+\r
+ private synchronized boolean privDelete( File file)\r
+ {\r
+ actionCode = REGULAR_FILE_DELETE_ACTION;\r
+ actionRegularFile = file;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+ private synchronized boolean privDelete(StorageFile file)\r
+ {\r
+ actionCode = STORAGE_FILE_DELETE_ACTION;\r
+ actionStorageFile = file;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ }\r
+ }\r
+\r
+\r
+\r
+ private synchronized boolean privMkdirs( File file)\r
+ {\r
+ actionCode = REGULAR_FILE_MKDIRS_ACTION;\r
+ actionRegularFile = file;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+\r
+ private synchronized boolean privIsDirectory( File file)\r
+ {\r
+ actionCode = REGULAR_FILE_IS_DIRECTORY_ACTION;\r
+ actionRegularFile = file;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+ private synchronized boolean privRemoveDirectory( File file)\r
+ {\r
+ actionCode = REGULAR_FILE_REMOVE_DIRECTORY_ACTION;\r
+ actionRegularFile = file;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+ private synchronized boolean privRenameTo( File file1, File file2)\r
+ {\r
+ actionCode = REGULAR_FILE_RENAME_TO_ACTION;\r
+ actionRegularFile = file1;\r
+ actionRegularFile2 = file2;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ actionRegularFile2 = null;\r
+ }\r
+ }\r
+\r
+ private synchronized boolean privCopyDirectory(StorageFile from, \r
+ File to, \r
+ byte[] buffer, \r
+ String[] filter,\r
+ boolean copySubdirs)\r
+ {\r
+ actionCode = COPY_STORAGE_DIRECTORY_TO_REGULAR_ACTION;\r
+ actionStorageFile = from;\r
+ actionRegularFile = to;\r
+ actionBuffer = buffer;\r
+ actionFilter = filter;\r
+ actionCopySubDirs = copySubdirs;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ actionRegularFile = null;\r
+ actionBuffer = null;\r
+ actionFilter = null;\r
+ }\r
+ }\r
+\r
+\r
+ private synchronized boolean privCopyDirectory( File from, StorageFile to, byte[] buffer, String[] filter)\r
+ {\r
+ actionCode = COPY_REGULAR_DIRECTORY_TO_STORAGE_ACTION;\r
+ actionStorageFile = to;\r
+ actionRegularFile = from;\r
+ actionBuffer = buffer;\r
+ actionFilter = filter;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ actionRegularFile = null;\r
+ actionBuffer = null;\r
+ actionFilter = null;\r
+ }\r
+ }\r
+\r
+ \r
+ private synchronized boolean privCopyFile( File from, StorageFile to)\r
+ {\r
+ actionCode = COPY_REGULAR_FILE_TO_STORAGE_ACTION;\r
+ actionStorageFile = to;\r
+ actionRegularFile = from;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+ private synchronized boolean privCopyFile( StorageFile from, File to)\r
+ {\r
+ actionCode = COPY_STORAGE_FILE_TO_REGULAR_ACTION;\r
+ actionStorageFile = from;\r
+ actionRegularFile = to;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+\r
+ \r
+ private synchronized boolean privCopyFile( StorageFile from, StorageFile to)\r
+ {\r
+ actionCode = COPY_STORAGE_FILE_TO_STORAGE_ACTION;\r
+ actionStorageFile = from;\r
+ actionToStorageFile = to;\r
+\r
+ try\r
+ {\r
+ Object ret = AccessController.doPrivileged( this);\r
+ return ((Boolean) ret).booleanValue();\r
+ }\r
+ catch( PrivilegedActionException pae) { return false;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ actionToStorageFile = null;\r
+ }\r
+ }\r
+\r
+\r
+ private synchronized String[] privList(final File file)\r
+ {\r
+ actionCode = REGULAR_FILE_LIST_DIRECTORY_ACTION;\r
+ actionRegularFile = file;\r
+\r
+ try\r
+ {\r
+ return (String[]) AccessController.doPrivileged( this);\r
+ }\r
+ catch( PrivilegedActionException pae) { return null;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+ private synchronized String[] privList(final StorageFile file)\r
+ {\r
+ actionCode = STORAGE_FILE_LIST_DIRECTORY_ACTION;\r
+ actionStorageFile = file;\r
+\r
+ try\r
+ {\r
+ return (String[]) AccessController.doPrivileged( this);\r
+ }\r
+ catch( PrivilegedActionException pae) { return null;} // does not throw an exception\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ }\r
+ }\r
+\r
+\r
+ private synchronized String privGetCanonicalPath(final StorageFile file)\r
+ {\r
+ actionCode = STORAGE_FILE_GET_CANONICALPATH_ACTION;\r
+ actionStorageFile = file;\r
+\r
+ try\r
+ {\r
+ return (String) AccessController.doPrivileged( this);\r
+ }\r
+ catch( PrivilegedActionException pae) { \r
+ return null;\r
+ } // does not throw an exception\r
+ catch(SecurityException se) {\r
+ // there are no permission to get canonical path \r
+ // just return null.\r
+ return null;\r
+ }\r
+ finally\r
+ {\r
+ actionStorageFile = null;\r
+ }\r
+ }\r
+\r
+\r
+ private synchronized String privGetCanonicalPath(final File file)\r
+ {\r
+ actionCode = REGULAR_FILE_GET_CANONICALPATH_ACTION;\r
+ actionRegularFile = file;\r
+\r
+ try\r
+ {\r
+ return (String) AccessController.doPrivileged( this);\r
+ }\r
+ catch( PrivilegedActionException pae) { \r
+ return null;\r
+ } // does not throw an exception\r
+ catch(SecurityException se) { \r
+ // there are no permission to get canonical path \r
+ // just return null.\r
+ return null;\r
+ }\r
+ finally\r
+ {\r
+ actionRegularFile = null;\r
+ }\r
+ }\r
+\r
+\r
+ // PrivilegedExceptionAction method\r
+ public final Object run() throws IOException\r
+ {\r
+ switch(actionCode)\r
+ {\r
+ case FILE_WRITER_ACTION:\r
+ // SECURITY PERMISSION - MP1\r
+ return new OutputStreamWriter( actionStorageFile.getOutputStream( actionAppend));\r
+\r
+ case REGULAR_FILE_EXISTS_ACTION:\r
+ return ReuseFactory.getBoolean(actionRegularFile.exists());\r
+\r
+ case STORAGE_FILE_EXISTS_ACTION:\r
+ return ReuseFactory.getBoolean(actionStorageFile.exists());\r
+\r
+ case REGULAR_FILE_DELETE_ACTION:\r
+ return ReuseFactory.getBoolean(actionRegularFile.delete());\r
+\r
+ case STORAGE_FILE_DELETE_ACTION:\r
+ return ReuseFactory.getBoolean(actionStorageFile.delete());\r
+\r
+ case REGULAR_FILE_MKDIRS_ACTION:\r
+ // SECURITY PERMISSION - OP4\r
+ return ReuseFactory.getBoolean(actionRegularFile.mkdirs());\r
+\r
+ case REGULAR_FILE_IS_DIRECTORY_ACTION:\r
+ // SECURITY PERMISSION - MP1\r
+ return ReuseFactory.getBoolean(actionRegularFile.isDirectory());\r
+\r
+ case REGULAR_FILE_REMOVE_DIRECTORY_ACTION:\r
+ // SECURITY PERMISSION - MP1, OP5\r
+ return ReuseFactory.getBoolean(FileUtil.removeDirectory(actionRegularFile));\r
+\r
+ case REGULAR_FILE_RENAME_TO_ACTION:\r
+ // SECURITY PERMISSION - OP4\r
+ return ReuseFactory.getBoolean(actionRegularFile.renameTo(actionRegularFile2));\r
+\r
+ case COPY_STORAGE_DIRECTORY_TO_REGULAR_ACTION:\r
+ // SECURITY PERMISSION - MP1, OP4\r
+ return ReuseFactory.getBoolean(FileUtil.copyDirectory(storageFactory,\r
+ actionStorageFile,\r
+ actionRegularFile,\r
+ actionBuffer,\r
+ actionFilter,\r
+ actionCopySubDirs));\r
+\r
+ case COPY_REGULAR_DIRECTORY_TO_STORAGE_ACTION:\r
+ // SECURITY PERMISSION - MP1, OP4\r
+ return ReuseFactory.getBoolean(FileUtil.copyDirectory((WritableStorageFactory)storageFactory,\r
+ actionRegularFile,\r
+ actionStorageFile,\r
+ actionBuffer,\r
+ actionFilter));\r
+\r
+ case COPY_REGULAR_FILE_TO_STORAGE_ACTION:\r
+ // SECURITY PERMISSION - MP1, OP4\r
+ return ReuseFactory.getBoolean(FileUtil.copyFile((WritableStorageFactory) storageFactory,\r
+ actionRegularFile,\r
+ actionStorageFile));\r
+\r
+ case REGULAR_FILE_LIST_DIRECTORY_ACTION:\r
+ // SECURITY PERMISSION - MP1\r
+ return (String[])(actionRegularFile.list());\r
+\r
+ case STORAGE_FILE_LIST_DIRECTORY_ACTION:\r
+ // SECURITY PERMISSION - MP1\r
+ return (String[])(actionStorageFile.list());\r
+\r
+ case COPY_STORAGE_FILE_TO_REGULAR_ACTION:\r
+ // SECURITY PERMISSION - MP1, OP4\r
+ return ReuseFactory.getBoolean(FileUtil.copyFile(\r
+ (WritableStorageFactory) storageFactory,\r
+ actionStorageFile,\r
+ actionRegularFile));\r
+\r
+ \r
+ case COPY_STORAGE_FILE_TO_STORAGE_ACTION:\r
+ // SECURITY PERMISSION - MP1, OP4\r
+ return ReuseFactory.getBoolean(FileUtil.copyFile(\r
+ (WritableStorageFactory) storageFactory,\r
+ actionStorageFile,\r
+ actionToStorageFile));\r
+\r
+ case REGULAR_FILE_GET_CANONICALPATH_ACTION:\r
+ // SECURITY PERMISSION - MP1\r
+ return (String)(actionRegularFile.getCanonicalPath());\r
+ \r
+ case STORAGE_FILE_GET_CANONICALPATH_ACTION:\r
+ // SECURITY PERMISSION - MP1\r
+ return (String)(actionStorageFile.getCanonicalPath());\r
+ }\r
+ return null;\r
+ } // end of run\r
+}\r