2
votes

strange behaviour in our production environment, these days. I have the following:

try {
    var sourcePath = ... // my source path
    var destinationPath = ... // guess what? You're right, my destination path

    File.Copy(sourcePath, destinationPath);
    Log.Debug(string.Format("Image {0} copied successfully", imagename));
}
catch(Exception e) {
    // exception handling
}

Both source and destination path are on a network share, a folder on an other (virtual) machine with a large number of files (> 500k). From the last 2 days, the code above runs, logs the last line (the one stating that image have been copied), but if I check in the destination folder, the supposed destination file does not exist.

I thought that for any I/O error File.Copy would raise an exception, so this thing is driving me mad. Please note that other code parts that write files in that folder are working correctly. Also, note that all files names are unique (business code not included for brevity is making sure of that), and I think an exception would be thrown or the file would be at least overwritten, in that case.

Has anyone faced the same problem? Possible causes? Any solution?

EDIT 2016-07-01 15:12 (GMT+0200)

Ok, folks, apparently files aren't being deleted at all... simply for apparently no reason at all, after they are copied they're left open in read+write mode from the client connected user. I found this trying running the reader application on my computer, in debug mode, and trying to open one of the files i knew that were copied recently. I got an exception stating that the file was opened by someone else, and that seemed weird to me. Opening Computer Management in the remote server (the one which stores the files), then going to Shared Folders > Open Files, I found that the file was left open in read+write mode from the impersonated user that the web application that copies the files is impersonating to do that job. Also a whole bunch of other files where in the same conditions, and many others where open in read mode. I found also in Shared Folders > Sessions, an astronomical long list of session of the impersonated user, all with long idle time. Since impersonation is used only to copy the files, and then is disposed, I shouldn't expect that, right?

I think maybe there is a problem in the way we impersonate the user during file copy, linked to the large number of files in the destination folder. I'll check that.

END EDIT

Thanks,

Claudio Valerio

1
Maybe someone/something is deleting the file after they get copied? - Matteo Umili
Hi Matteo, thanks for commenting. Sadly no, there is not any line of code that deletes from that specific folder, and no one can access via Windows Explorer but me and my coworkers (no FTP access). - Claudio Valerio
Maybe your destinationPath isn't what you think it is in certain cases. Perhaps you should log that too. - Charles Mager
Hi Charles, The destination path is always a specific, existing folder (100% sure) combined with an unique file name. Besides, this code runs on a small bunch of image files each time, and in a single bunch one or two files are copied correcly and exist. - Claudio Valerio
Also source file exists, obviously (otherwise i should get a FileNotFoundException, as per documentation). - Claudio Valerio

1 Answers

0
votes

Found a solution, I think. My problem was the code used for impersonating the user with write permissions on the destination folder. (In my defence, all this project have been inherited from previous software company, and it's pretty massive, so keeping an eye on everything isn't that easy)

The impersonation process was wrapped in a class implementing IDisposable

public class Impersonator :
    IDisposable
{
    public Impersonator()
    {
        string userName = // get username from config
        string password = // get password from config
        string domainName = // get domain from config
        ImpersonateValidUser(userName, domainName, password);
    }

    public void Dispose()
    {
        UndoImpersonation();
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int DuplicateToken(
        IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern bool CloseHandle(
        IntPtr handle);

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0;

    private void ImpersonateValidUser(
        string userName,
        string domain,
        string password)
    {
        WindowsIdentity tempWindowsIdentity = null;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if (RevertToSelf())
            {
                if (LogonUser(
                    userName,
                    domain,
                    password,
                    LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT,
                    ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                    }
                    else
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            else
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                CloseHandle(token);
            }
            if (tokenDuplicate != IntPtr.Zero)
            {
                CloseHandle(tokenDuplicate);
            }
        }
    }

    private void UndoImpersonation()
    {
        if (impersonationContext != null)
        {
            impersonationContext.Undo();
        }
    }

    private WindowsImpersonationContext impersonationContext = null;

}

This class is used this way:

using(new Impersonator())
{
    // do stuff with files in here
}

My suspect was that closing the handlers of the impersonated user, somehow, it could break something in the way that windows handles file open by the impersonated user through a network share, as in my case, leaving shared files open in read+write mode, preventing any other process/user to open them.

I modified the Impersonator class as follows:

public class Impersonator :
    IDisposable
{
    public Impersonator()
    {
        string userName = // get username from config
        string password = // get password from config
        string domainName = // get domain from config
        ImpersonateValidUser(userName, domainName, password);
    }

    public void Dispose()
    {
        UndoImpersonation();
        impersonationContext.Dispose();
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int DuplicateToken(
        IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern bool CloseHandle(
        IntPtr handle);

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0;

    private void ImpersonateValidUser(
        string userName,
        string domain,
        string password)
    {
        WindowsIdentity tempWindowsIdentity = null;
        token = IntPtr.Zero;
        tokenDuplicate = IntPtr.Zero;

        try
        {
            if (RevertToSelf())
            {
                if (LogonUser(
                    userName,
                    domain,
                    password,
                    LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT,
                    ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                    }
                    else
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            else
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        finally
        {
        }
    }

    private void UndoImpersonation()
    {
        try
        {
            if (impersonationContext != null)
            {
                impersonationContext.Undo();
            }
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                CloseHandle(token);
            }
            if (tokenDuplicate != IntPtr.Zero)
            {
                CloseHandle(tokenDuplicate);
            }
        }
    }

    private WindowsImpersonationContext impersonationContext = null;
    private IntPtr token;
    private IntPtr tokenDuplicate;

}

Basically I moved the handlers closing in the UndoImpersonation method. Also I had a doubt about leaving the impersonationContext not explicitly disposed, si I disposed it in the Dispose method of the Impersonator class.

Since I put in production this update, I hadn't any other issue with this code, and not any other shared file left open in read+write mode on the destination server. Maybe not the optimal solution (I still have a whole bunch of sessions in Computer Management > Shared Folders > Sessions, but this seems not harming the system, for now.

If anyone have some additional comment, suggestion or depth study about this situation, I will be please to read.

Thanks,

Claudio