安全流更新文件

时间:2008-11-27 21:22:08

标签: .net windows

我们通过将新记录写入临时文件,然后用临时文件替换旧文件来执行大型文本文件的更新。一个严重缩写的版本:

var tpath = Path.GetTempFileName();
try
{
    using (var sf = new StreamReader(sourcepath))
    using (var tf = new StreamWriter(tpath))
    {
        string line;
        while ((line = sf.ReadLine()) != null)
            tf.WriteLine(UpdateLine(line));
    }

    File.Delete(sourcepath);
    File.Move(tpath, sourcepath);
}
catch
{
    File.Delete(tpath);
    throw;
}

如果有异常抛出异常(找不到文件,没有权限),原始文件保持不变,这就是我们想要的。

但是,代码存在以下问题:

  1. 是否存在Delete有效但Move失败的真实情况?这将删除原始数据和更新数据。这很糟糕。

  2. 最常见的失败是源文件从另一个应用程序打开,Delete失败。这意味着将丢弃所有更新工作。有没有办法查看源文件在开始时是否可删除,如果没有则放弃更新?

  3. 我们让用户将Windows资源管理器摘要属性(如标题或注释)放在文件上。删除文件时会丢弃这些内容。有没有办法将旧文件的Summary属性复制到新文件?我们应该这样做吗?

8 个答案:

答案 0 :(得分:5)

避免“删除然后移动失败问题”的正常方法是:

  • 写入file.new
  • 将file.current移动到file.old
  • 将file.new移至file.current
  • 删除file.new

然后当你来阅读时,如果缺少file.current,则使用file.new,如果你看到则删除file.old。

检查文件是否可用:尝试将其打开以进行写入,但追加到最后。当然,你需要在移动它之前关闭手柄,而在其他人之间可以打开它 - 但它至少会是合理的优化。

不确定复制摘要等,我很害怕。

答案 1 :(得分:2)

为什么不先尝试检查FileAttributes?

尝试这样的事情:

//If File is readonly
if ( (file.Attribute & System.FileAttributes.ReadOnly) == System.FileAttributes.ReadOnly ) 
        //Don't delete. 

也可以尝试使用.OpenWrite()。如果您可以打开要写入的文件,则不会访问该文件,并且当前未使用该文件。如果文件当前处于未打开状态,则只能打开文件进行写入。我不推荐这个,但它可以帮到你。

  FileStream fs = File.OpenWrite(file);
  fs.Close();
  return false; 

您还可以使用FileLock检查方法。像这样:

protected virtual bool IsFileLocked(FileInfo file)
{
    try
    {
        using (file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
           return false;
        }
    }

    catch (IOException)
    {
        return true;
    }

}

您可能还想检查FileIOPermission.Write。这允许查看文件是否可写(并且能够删除)。

fileIOPerm = New FileIOPermission(FileIOPermissionAccess.Write, FileSpec);
fileIOPerm.Demand();

关于原帖中的问题#3 ...您始终可以使用File.Copy(path1,path2,true)将文件移动到临时文件夹。您可能需要考虑使用临时文件夹并为文件操作编写更好的逻辑。

如果你决定使用临时文件夹或临时文件/中间文件,那么你也可以修复你的问题#2。尝试先移动文件。

答案 2 :(得分:1)

一些“肮脏”的伎俩。

  1. 首先不要删除原始文件,先将其移动到另一个位置(临时路径),然后如果移动更新的文件成功则删除旧文件。如果更新失败,您可以在原处恢复文件。

  2. 我认为本文将帮助您MSDN

  3. 如果用户需要“标题”和“评论”,您应该保留它们。我从来没有尝试过将它们从一个文件复制到另一个文件,所以我不知道如何帮助你。

答案 3 :(得分:1)

Windows Vista或更高版本上的

Transactional NTFS可能对您的方案有用。

答案 4 :(得分:1)

正如已经提到的,你真的应该调查ReplaceFile,它旨在帮助你正在做的事情。 .NET函数只是Win32函数的一个包装器,人们可能希望原子性问题得到解决。

答案 5 :(得分:1)

很多好的建议。我能够用以下方法解决问题:

var sInfo = new FileInfo(sourcePath);
if (sInfo.IsReadOnly)
    throw new IOException("File '" + sInfo.FullName + "' is read-only.");

var tPath = Path.GetTempFileName();
try
{
    // This throws if sourcePath does not exist, is opened, or is not readable.
    using (var sf = sInfo.OpenText())
    using (var tf = new StreamWriter(tPath))
    {
        string line;
        while ((line = sf.ReadLine()) != null)
            tf.WriteLine(UpdateLine(line));
    }

    string backupPath = sInfo.FullName + ".bak";
    if (File.Exists(backupPath))
        File.Delete(backupPath);

    File.Move(tPath, backupPath);
    tPath = backupPath;
    File.Replace(tPath, sInfo.FullName, null);
}
catch (Exception ex)
{
    File.Delete(tPath);
    throw new IOException("File '" + sInfo.FullName + "' could not be overwritten.", ex);
}
如果源文件是打开的或不可读的,则会抛出

OpenText,并且未完成更新。如果有任何抛出,原始文件保持不变。 Replace将旧文件的“摘要”属性复制到新文件中。即使源文件与临时文件夹位于不同的卷上,也可以使用此功能。

答案 6 :(得分:0)

我发现将这个模式包装在它自己的类中很有用。

class Program {
    static void Main( string[] args ) {
        using( var ft = new FileTransaction( @"C:\MyDir\MyFile.txt" ) )
        using( var sw = new StreamWriter( ft.TempPath ) ) {
            sw.WriteLine( "Hello" );
            ft.Commit();
        }
    }
}

public class FileTransaction :IDisposable {
    public string TempPath { get; private set; }
    private readonly string filePath;

    public FileTransaction( string filePath ) {
        this.filePath = filePath;
        this.TempPath = Path.GetTempFileName();
    }

    public void Dispose() {
        if( TempPath != null ) {
            try {
                File.Delete( TempPath );
            }
            catch { }
        }
    }

    public void Commit() {
        try {
            var oldPath = filePath + ".old";
            File.Move( filePath, oldPath );
        }
        catch {}

        File.Move( TempPath, filePath );

        TempPath = null;
    }
}

答案 7 :(得分:0)

此代码段显示了一种获取文件独占访问权限的技术(在本例中为读取):

// Try to open a file exclusively
FileInfo fi = new FileInfo(fullFilePath);

int attempts = maxAttempts;
do
{
    try
    {
        // Try to open for reading with exclusive access...
        fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.None);
    }
    // Ignore any errors... 
    catch { }

    if (fs != null)
    {
        break;
    }
    else
    {
        Thread.Sleep(100);
    }
}
while (--attempts > 0);

// Did we manage to open file exclusively?
if (fs != null)
{
    // use open file....

}