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