1
2
3
4
5
6
7
8
9
10
11
12 package org.eclipse.jgit.internal.storage.file;
13
14 import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
15
16 import java.io.File;
17 import java.io.FileInputStream;
18 import java.io.FileNotFoundException;
19 import java.io.FileOutputStream;
20 import java.io.FilenameFilter;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.nio.ByteBuffer;
24 import java.nio.channels.Channels;
25 import java.nio.channels.FileChannel;
26 import java.nio.file.Files;
27 import java.nio.file.StandardCopyOption;
28 import java.nio.file.attribute.FileTime;
29 import java.text.MessageFormat;
30 import java.time.Instant;
31 import java.util.concurrent.TimeUnit;
32
33 import org.eclipse.jgit.internal.JGitText;
34 import org.eclipse.jgit.lib.Constants;
35 import org.eclipse.jgit.lib.ObjectId;
36 import org.eclipse.jgit.util.FS;
37 import org.eclipse.jgit.util.FS.LockToken;
38 import org.eclipse.jgit.util.FileUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43
44
45
46
47
48
49
50
51
52 public class LockFile {
53 private static final Logger LOG = LoggerFactory.getLogger(LockFile.class);
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public static boolean unlock(File file) {
68 final File lockFile = getLockFile(file);
69 final int flags = FileUtils.RETRY | FileUtils.SKIP_MISSING;
70 try {
71 FileUtils.delete(lockFile, flags);
72 } catch (IOException ignored) {
73
74 }
75 return !lockFile.exists();
76 }
77
78
79
80
81
82
83
84 static File getLockFile(File file) {
85 return new File(file.getParentFile(),
86 file.getName() + LOCK_SUFFIX);
87 }
88
89
90 static final FilenameFilter FILTER = (File dir,
91 String name) -> !name.endsWith(LOCK_SUFFIX);
92
93 private final File ref;
94
95 private final File lck;
96
97 private boolean haveLck;
98
99 private FileOutputStream os;
100
101 private boolean needSnapshot;
102
103 private boolean fsync;
104
105 private boolean isAppend;
106
107 private boolean written;
108
109 private FileSnapshot commitSnapshot;
110
111 private LockToken token;
112
113
114
115
116
117
118
119 public LockFile(File f) {
120 ref = f;
121 lck = getLockFile(ref);
122 }
123
124
125
126
127
128
129
130
131
132
133 public boolean lock() throws IOException {
134 if (haveLck) {
135 throw new IllegalStateException(
136 MessageFormat.format(JGitText.get().lockAlreadyHeld, ref));
137 }
138 FileUtils.mkdirs(lck.getParentFile(), true);
139 try {
140 token = FS.DETECTED.createNewFileAtomic(lck);
141 } catch (IOException e) {
142 LOG.error(JGitText.get().failedCreateLockFile, lck, e);
143 throw e;
144 }
145 boolean obtainedLock = token.isCreated();
146 if (obtainedLock) {
147 haveLck = true;
148 isAppend = false;
149 written = false;
150 } else {
151 closeToken();
152 }
153 return obtainedLock;
154 }
155
156
157
158
159
160
161
162
163
164
165 public boolean lockForAppend() throws IOException {
166 if (!lock()) {
167 return false;
168 }
169 copyCurrentContent();
170 isAppend = true;
171 written = false;
172 return true;
173 }
174
175
176 boolean isLocked() {
177 return haveLck;
178 }
179
180 private FileOutputStream getStream() throws IOException {
181 return new FileOutputStream(lck, isAppend);
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 public void copyCurrentContent() throws IOException {
204 requireLock();
205 try (FileOutputStream out = getStream()) {
206 try (FileInputStream fis = new FileInputStream(ref)) {
207 if (fsync) {
208 FileChannel in = fis.getChannel();
209 long pos = 0;
210 long cnt = in.size();
211 while (0 < cnt) {
212 long r = out.getChannel().transferFrom(in, pos, cnt);
213 pos += r;
214 cnt -= r;
215 }
216 } else {
217 final byte[] buf = new byte[2048];
218 int r;
219 while ((r = fis.read(buf)) >= 0) {
220 out.write(buf, 0, r);
221 }
222 }
223 } catch (FileNotFoundException fnfe) {
224 if (ref.exists()) {
225 throw fnfe;
226 }
227
228
229 }
230 } catch (IOException | RuntimeException | Error ioe) {
231 unlock();
232 throw ioe;
233 }
234 }
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 public void write(ObjectId id) throws IOException {
250 byte[] buf = new byte[Constants.OBJECT_ID_STRING_LENGTH + 1];
251 id.copyTo(buf, 0);
252 buf[Constants.OBJECT_ID_STRING_LENGTH] = '\n';
253 write(buf);
254 }
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270 public void write(byte[] content) throws IOException {
271 requireLock();
272 try (FileOutputStream out = getStream()) {
273 if (written) {
274 throw new IOException(MessageFormat
275 .format(JGitText.get().lockStreamClosed, ref));
276 }
277 if (fsync) {
278 FileChannel fc = out.getChannel();
279 ByteBuffer buf = ByteBuffer.wrap(content);
280 while (0 < buf.remaining()) {
281 fc.write(buf);
282 }
283 fc.force(true);
284 } else {
285 out.write(content);
286 }
287 written = true;
288 } catch (IOException | RuntimeException | Error ioe) {
289 unlock();
290 throw ioe;
291 }
292 }
293
294
295
296
297
298
299
300
301
302
303 public OutputStream getOutputStream() {
304 requireLock();
305
306 if (written || os != null) {
307 throw new IllegalStateException(MessageFormat
308 .format(JGitText.get().lockStreamMultiple, ref));
309 }
310
311 return new OutputStream() {
312
313 private OutputStream out;
314
315 private boolean closed;
316
317 private OutputStream get() throws IOException {
318 if (written) {
319 throw new IOException(MessageFormat
320 .format(JGitText.get().lockStreamMultiple, ref));
321 }
322 if (out == null) {
323 os = getStream();
324 if (fsync) {
325 out = Channels.newOutputStream(os.getChannel());
326 } else {
327 out = os;
328 }
329 }
330 return out;
331 }
332
333 @Override
334 public void write(byte[] b, int o, int n) throws IOException {
335 get().write(b, o, n);
336 }
337
338 @Override
339 public void write(byte[] b) throws IOException {
340 get().write(b);
341 }
342
343 @Override
344 public void write(int b) throws IOException {
345 get().write(b);
346 }
347
348 @Override
349 public void close() throws IOException {
350 if (closed) {
351 return;
352 }
353 closed = true;
354 try {
355 if (written) {
356 throw new IOException(MessageFormat
357 .format(JGitText.get().lockStreamClosed, ref));
358 }
359 if (out != null) {
360 if (fsync) {
361 os.getChannel().force(true);
362 }
363 out.close();
364 os = null;
365 }
366 written = true;
367 } catch (IOException | RuntimeException | Error ioe) {
368 unlock();
369 throw ioe;
370 }
371 }
372 };
373 }
374
375 void requireLock() {
376 if (!haveLck) {
377 unlock();
378 throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref));
379 }
380 }
381
382
383
384
385
386
387
388
389
390 public void setNeedStatInformation(boolean on) {
391 setNeedSnapshot(on);
392 }
393
394
395
396
397
398
399
400
401 public void setNeedSnapshot(boolean on) {
402 needSnapshot = on;
403 }
404
405
406
407
408
409
410
411 public void setFSync(boolean on) {
412 fsync = on;
413 }
414
415
416
417
418
419
420
421
422
423
424
425
426
427 public void waitForStatChange() throws InterruptedException {
428 FileSnapshot o = FileSnapshot.save(ref);
429 FileSnapshot n = FileSnapshot.save(lck);
430 long fsTimeResolution = FS.getFileStoreAttributes(lck.toPath())
431 .getFsTimestampResolution().toNanos();
432 while (o.equals(n)) {
433 TimeUnit.NANOSECONDS.sleep(fsTimeResolution);
434 try {
435 Files.setLastModifiedTime(lck.toPath(),
436 FileTime.from(Instant.now()));
437 } catch (IOException e) {
438 n.waitUntilNotRacy();
439 }
440 n = FileSnapshot.save(lck);
441 }
442 }
443
444
445
446
447
448
449
450
451
452
453
454
455 public boolean commit() {
456 if (os != null) {
457 unlock();
458 throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotClosed, ref));
459 }
460
461 saveStatInformation();
462 try {
463 FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
464 haveLck = false;
465 isAppend = false;
466 written = false;
467 closeToken();
468 return true;
469 } catch (IOException e) {
470 unlock();
471 return false;
472 }
473 }
474
475 private void closeToken() {
476 if (token != null) {
477 token.close();
478 token = null;
479 }
480 }
481
482 private void saveStatInformation() {
483 if (needSnapshot)
484 commitSnapshot = FileSnapshot.save(lck);
485 }
486
487
488
489
490
491
492
493 @Deprecated
494 public long getCommitLastModified() {
495 return commitSnapshot.lastModified();
496 }
497
498
499
500
501
502
503 public Instant getCommitLastModifiedInstant() {
504 return commitSnapshot.lastModifiedInstant();
505 }
506
507
508
509
510
511
512 public FileSnapshot getCommitSnapshot() {
513 return commitSnapshot;
514 }
515
516
517
518
519
520
521
522 public void createCommitSnapshot() {
523 saveStatInformation();
524 }
525
526
527
528
529
530
531 public void unlock() {
532 if (os != null) {
533 try {
534 os.close();
535 } catch (IOException e) {
536 LOG.error(MessageFormat
537 .format(JGitText.get().unlockLockFileFailed, lck), e);
538 }
539 os = null;
540 }
541
542 if (haveLck) {
543 haveLck = false;
544 try {
545 FileUtils.delete(lck, FileUtils.RETRY);
546 } catch (IOException e) {
547 LOG.error(MessageFormat
548 .format(JGitText.get().unlockLockFileFailed, lck), e);
549 } finally {
550 closeToken();
551 }
552 }
553 isAppend = false;
554 written = false;
555 }
556
557
558 @SuppressWarnings("nls")
559 @Override
560 public String toString() {
561 return "LockFile[" + lck + ", haveLck=" + haveLck + "]";
562 }
563 }