From 1e269f2a2cd1cdd62bfa67eb081a07d8b5b99a51 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 13 Aug 2017 07:41:35 -0400 Subject: [PATCH] externalized db lock --- .../data/update/NvdCveUpdater.java | 79 +--------- .../exception/H2DBLockException.java | 66 ++++++++ .../owasp/dependencycheck/utils/H2DBLock.java | 146 ++++++++++++++++++ 3 files changed, 220 insertions(+), 71 deletions(-) create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/H2DBLockException.java create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/H2DBLock.java diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java index 36014d139..b5fc703cd 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java @@ -47,9 +47,11 @@ import org.owasp.dependencycheck.data.update.nvd.DownloadTask; import org.owasp.dependencycheck.data.update.nvd.NvdCveInfo; import org.owasp.dependencycheck.data.update.nvd.ProcessTask; import org.owasp.dependencycheck.data.update.nvd.UpdateableNvdCve; +import org.owasp.dependencycheck.exception.H2DBLockException; import org.owasp.dependencycheck.utils.DateUtil; import org.owasp.dependencycheck.utils.Downloader; import org.owasp.dependencycheck.utils.DownloadFailedException; +import org.owasp.dependencycheck.utils.H2DBLock; import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; @@ -107,46 +109,9 @@ public class NvdCveUpdater implements CachedWebDataSource { if (isUpdateConfiguredFalse()) { return; } - FileLock lock = null; - RandomAccessFile ulFile = null; - File lockFile = null; + H2DBLock dbupdate = new H2DBLock(); try { - if (ConnectionFactory.isH2Connection()) { - final File dir = Settings.getDataDirectory(); - lockFile = new File(dir, "odc.update.lock"); - if (lockFile.isFile() && getFileAge(lockFile) > 5 && !lockFile.delete()) { - LOGGER.warn("An old db update lock file was found but the system was unable to delete " - + "the file. Consider manually deleting {}", lockFile.getAbsolutePath()); - } - int ctr = 0; - do { - try { - if (!lockFile.exists() && lockFile.createNewFile()) { - ulFile = new RandomAccessFile(lockFile, "rw"); - lock = ulFile.getChannel().lock(); - } - } catch (IOException ex) { - LOGGER.trace("Expected error as another thread has likely locked the file", ex); - } finally { - if (lock == null && ulFile != null) { - ulFile.close(); - } - } - if (lock == null || !lock.isValid()) { - try { - LOGGER.debug("Sleeping thread {} for 5 seconds because we could not obtain the update lock.", - Thread.currentThread().getName()); - Thread.sleep(5000); - } catch (InterruptedException ex) { - LOGGER.trace("ignorable error, sleep was interrupted.", ex); - Thread.currentThread().interrupt(); - } - } - } while (++ctr < 60 && (lock == null || !lock.isValid())); - if (lock == null || !lock.isValid()) { - throw new UpdateException("Unable to obtain the update lock, skipping the database update. Skippinig the database update."); - } - } + dbupdate.lock(); initializeExecutorServices(); cveDb = CveDB.getInstance(); dbProperties = cveDb.getDatabaseProperties(); @@ -168,30 +133,14 @@ public class NvdCveUpdater implements CachedWebDataSource { throw new UpdateException("Unable to download the NVD CVE data.", ex); } catch (DatabaseException ex) { throw new UpdateException("Database Exception, unable to update the data to use the most current data.", ex); - } catch (IOException ex) { - throw new UpdateException("Database Exception", ex); + } catch (H2DBLockException ex) { + throw new UpdateException("Unable to obtain an exclusive lock on the H2 database to perform updates", ex); } finally { - shutdownExecutorServices(); if (cveDb != null) { cveDb.close(); } - if (lock != null) { - try { - lock.release(); - } catch (IOException ex) { - LOGGER.trace("Ignorable exception", ex); - } - } - if (ulFile != null) { - try { - ulFile.close(); - } catch (IOException ex) { - LOGGER.trace("Ignorable exception", ex); - } - } - if (lockFile != null && lockFile.isFile() && !lockFile.delete()) { - LOGGER.error("Lock file '{}' was unable to be deleted. Please manually delete this file.", lockFile.toString()); - } + dbupdate.release(); + shutdownExecutorServices(); } } @@ -218,18 +167,6 @@ public class NvdCveUpdater implements CachedWebDataSource { return !autoUpdate; } - /** - * Returns the age of the file in minutes. - * - * @param file the file to calculate the age - * @return the age of the file - */ - private long getFileAge(File file) { - final Date d = new Date(); - final long modified = file.lastModified(); - return (d.getTime() - modified) / 1000 / 60; - } - /** * Initialize the executor services for download and processing of the NVD * CVE XML data. diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/H2DBLockException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/H2DBLockException.java new file mode 100644 index 000000000..55e496592 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/H2DBLockException.java @@ -0,0 +1,66 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2017 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.exception; + +/** + * An exception used when trying to obtain a lock on the H2 database. + * + * @author Jeremy Long + */ +public class H2DBLockException extends Exception { + + /** + * The serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a new H2DBLockException. + */ + public H2DBLockException() { + super(); + } + + /** + * Creates a new H2DBLockException. + * + * @param msg a message for the exception. + */ + public H2DBLockException(String msg) { + super(msg); + } + + /** + * Creates a new H2DBLockException. + * + * @param ex the cause of the exception. + */ + public H2DBLockException(Throwable ex) { + super(ex); + } + + /** + * Creates a new H2DBLockException. + * + * @param msg a message for the exception. + * @param ex the cause of the exception. + */ + public H2DBLockException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/H2DBLock.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/H2DBLock.java new file mode 100644 index 000000000..f92fa1bf6 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/H2DBLock.java @@ -0,0 +1,146 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2017 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.utils; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileLock; +import java.util.Date; +import org.owasp.dependencycheck.data.nvdcve.ConnectionFactory; +import org.owasp.dependencycheck.data.update.NvdCveUpdater; +import org.owasp.dependencycheck.data.update.exception.UpdateException; +import org.owasp.dependencycheck.exception.H2DBLockException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Jeremy Long + */ +public class H2DBLock { + + /** + * The logger. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(H2DBLock.class); + /** + * The file lock. + */ + private FileLock lock = null; + /** + * Reference to the file that we are locking. + */ + private RandomAccessFile file = null; + /** + * The lock file. + */ + private File lockFile = null; + + /** + * Determine if the lock is currently held. + * + * @return true if the lock is currently held + */ + public boolean isLocked() { + return lock != null && lock.isValid(); + } + + public void lock() throws H2DBLockException { + if (ConnectionFactory.isH2Connection()) { + try { + final File dir = Settings.getDataDirectory(); + lockFile = new File(dir, "odc.update.lock"); + if (lockFile.isFile() && getFileAge(lockFile) > 5 && !lockFile.delete()) { + LOGGER.warn("An old db update lock file was found but the system was unable to delete " + + "the file. Consider manually deleting {}", lockFile.getAbsolutePath()); + } + int ctr = 0; + do { + try { + if (!lockFile.exists() && lockFile.createNewFile()) { + file = new RandomAccessFile(lockFile, "rw"); + lock = file.getChannel().lock(); + } + } catch (IOException ex) { + LOGGER.trace("Expected error as another thread has likely locked the file", ex); + } finally { + if (lock == null && file != null) { + try { + file.close(); + } catch (IOException ex) { + LOGGER.trace("Unable to close the ulFile", ex); + } + } + } + if (lock == null || !lock.isValid()) { + try { + LOGGER.debug("Sleeping thread {} for 5 seconds because we could not obtain the update lock.", + Thread.currentThread().getName()); + Thread.sleep(5000); + } catch (InterruptedException ex) { + LOGGER.trace("ignorable error, sleep was interrupted.", ex); + Thread.currentThread().interrupt(); + } + } + } while (++ctr < 60 && (lock == null || !lock.isValid())); + if (lock == null || !lock.isValid()) { + throw new H2DBLockException("Unable to obtain the update lock, skipping the database update. Skippinig the database update."); + } + } catch (IOException ex) { + throw new H2DBLockException(ex.getMessage(), ex); + } + } + } + + public void release() { + if (lock != null) { + try { + lock.release(); + lock = null; + } catch (IOException ex) { + LOGGER.trace("Ignorable exception", ex); + } + } + if (file != null) { + try { + file.close(); + file = null; + } catch (IOException ex) { + LOGGER.trace("Ignorable exception", ex); + } + } + if (lockFile != null && lockFile.isFile() && !lockFile.delete()) { + LOGGER.error("Lock file '{}' was unable to be deleted. Please manually delete this file.", lockFile.toString()); + lockFile.deleteOnExit(); + } + lockFile = null; + } + + /** + * Returns the age of the file in minutes. + * + * @param file the file to calculate the age + * @return the age of the file + */ + private long getFileAge(File file) { + final Date d = new Date(); + final long modified = file.lastModified(); + return (d.getTime() - modified) / 1000 / 60; + } +}