2
This code is derived from jgit (http://eclipse.org/jgit).
3
Copyright owners are documented in jgit's IP log.
5
This program and the accompanying materials are made available
6
under the terms of the Eclipse Distribution License v1.0 which
7
accompanies this distribution, is reproduced below, and is
8
available at http://www.eclipse.org/org/documents/edl-v10.php
12
Redistribution and use in source and binary forms, with or
13
without modification, are permitted provided that the following
16
- Redistributions of source code must retain the above copyright
17
notice, this list of conditions and the following disclaimer.
19
- Redistributions in binary form must reproduce the above
20
copyright notice, this list of conditions and the following
21
disclaimer in the documentation and/or other materials provided
22
with the distribution.
24
- Neither the name of the Eclipse Foundation, Inc. nor the
25
names of its contributors may be used to endorse or promote
26
products derived from this software without specific prior
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48
using NGit.Storage.File;
52
namespace NGit.Storage.File
54
/// <summary>Git style file locking and replacement.</summary>
56
/// Git style file locking and replacement.
58
/// To modify a ref file Git tries to use an atomic update approach: we write the
59
/// new data into a brand new file, then rename it in place over the old name.
60
/// This way we can just delete the temporary file if anything goes wrong, and
61
/// nothing has been damaged. To coordinate access from multiple processes at
62
/// once Git tries to atomically create the new temporary file under a well-known
67
internal static readonly string SUFFIX = ".lock";
70
/// <summary>Unlock the given file.</summary>
72
/// Unlock the given file.
74
/// This method can be used for recovering from a thrown
75
/// <see cref="NGit.Errors.LockFailedException">NGit.Errors.LockFailedException</see>
76
/// . This method does not validate that the lock
77
/// is or is not currently held before attempting to unlock it.
79
/// <param name="file"></param>
80
/// <returns>true if unlocked, false if unlocking failed</returns>
81
public static bool Unlock(FilePath file)
83
FilePath lockFile = GetLockFile(file);
84
int flags = FileUtils.RETRY | FileUtils.SKIP_MISSING;
87
FileUtils.Delete(lockFile, flags);
92
// Ignore and return whether lock file still exists
93
return !lockFile.Exists();
96
/// <summary>Get the lock file corresponding to the given file.</summary>
97
/// <remarks>Get the lock file corresponding to the given file.</remarks>
98
/// <param name="file"></param>
99
/// <returns>lock file</returns>
100
internal static FilePath GetLockFile(FilePath file)
102
return new FilePath(file.GetParentFile(), file.GetName() + SUFFIX);
105
private sealed class _FilenameFilter_111 : FilenameFilter
107
public _FilenameFilter_111()
111
public bool Accept(FilePath dir, string name)
113
return !name.EndsWith(NGit.Storage.File.LockFile.SUFFIX);
117
/// <summary>Filter to skip over active lock files when listing a directory.</summary>
118
/// <remarks>Filter to skip over active lock files when listing a directory.</remarks>
119
internal static readonly FilenameFilter FILTER = new _FilenameFilter_111();
121
private readonly FilePath @ref;
123
private readonly FilePath lck;
125
private bool haveLck;
127
private FileOutputStream os;
129
private bool needSnapshot;
133
private FileSnapshot commitSnapshot;
135
private readonly FS fs;
137
/// <summary>Create a new lock for any file.</summary>
138
/// <remarks>Create a new lock for any file.</remarks>
139
/// <param name="f">the file that will be locked.</param>
140
/// <param name="fs">
141
/// the file system abstraction which will be necessary to perform
142
/// certain file system operations.
144
public LockFile(FilePath f, FS fs)
147
lck = GetLockFile(@ref);
151
/// <summary>Try to establish the lock.</summary>
152
/// <remarks>Try to establish the lock.</remarks>
154
/// true if the lock is now held by the caller; false if it is held
157
/// <exception cref="System.IO.IOException">
158
/// the temporary output file could not be created. The caller
159
/// does not hold the lock.
161
public virtual bool Lock()
163
FileUtils.Mkdirs(lck.GetParentFile(), true);
164
if (lck.CreateNewFile())
169
os = new FileOutputStream(lck);
171
catch (IOException ioe)
180
/// <summary>Try to establish the lock for appending.</summary>
181
/// <remarks>Try to establish the lock for appending.</remarks>
183
/// true if the lock is now held by the caller; false if it is held
186
/// <exception cref="System.IO.IOException">
187
/// the temporary output file could not be created. The caller
188
/// does not hold the lock.
190
public virtual bool LockForAppend()
196
CopyCurrentContent();
200
/// <summary>Copy the current file content into the temporary file.</summary>
202
/// Copy the current file content into the temporary file.
204
/// This method saves the current file content by inserting it into the
205
/// temporary file, so that the caller can safely append rather than replace
206
/// the primary file.
208
/// This method does nothing if the current file does not exist, or exists
211
/// <exception cref="System.IO.IOException">
212
/// the temporary file could not be written, or a read error
213
/// occurred while reading from the current file. The lock is
214
/// released before throwing the underlying IO exception to the
217
/// <exception cref="Sharpen.RuntimeException">
218
/// the temporary file could not be written. The lock is released
219
/// before throwing the underlying exception to the caller.
221
public virtual void CopyCurrentContent()
226
FileInputStream fis = new FileInputStream(@ref);
231
FileChannel @in = fis.GetChannel();
233
long cnt = @in.Size();
236
long r = os.GetChannel().TransferFrom(@in, pos, cnt);
243
byte[] buf = new byte[2048];
245
while ((r = fis.Read(buf)) >= 0)
256
catch (FileNotFoundException)
259
catch (IOException ioe)
261
// Don't worry about a file that doesn't exist yet, it
262
// conceptually has no current content to copy.
267
catch (RuntimeException ioe)
279
/// <summary>Write an ObjectId and LF to the temporary file.</summary>
280
/// <remarks>Write an ObjectId and LF to the temporary file.</remarks>
281
/// <param name="id">
282
/// the id to store in the file. The id will be written in hex,
283
/// followed by a sole LF.
285
/// <exception cref="System.IO.IOException">
286
/// the temporary file could not be written. The lock is released
287
/// before throwing the underlying IO exception to the caller.
289
/// <exception cref="Sharpen.RuntimeException">
290
/// the temporary file could not be written. The lock is released
291
/// before throwing the underlying exception to the caller.
293
public virtual void Write(ObjectId id)
295
byte[] buf = new byte[Constants.OBJECT_ID_STRING_LENGTH + 1];
297
buf[Constants.OBJECT_ID_STRING_LENGTH] = (byte)('\n');
301
/// <summary>Write arbitrary data to the temporary file.</summary>
302
/// <remarks>Write arbitrary data to the temporary file.</remarks>
303
/// <param name="content">
304
/// the bytes to store in the temporary file. No additional bytes
305
/// are added, so if the file must end with an LF it must appear
306
/// at the end of the byte array.
308
/// <exception cref="System.IO.IOException">
309
/// the temporary file could not be written. The lock is released
310
/// before throwing the underlying IO exception to the caller.
312
/// <exception cref="Sharpen.RuntimeException">
313
/// the temporary file could not be written. The lock is released
314
/// before throwing the underlying exception to the caller.
316
public virtual void Write(byte[] content)
323
FileChannel fc = os.GetChannel();
324
ByteBuffer buf = ByteBuffer.Wrap(content);
325
while (0 < buf.Remaining())
338
catch (IOException ioe)
343
catch (RuntimeException ioe)
355
/// <summary>Obtain the direct output stream for this lock.</summary>
357
/// Obtain the direct output stream for this lock.
359
/// The stream may only be accessed once, and only after
360
/// <see cref="Lock()">Lock()</see>
362
/// been successfully invoked and returned true. Callers must close the
363
/// stream prior to calling
364
/// <see cref="Commit()">Commit()</see>
365
/// to commit the change.
367
/// <returns>a stream to write to the new file. The stream is unbuffered.</returns>
368
public virtual OutputStream GetOutputStream()
374
@out = Channels.NewOutputStream(os.GetChannel());
380
return new _OutputStream_323(this, @out);
383
private sealed class _OutputStream_323 : OutputStream
385
public _OutputStream_323(LockFile _enclosing, OutputStream @out)
387
this._enclosing = _enclosing;
391
/// <exception cref="System.IO.IOException"></exception>
392
public override void Write(byte[] b, int o, int n)
397
/// <exception cref="System.IO.IOException"></exception>
398
public override void Write(byte[] b)
403
/// <exception cref="System.IO.IOException"></exception>
404
public override void Write(int b)
409
/// <exception cref="System.IO.IOException"></exception>
410
public override void Close()
414
if (this._enclosing.fsync)
416
this._enclosing.os.GetChannel().Force(true);
419
this._enclosing.os = null;
421
catch (IOException ioe)
423
this._enclosing.Unlock();
426
catch (RuntimeException ioe)
428
this._enclosing.Unlock();
433
this._enclosing.Unlock();
438
private readonly LockFile _enclosing;
440
private readonly OutputStream @out;
443
private void RequireLock()
448
throw new InvalidOperationException(MessageFormat.Format(JGitText.Get().lockOnNotHeld
455
/// <see cref="Commit()">Commit()</see>
456
/// remember modification time.
458
/// This is an alias for
459
/// <code>setNeedSnapshot(true)</code>
462
/// <param name="on">true if the commit method must remember the modification time.</param>
463
public virtual void SetNeedStatInformation(bool on)
470
/// <see cref="Commit()">Commit()</see>
472
/// <see cref="FileSnapshot">FileSnapshot</see>
475
/// <param name="on">true if the commit method must remember the FileSnapshot.</param>
476
public virtual void SetNeedSnapshot(bool on)
483
/// <see cref="Commit()">Commit()</see>
484
/// force dirty data to the drive.
486
/// <param name="on">true if dirty data should be forced to the drive.</param>
487
public virtual void SetFSync(bool on)
492
/// <summary>Wait until the lock file information differs from the old file.</summary>
494
/// Wait until the lock file information differs from the old file.
496
/// This method tests the last modification date. If both are the same, this
497
/// method sleeps until it can force the new lock file's modification date to
498
/// be later than the target file.
500
/// <exception cref="System.Exception">
501
/// the thread was interrupted before the last modified date of
502
/// the lock file was different from the last modified date of
505
public virtual void WaitForStatChange()
507
FileSnapshot o = FileSnapshot.Save(@ref);
508
FileSnapshot n = FileSnapshot.Save(lck);
511
Sharpen.Thread.Sleep(25);
512
lck.SetLastModified(Runtime.CurrentTimeMillis());
513
n = FileSnapshot.Save(lck);
517
/// <summary>Commit this change and release the lock.</summary>
519
/// Commit this change and release the lock.
521
/// If this method fails (returns false) the lock is still released.
524
/// true if the commit was successful and the file contains the new
525
/// data; false if the commit failed and the file remains with the
528
/// <exception cref="System.InvalidOperationException">the lock is not held.</exception>
529
public virtual bool Commit()
534
throw new InvalidOperationException(MessageFormat.Format(JGitText.Get().lockOnNotClosed
537
SaveStatInformation();
538
if (lck.RenameTo(@ref))
542
if (!@ref.Exists() || DeleteRef())
553
private bool DeleteRef()
555
if (!fs.RetryFailedLockFileCommit())
557
return @ref.Delete();
559
// File deletion fails on windows if another thread is
560
// concurrently reading the same file. So try a few times.
562
for (int attempts = 0; attempts < 10; attempts++)
570
Sharpen.Thread.Sleep(100);
580
private bool RenameLock()
582
if (!fs.RetryFailedLockFileCommit())
584
return lck.RenameTo(@ref);
586
// File renaming fails on windows if another thread is
587
// concurrently reading the same file. So try a few times.
589
for (int attempts = 0; attempts < 10; attempts++)
591
if (lck.RenameTo(@ref))
597
Sharpen.Thread.Sleep(100);
607
private void SaveStatInformation()
611
commitSnapshot = FileSnapshot.Save(lck);
615
/// <summary>Get the modification time of the output file when it was committed.</summary>
616
/// <remarks>Get the modification time of the output file when it was committed.</remarks>
617
/// <returns>modification time of the lock file right before we committed it.</returns>
618
public virtual long GetCommitLastModified()
620
return commitSnapshot.LastModified();
625
/// <see cref="FileSnapshot">FileSnapshot</see>
626
/// just before commit.
628
public virtual FileSnapshot GetCommitSnapshot()
630
return commitSnapshot;
633
/// <summary>Unlock this file and abort this change.</summary>
635
/// Unlock this file and abort this change.
637
/// The temporary file (if created) is deleted before returning.
639
public virtual void Unlock()
658
FileUtils.Delete(lck, FileUtils.RETRY);
666
// couldn't delete the file even after retry.
667
public override string ToString()
669
return "LockFile[" + lck + ", haveLck=" + haveLck + "]";