0

I would like to know if there is a way to create a file and set the Last Write Time (and other timestamp information) without allowing another process to acquire a lock to the file between these two operations.

The reason I want to do this is to fix an issue where antivirus acquires a lock to the file just after it has been created and still has the lock by the time the file attributes are being attempted to be set. Specifically the code I am working with is SevenZipSharp (no longer maintained as far as I can see).

Code that reproduces this issue is:

var filePath = "test.txt";
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
    var bytes = Encoding.ASCII.GetBytes("Hello fail.");
    fileStream.Write(bytes, 0, bytes.Length);
    var fileInfo = new FileInfo(filePath);
    fileInfo.CreationTime = DateTime.Now;
}

This produces the following exception when executing the last statement: System.IO.IOException "The process cannot access the file 'c:\test.txt' because it is being used by another process."

I am considering implementing the setting of the time attributes with a retry mechanism, but wondered if there was a more elegant solution.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
flange
  • 45
  • 4
  • Why *"without allowing another process to acquire a lock to the file between"* ? Why you can't change time later? – Sinatr Jul 25 '16 at 14:32
  • sounds like Anti-viral software is attacking it and preventing modification? – Blue Eyed Behemoth Jul 25 '16 at 14:33
  • You can change CreationTime after fileStream.Dispose(). But not in between. –  Jul 25 '16 at 14:34
  • I can set it later (this is what the current SevenZipSharp code does), but it sometimes fails because the file is locked by antivirus. – flange Jul 25 '16 at 14:34
  • `CreationTime` doesn't change when you copy file. Theoretically (if antivirus is the issue) you can create file somewhere else, where it doesn't looks for and then just copy it from there. Or well simply add an exception to anti-virus. – Sinatr Jul 25 '16 at 14:38
  • I am assuming there is nowhere safe from antivirus. An AV exception works but is not an option as it is production software. – flange Jul 25 '16 at 14:43
  • You could always try accessing the underlying handle from the `FileStream` object and use [`SetFileInformationByHandle`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365539(v=vs.85).aspx) by P/Invoke. – Damien_The_Unbeliever Jul 25 '16 at 14:43

3 Answers3

2

As @Damien_The_Unbeliever mentioned, you need to get the file handle. Try this.

class Program {
    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool SetFileTime(SafeFileHandle hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime);

    static void Main(string[] args) {
        var filePath = "test.txt";
        long when = DateTime.Now.AddDays(10).ToFileTime();
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite)) {
            if (!SetFileTime(fileStream.SafeFileHandle, ref when, ref when, ref when)) {
                throw new Win32Exception();
            }
            var bytes = Encoding.ASCII.GetBytes("Hello fail.");
            fileStream.Write(bytes, 0, bytes.Length);
        }
    }
}
aquinas
  • 23,318
  • 5
  • 58
  • 81
0

The problem is you're trying to access a file that's being used by your using statement. That's why you're getting the error. You need to finish your using statement, then you can assign the creation time on the file.

If the file is getting locked by other software, your best bet is to create a while loop to wait for the file.

Try the following:

var filePath = "test.txt";
DateTime creationTime;
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
    var bytes = Encoding.ASCII.GetBytes("Hello fail.");
    fileStream.Write(bytes, 0, bytes.Length);
    creationTime = DateTime.Now;
}
int numTries = 0;
while (true)
{
    ++numTries;
    try
    {
        // Attempt to open the file exclusively.
        using (var fileInfo = new FileInfo(filePath))
        {
             // If we got this far the file is ready
            fileInfo.CreationTime = creationTime;
            break;
        }
    }
    catch (Exception ex)
    {
        if (numTries > 10)
        {
            // Get out of it
            Console.WriteLine("This joker still has your file, I'm out.");
            break;
        }

        // Wait for the lock to be released
        System.Threading.Thread.Sleep(500);
    }
}
Blue Eyed Behemoth
  • 3,692
  • 1
  • 17
  • 27
  • This stops the exception, but doesn't solve the problem of another process acquiring a lock between creation and setting the attributes. As I said in the original post, I am considering a retry, but would like to know if another approach that doesn't free up the file lock is possible. – flange Jul 25 '16 at 14:42
  • Yeah, I understand what your goal is, but you have to release the file before you can reopen it using different libraries. – Blue Eyed Behemoth Jul 25 '16 at 14:46
  • You should check the exception is due to the file being locked: http://stackoverflow.com/a/11060322 – Jeremy Thompson Jul 25 '16 at 14:50
0

No, there is nothing you can do to prevent another process from locking a file.

You could try writing a temporary version of the file and then execute Robocopy using Process.Start to copy the file while setting the attributes. The original copy of the file becomes irrelevant - you could clean it up later.

That will depend on whether the method Robocopy uses to copy the file and set attributes is atomic.

Someone has already written a wrapper to avoid the ugliness of calling a command-line app from your .NET code - It's called RoboSharp and has a nuget package.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • 1
    If Robocopy was atomic, then atomicity is possible. If atomicity is possible, then presumably there is a way to employ this atomicity in my code. – flange Jul 25 '16 at 14:48
  • Yes, it would be using some lower-level APIs. I wonder if there's a 3rd-party library that has that packaged up already. Now I'm curious. I'll take a look... yes, there is a nuget package that wraps RoboCopy. – Scott Hannen Jul 25 '16 at 14:57
  • But if the answer from @aquinas works then this solution is really clunky and questionable by comparison. – Scott Hannen Jul 25 '16 at 15:02