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 boolean snapshotNoConfig;
110
111 private FileSnapshot commitSnapshot;
112
113 private LockToken token;
114
115
116
117
118
119
120
121 public LockFile(File f) {
122 ref = f;
123 lck = getLockFile(ref);
124 }
125
126
127
128
129
130
131
132
133
134
135 public boolean lock() throws IOException {
136 if (haveLck) {
137 throw new IllegalStateException(
138 MessageFormat.format(JGitText.get().lockAlreadyHeld, ref));
139 }
140 FileUtils.mkdirs(lck.getParentFile(), true);
141 try {
142 token = FS.DETECTED.createNewFileAtomic(lck);
143 } catch (IOException e) {
144 LOG.error(JGitText.get().failedCreateLockFile, lck, e);
145 throw e;
146 }
147 boolean obtainedLock = token.isCreated();
148 if (obtainedLock) {
149 haveLck = true;
150 isAppend = false;
151 written = false;
152 } else {
153 closeToken();
154 }
155 return obtainedLock;
156 }
157
158
159
160
161
162
163
164
165
166
167 public boolean lockForAppend() throws IOException {
168 if (!lock()) {
169 return false;
170 }
171 copyCurrentContent();
172 isAppend = true;
173 written = false;
174 return true;
175 }
176
177
178 boolean isLocked() {
179 return haveLck;
180 }
181
182 private FileOutputStream getStream() throws IOException {
183 return new FileOutputStream(lck, isAppend);
184 }
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 public void copyCurrentContent() throws IOException {
206 requireLock();
207 try (FileOutputStream out = getStream()) {
208 try (FileInputStream fis = new FileInputStream(ref)) {
209 if (fsync) {
210 FileChannel in = fis.getChannel();
211 long pos = 0;
212 long cnt = in.size();
213 while (0 < cnt) {
214 long r = out.getChannel().transferFrom(in, pos, cnt);
215 pos += r;
216 cnt -= r;
217 }
218 } else {
219 final byte[] buf = new byte[2048];
220 int r;
221 while ((r = fis.read(buf)) >= 0) {
222 out.write(buf, 0, r);
223 }
224 }
225 } catch (FileNotFoundException fnfe) {
226 if (ref.exists()) {
227 throw fnfe;
228 }
229
230
231 }
232 } catch (IOException | RuntimeException | Error ioe) {
233 unlock();
234 throw ioe;
235 }
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251 public void write(ObjectId id) throws IOException {
252 byte[] buf = new byte[Constants.OBJECT_ID_STRING_LENGTH + 1];
253 id.copyTo(buf, 0);
254 buf[Constants.OBJECT_ID_STRING_LENGTH] = '\n';
255 write(buf);
256 }
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272 public void write(byte[] content) throws IOException {
273 requireLock();
274 try (FileOutputStream out = getStream()) {
275 if (written) {
276 throw new IOException(MessageFormat
277 .format(JGitText.get().lockStreamClosed, ref));
278 }
279 if (fsync) {
280 FileChannel fc = out.getChannel();
281 ByteBuffer buf = ByteBuffer.wrap(content);
282 while (0 < buf.remaining()) {
283 fc.write(buf);
284 }
285 fc.force(true);
286 } else {
287 out.write(content);
288 }
289 written = true;
290 } catch (IOException | RuntimeException | Error ioe) {
291 unlock();
292 throw ioe;
293 }
294 }
295
296
297
298
299
300
301
302
303
304
305 public OutputStream getOutputStream() {
306 requireLock();
307
308 if (written || os != null) {
309 throw new IllegalStateException(MessageFormat
310 .format(JGitText.get().lockStreamMultiple, ref));
311 }
312
313 return new OutputStream() {
314
315 private OutputStream out;
316
317 private boolean closed;
318
319 private OutputStream get() throws IOException {
320 if (written) {
321 throw new IOException(MessageFormat
322 .format(JGitText.get().lockStreamMultiple, ref));
323 }
324 if (out == null) {
325 os = getStream();
326 if (fsync) {
327 out = Channels.newOutputStream(os.getChannel());
328 } else {
329 out = os;
330 }
331 }
332 return out;
333 }
334
335 @Override
336 public void write(byte[] b, int o, int n) throws IOException {
337 get().write(b, o, n);
338 }
339
340 @Override
341 public void write(byte[] b) throws IOException {
342 get().write(b);
343 }
344
345 @Override
346 public void write(int b) throws IOException {
347 get().write(b);
348 }
349
350 @Override
351 public void close() throws IOException {
352 if (closed) {
353 return;
354 }
355 closed = true;
356 try {
357 if (written) {
358 throw new IOException(MessageFormat
359 .format(JGitText.get().lockStreamClosed, ref));
360 }
361 if (out != null) {
362 if (fsync) {
363 os.getChannel().force(true);
364 }
365 out.close();
366 os = null;
367 }
368 written = true;
369 } catch (IOException | RuntimeException | Error ioe) {
370 unlock();
371 throw ioe;
372 }
373 }
374 };
375 }
376
377 void requireLock() {
378 if (!haveLck) {
379 unlock();
380 throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref));
381 }
382 }
383
384
385
386
387
388
389
390
391
392 public void setNeedStatInformation(boolean on) {
393 setNeedSnapshot(on);
394 }
395
396
397
398
399
400
401
402
403 public void setNeedSnapshot(boolean on) {
404 needSnapshot = on;
405 }
406
407
408
409
410
411
412
413
414
415
416
417 public void setNeedSnapshotNoConfig(boolean on) {
418 needSnapshot = on;
419 snapshotNoConfig = on;
420 }
421
422
423
424
425
426
427
428 public void setFSync(boolean on) {
429 fsync = on;
430 }
431
432
433
434
435
436
437
438
439
440
441
442
443
444 public void waitForStatChange() throws InterruptedException {
445 FileSnapshot o = FileSnapshot.save(ref);
446 FileSnapshot n = FileSnapshot.save(lck);
447 long fsTimeResolution = FS.getFileStoreAttributes(lck.toPath())
448 .getFsTimestampResolution().toNanos();
449 while (o.equals(n)) {
450 TimeUnit.NANOSECONDS.sleep(fsTimeResolution);
451 try {
452 Files.setLastModifiedTime(lck.toPath(),
453 FileTime.from(Instant.now()));
454 } catch (IOException e) {
455 n.waitUntilNotRacy();
456 }
457 n = FileSnapshot.save(lck);
458 }
459 }
460
461
462
463
464
465
466
467
468
469
470
471
472 public boolean commit() {
473 if (os != null) {
474 unlock();
475 throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotClosed, ref));
476 }
477
478 saveStatInformation();
479 try {
480 FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
481 haveLck = false;
482 isAppend = false;
483 written = false;
484 closeToken();
485 return true;
486 } catch (IOException e) {
487 unlock();
488 return false;
489 }
490 }
491
492 private void closeToken() {
493 if (token != null) {
494 token.close();
495 token = null;
496 }
497 }
498
499 private void saveStatInformation() {
500 if (needSnapshot) {
501 commitSnapshot = snapshotNoConfig ?
502
503 FileSnapshot.saveNoConfig(lck)
504 : FileSnapshot.save(lck);
505 }
506 }
507
508
509
510
511
512
513
514 @Deprecated
515 public long getCommitLastModified() {
516 return commitSnapshot.lastModified();
517 }
518
519
520
521
522
523
524 public Instant getCommitLastModifiedInstant() {
525 return commitSnapshot.lastModifiedInstant();
526 }
527
528
529
530
531
532
533 public FileSnapshot getCommitSnapshot() {
534 return commitSnapshot;
535 }
536
537
538
539
540
541
542
543 public void createCommitSnapshot() {
544 saveStatInformation();
545 }
546
547
548
549
550
551
552 public void unlock() {
553 if (os != null) {
554 try {
555 os.close();
556 } catch (IOException e) {
557 LOG.error(MessageFormat
558 .format(JGitText.get().unlockLockFileFailed, lck), e);
559 }
560 os = null;
561 }
562
563 if (haveLck) {
564 haveLck = false;
565 try {
566 FileUtils.delete(lck, FileUtils.RETRY);
567 } catch (IOException e) {
568 LOG.error(MessageFormat
569 .format(JGitText.get().unlockLockFileFailed, lck), e);
570 } finally {
571 closeToken();
572 }
573 }
574 isAppend = false;
575 written = false;
576 }
577
578
579 @SuppressWarnings("nls")
580 @Override
581 public String toString() {
582 return "LockFile[" + lck + ", haveLck=" + haveLck + "]";
583 }
584 }