1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.utils;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.RandomAccessFile;
23 import java.nio.channels.FileLock;
24 import java.security.SecureRandom;
25 import java.sql.Timestamp;
26 import java.util.Date;
27 import javax.annotation.concurrent.NotThreadSafe;
28 import org.owasp.dependencycheck.exception.WriteLockException;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32
33
34
35
36
37
38 @NotThreadSafe
39 public class WriteLock implements AutoCloseable {
40
41
42
43
44 private static final Logger LOGGER = LoggerFactory.getLogger(WriteLock.class);
45
46
47
48 private static final SecureRandom SECURE_RANDOM = new SecureRandom();
49
50
51
52 public static final int SLEEP_DURATION = 15000;
53
54
55
56 public static final int MAX_SLEEP_COUNT = 160;
57
58
59
60 private FileLock lock = null;
61
62
63
64 private RandomAccessFile file = null;
65
66
67
68 private File lockFile = null;
69
70
71
72 private final Settings settings;
73
74
75
76 private final String magic;
77
78
79
80 private final boolean isLockable;
81
82
83
84 private final String lockFileName;
85
86
87
88
89 private WriteLockShutdownHook hook = null;
90
91
92
93
94
95
96
97 public WriteLock(Settings settings) throws WriteLockException {
98 this(settings, true);
99 }
100
101
102
103
104
105
106
107
108
109
110
111 public WriteLock(Settings settings, boolean isLockable) throws WriteLockException {
112 this(settings, isLockable, "odc.update.lock");
113 }
114
115
116
117
118
119
120
121
122
123
124
125
126
127 public WriteLock(Settings settings, boolean isLockable, String lockFileName) throws WriteLockException {
128 this.settings = settings;
129 final byte[] random = new byte[16];
130 SECURE_RANDOM.nextBytes(random);
131 magic = Checksum.getHex(random);
132 this.isLockable = isLockable;
133 this.lockFileName = lockFileName;
134 lock();
135 }
136
137
138
139
140
141
142 public final void lock() throws WriteLockException {
143 if (!isLockable) {
144 return;
145 }
146 try {
147 final File dir = settings.getDataDirectory();
148 lockFile = new File(dir, lockFileName);
149 checkState();
150 int ctr = 0;
151 do {
152 try {
153 if (!lockFile.exists() && lockFile.createNewFile()) {
154 file = new RandomAccessFile(lockFile, "rw");
155 lock = file.getChannel().lock();
156 file.writeBytes(magic);
157 file.getChannel().force(true);
158 Thread.sleep(20);
159 file.seek(0);
160 final String current = file.readLine();
161 if (current != null && !current.equals(magic)) {
162 lock.close();
163 lock = null;
164 LOGGER.debug("Another process obtained a lock first ({})", Thread.currentThread().getName());
165 } else {
166 addShutdownHook();
167 final Timestamp timestamp = new Timestamp(System.currentTimeMillis());
168 LOGGER.debug("Lock file created ({}) {} @ {}", Thread.currentThread().getName(), magic, timestamp);
169 }
170 }
171 } catch (InterruptedException ex) {
172 Thread.currentThread().interrupt();
173 LOGGER.trace("Expected error as another thread has likely locked the file", ex);
174 } catch (IOException ex) {
175 LOGGER.trace("Expected error as another thread has likely locked the file", ex);
176 } finally {
177 if (lock == null && file != null) {
178 try {
179 file.close();
180 file = null;
181 } catch (IOException ex) {
182 LOGGER.trace("Unable to close the lock file", ex);
183 }
184 }
185 }
186 if (lock == null || !lock.isValid()) {
187 try {
188 final Timestamp timestamp = new Timestamp(System.currentTimeMillis());
189 LOGGER.debug("Sleeping thread {} ({}) for {} seconds because an exclusive lock on the database could not be obtained ({})",
190 Thread.currentThread().getName(), magic, SLEEP_DURATION / 1000, timestamp);
191 Thread.sleep(SLEEP_DURATION);
192 } catch (InterruptedException ex) {
193 LOGGER.debug("sleep was interrupted.", ex);
194 Thread.currentThread().interrupt();
195 }
196 }
197 } while (++ctr < MAX_SLEEP_COUNT && (lock == null || !lock.isValid()));
198 if (lock == null || !lock.isValid()) {
199 throw new WriteLockException("Unable to obtain the update lock, skipping the database update. Skipping the database update.");
200 }
201 } catch (IOException ex) {
202 throw new WriteLockException(ex.getMessage(), ex);
203 }
204 }
205
206
207
208
209 @Override
210 public void close() {
211 if (!isLockable) {
212 return;
213 }
214 if (lock != null) {
215 try {
216 lock.release();
217 lock = null;
218 } catch (IOException ex) {
219 LOGGER.debug("Failed to release lock", ex);
220 }
221 }
222 if (file != null) {
223 try {
224 file.close();
225 file = null;
226 } catch (IOException ex) {
227 LOGGER.debug("Unable to delete lock file", ex);
228 }
229 }
230 if (lockFile != null && lockFile.isFile()) {
231 final String msg = readLockFile();
232 if (msg != null && msg.equals(magic) && !lockFile.delete()) {
233 LOGGER.error("Lock file '{}' was unable to be deleted. Please manually delete this file.", lockFile.toString());
234 lockFile.deleteOnExit();
235 }
236 }
237 lockFile = null;
238 removeShutdownHook();
239 final Timestamp timestamp = new Timestamp(System.currentTimeMillis());
240 LOGGER.debug("Lock released ({}) {} @ {}", Thread.currentThread().getName(), magic, timestamp);
241 }
242
243
244
245
246
247
248
249
250 private void checkState() throws WriteLockException {
251 if (!lockFile.getParentFile().isDirectory() && !lockFile.mkdir()) {
252 throw new WriteLockException("Unable to create path to data directory.");
253 }
254 if (lockFile.isFile()) {
255
256 if (getFileAge(lockFile) > 30) {
257 LOGGER.debug("An old write lock file was found: {}", lockFile.getAbsolutePath());
258 if (!lockFile.delete()) {
259 LOGGER.warn("An old write lock file was found but the system was unable to delete "
260 + "the file. Consider manually deleting {}", lockFile.getAbsolutePath());
261 }
262 } else {
263 LOGGER.info("Lock file found `{}`", lockFile);
264 LOGGER.info("Existing update in progress; waiting for update to complete");
265 }
266 }
267 }
268
269
270
271
272
273
274
275
276 private String readLockFile() {
277 String msg = null;
278 try (RandomAccessFile f = new RandomAccessFile(lockFile, "rw")) {
279 msg = f.readLine();
280 } catch (IOException ex) {
281 LOGGER.debug(String.format("Error reading lock file: %s", lockFile), ex);
282 }
283 return msg;
284 }
285
286
287
288
289
290
291
292 private double getFileAge(File file) {
293 final Date d = new Date();
294 final long modified = file.lastModified();
295 final double time = (d.getTime() - modified) / 1000.0 / 60.0;
296 LOGGER.debug("Lock file age is {} minutes", time);
297 return time;
298 }
299
300
301
302
303 private void addShutdownHook() {
304 if (hook == null) {
305 hook = WriteLockShutdownHookFactory.getHook(settings);
306 hook.add(this);
307
308 }
309 }
310
311
312
313
314 private void removeShutdownHook() {
315 if (hook != null) {
316 hook.remove();
317 hook = null;
318 }
319 }
320 }