【问题标题】:Windows temporary files behaviour - are they deleted by the system?Windows 临时文件行为 - 它们是否被系统删除?
【发布时间】:2010-09-24 08:30:37
【问题描述】:

使用 .net 框架,您可以选择创建临时文件

Path.GetTempFileName(); 

MSDN 没有告诉我们临时文件会发生什么。我记得在某处读到它们在重新启动时被操作系统删除。这是真的吗?

如果文件没有被操作系统删除,为什么它们被称为临时文件?它们是普通目录中的普通文件。

【问题讨论】:

标签: .net windows file


【解决方案1】:

简短的回答:它们不会被删除。

长答案: 托管的Path.GetTempFileName() 方法调用本机Win32API 的GetTempFileName() 方法,如下所示:

//actual .NET 2.0 decompiled code 
// .NET Reflector rocks for looking at plumbing
public static string GetTempFileName()
{
    string tempPath = GetTempPath();
    new FileIOPermission(FileIOPermissionAccess.Write, tempPath).Demand();
    StringBuilder tmpFileName = new StringBuilder(260);
    if (Win32Native.GetTempFileName(tempPath, "tmp", 0, tmpFileName) == 0)
    {
        __Error.WinIOError();
    }
    return tmpFileName.ToString();
}

本机方法的文档说明:

名称已由该函数创建的临时文件不会自动删除。要删除这些文件,请调用 DeleteFile。

我发现了一篇很棒的文章,名为 "Those pesky temp files"(2007 年 10 月存档),它从基础开始,涉及处理临时文件的一些不太明显的问题,例如:

  • 如何确保文件被删除(即使应用崩溃!提示:FileOption.DeleteOnClose 并让内核处理)
  • 如何为文件获取正确的缓存策略,以提高性能(提示:FileAttributes.Temporary
  • 如何确保文件内容安全,因为:
    • 托管方法比非托​​管方法更容易预测文件名
    • 临时文件被创建,然后关闭,然后你得到它的路径(只是为了再次打开它),从而为恶意代码/用户劫持文件留下了一个小窗口.

文章中的 C# 代码:

using System;
using System.IO;
using System.Security.Permissions;
using System.Security.Principal;
using System.Security.AccessControl;

public static class PathUtility
{
    private const int defaultBufferSize = 0x1000; // 4KB

#region GetSecureDeleteOnCloseTempFileStream

    /// <summary>
    /// Creates a unique, randomly named, secure, zero-byte temporary file on disk, which is automatically deleted when it is no longer in use. Returns the opened file stream.
    /// </summary>
    /// <remarks>
    /// <para>The generated file name is a cryptographically strong, random string. The file name is guaranteed to be unique to the system's temporary folder.</para>
    /// <para>The <see cref="GetSecureDeleteOnCloseTempFileStream"/> method will raise an <see cref="IOException"/> if no unique temporary file name is available. Although this is possible, it is highly improbable. To resolve this error, delete all uneeded temporary files.</para>
    /// <para>The file is created as a zero-byte file in the system's temporary folder.</para>
    /// <para>The file owner is set to the current user. The file security permissions grant full control to the current user only.</para>
    /// <para>The file sharing is set to none.</para>
    /// <para>The file is marked as a temporary file. File systems avoid writing data back to mass storage if sufficient cache memory is available, because an application deletes a temporary file after a handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.</para>
    /// <para>The system deletes the file immediately after it is closed or the <see cref="FileStream"/> is finalized.</para>
    /// </remarks>
    /// <returns>The opened <see cref="FileStream"/> object.</returns>
    public static FileStream GetSecureDeleteOnCloseTempFileStream()
    {    
        return GetSecureDeleteOnCloseTempFileStream(defaultBufferSize, FileOptions.DeleteOnClose);    
    }

    /// <summary>
    /// Creates a unique, randomly named, secure, zero-byte temporary file on disk, which is automatically deleted when it is no longer in use. Returns the opened file stream with the specified buffer size.
    /// </summary>
    /// <remarks>
    /// <para>The generated file name is a cryptographically strong, random string. The file name is guaranteed to be unique to the system's temporary folder.</para>
    /// <para>The <see cref="GetSecureDeleteOnCloseTempFileStream"/> method will raise an <see cref="IOException"/> if no unique temporary file name is available. Although this is possible, it is highly improbable. To resolve this error, delete all uneeded temporary files.</para>
    /// <para>The file is created as a zero-byte file in the system's temporary folder.</para>
    /// <para>The file owner is set to the current user. The file security permissions grant full control to the current user only.</para>
    /// <para>The file sharing is set to none.</para>
    /// <para>The file is marked as a temporary file. File systems avoid writing data back to mass storage if sufficient cache memory is available, because an application deletes a temporary file after a handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.</para>
    /// <para>The system deletes the file immediately after it is closed or the <see cref="FileStream"/> is finalized.</para>
    /// </remarks>
    /// <param name="bufferSize">A positive <see cref="Int32"/> value greater than 0 indicating the buffer size.</param>
    /// <returns>The opened <see cref="FileStream"/> object.</returns>
    public static FileStream GetSecureDeleteOnCloseTempFileStream(int bufferSize)
    {
        return GetSecureDeleteOnCloseTempFileStream(bufferSize, FileOptions.DeleteOnClose);
    }

    /// <summary>
    /// Creates a unique, randomly named, secure, zero-byte temporary file on disk, which is automatically deleted when it is no longer in use. Returns the opened file stream with the specified buffer size and file options.
    /// </summary>  
    /// <remarks>
    /// <para>The generated file name is a cryptographically strong, random string. The file name is guaranteed to be unique to the system's temporary folder.</para>
    /// <para>The <see cref="GetSecureDeleteOnCloseTempFileStream"/> method will raise an <see cref="IOException"/> if no unique temporary file name is available. Although this is possible, it is highly improbable. To resolve this error, delete all uneeded temporary files.</para>
    /// <para>The file is created as a zero-byte file in the system's temporary folder.</para>
    /// <para>The file owner is set to the current user. The file security permissions grant full control to the current user only.</para>
    /// <para>The file sharing is set to none.</para>
    /// <para>The file is marked as a temporary file. File systems avoid writing data back to mass storage if sufficient cache memory is available, because an application deletes a temporary file after a handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.</para>
    /// <para>The system deletes the file immediately after it is closed or the <see cref="FileStream"/> is finalized.</para>
    /// <para>Use the <paramref name="options"/> parameter to specify additional file options. You can specify <see cref="FileOptions.Encrypted"/> to encrypt the file contents using the current user account. Specify <see cref="FileOptions.Asynchronous"/> to enable overlapped I/O when using asynchronous reads and writes.</para>
    /// </remarks>
    /// <param name="bufferSize">A positive <see cref="Int32"/> value greater than 0 indicating the buffer size.</param>
    /// <param name="options">A <see cref="FileOptions"/> value that specifies additional file options.</param>
    /// <returns>The opened <see cref="FileStream"/> object.</returns>
    public static FileStream GetSecureDeleteOnCloseTempFileStream(int bufferSize, FileOptions options)
    {    
        FileStream fs = GetSecureFileStream(Path.GetTempPath(), bufferSize, options | FileOptions.DeleteOnClose);

        File.SetAttributes(fs.Name, File.GetAttributes(fs.Name) | FileAttributes.Temporary);

        return fs;    
    }

#endregion

#region GetSecureTempFileStream

    public static FileStream GetSecureTempFileStream()
    {    
        return GetSecureTempFileStream(defaultBufferSize, FileOptions.None);    
    }

    public static FileStream GetSecureTempFileStream(int bufferSize)
    {
        return GetSecureTempFileStream(bufferSize, FileOptions.None);
    }

    public static FileStream GetSecureTempFileStream(int bufferSize, FileOptions options)
    {
        FileStream fs = GetSecureFileStream(Path.GetTempPath(), bufferSize, options);

        File.SetAttributes(fs.Name, File.GetAttributes(fs.Name) | FileAttributes.NotContentIndexed | FileAttributes.Temporary);

        return fs;
    }

    #endregion

#region GetSecureTempFileName

    public static string GetSecureTempFileName()
    {    
        return GetSecureTempFileName(false);    
    }

    public static string GetSecureTempFileName(bool encrypted)
    {    
        using (FileStream fs = GetSecureFileStream(Path.GetTempPath(), defaultBufferSize, encrypted ? FileOptions.Encrypted : FileOptions.None))
        {    
            File.SetAttributes(fs.Name, File.GetAttributes(fs.Name) | FileAttributes.NotContentIndexed | FileAttributes.Temporary);

            return fs.Name;    
        }

    }

#endregion

#region GetSecureFileName

    public static string GetSecureFileName(string path)
    {    
        return GetSecureFileName(path, false);    
    }

    public static string GetSecureFileName(string path, bool encrypted)
    {    
        using (FileStream fs = GetSecureFileStream(path, defaultBufferSize, encrypted ? FileOptions.Encrypted : FileOptions.None))
        {    
            return fs.Name;    
        }    
    }

#endregion

#region GetSecureFileStream

    public static FileStream GetSecureFileStream(string path)
    {    
        return GetSecureFileStream(path, defaultBufferSize, FileOptions.None);    
    }

    public static FileStream GetSecureFileStream(string path, int bufferSize)
    {
        return GetSecureFileStream(path, bufferSize, FileOptions.None);
    }

    public static FileStream GetSecureFileStream(string path, int bufferSize, FileOptions options)
    {    
        if (path == null)
            throw new ArgumentNullException("path");

        if (bufferSize <= 0)
            throw new ArgumentOutOfRangeException("bufferSize");

        if ((options & ~(FileOptions.Asynchronous | FileOptions.DeleteOnClose | FileOptions.Encrypted | FileOptions.RandomAccess | FileOptions.SequentialScan | FileOptions.WriteThrough)) != FileOptions.None)
            throw new ArgumentOutOfRangeException("options");

        new FileIOPermission(FileIOPermissionAccess.Write, path).Demand();

        SecurityIdentifier user = WindowsIdentity.GetCurrent().User;

        FileSecurity fileSecurity = new FileSecurity();

        fileSecurity.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.FullControl, AccessControlType.Allow));

        fileSecurity.SetAccessRuleProtection(true, false);

        fileSecurity.SetOwner(user);

        // Attempt to create a unique file three times before giving up.
        // It is highly improbable that there will ever be a name clash,
        // therefore we do not check to see if the file first exists.

        for (int attempt = 0; attempt < 3; attempt++)
        {    
            try
            {    
                return new FileStream(Path.Combine(path, Path.GetRandomFileName()),
                                        FileMode.CreateNew, FileSystemRights.FullControl,
                                        FileShare.None, bufferSize, options, fileSecurity);
            }

            catch (IOException)
            {
                if (attempt == 2)
                    throw;
            }

        }

        // This code can never be reached.
        // The compiler thinks otherwise.
        throw new IOException();

    }

#endregion

}

【讨论】:

    【解决方案2】:

    我在互联网上读到很多次人们不想使用Path.GetTempFileName,因为他们说它可能会返回一个已经存在的文件,要解决这个问题,你可以根据 GUID 创建一个文件名。

    这个函数解决了这个问题:迭代直到找到一个不存在的具有特定扩展名的文件名。

    VB.net

    Public Shared Function GetTempFileName(ByVal extensionWithDot As String) As String
        Dim tempFileName As String
        Do
            tempFileName = System.IO.Path.GetTempFileName
            If extensionWithDot IsNot Nothing Then
                tempFileName = tempFileName.Replace(System.IO.Path.GetExtension(tempFileName), extensionWithDot)
            End If
        Loop While System.IO.File.Exists(tempFileName)
        Return tempFileName
    End Function
    

    C#:

    public static string GetTempFileName(string extensionWithDot)
    {
        string tempFileName = null;
        do {
            tempFileName = System.IO.Path.GetTempFileName;
            if (extensionWithDot != null) {
                tempFileName = tempFileName.Replace(System.IO.Path.GetExtension(tempFileName), extensionWithDot);
            }
        } while (System.IO.File.Exists(tempFileName));
        return tempFileName;
    }
    

    注意:我使用参数 extensionWithDot 因为 System.IO.Path.GetExtension 以点返回。

    【讨论】:

    • 对于将来遇到此问题的任何人,此答案解决了一个不存在的问题 - GetTempFileName 保证返回一个唯一命名的文件。您不必相信我的话,文档中对此非常清楚。此外,这会在临时目录中留下 0 字节的临时文件,这是不好的。
    【解决方案3】:

    它们被称为临时文件,因为在大多数情况下,用户可以假设她可以安全地清理临时目录中的混乱......如果不是,通常这些文件无论如何都会被锁定。

    一般来说,这些文件应该是短暂的:创建它们,根据需要使用它们,当场删除它们。更糟糕的是,在退出应用程序时删除它们。

    有时,你不能,例如。存档管理器或 VCS 允许使用编辑器(或差异查看器等)查看文件,但在编辑器之前关闭(或无法监视生成的进程...)。

    【讨论】:

      【解决方案4】:

      根据我的 %tmp% 中的 March 文件,我不会说。

      关于为什么它们被称为临时的 - 因为这是它们的预期用途。它们不是系统文件;它们不是应用程序文件,也不是用户文档……它们的存在只是为了允许应用程序进行临时处理(可能处理大量数据),或者经常通过 IPC 将数据传递给另一个进程。因此它们确实是暂时的。

      您的目标应该是删除您创建的任何临时文件,但不能承受致命的“杀死”等。我经常为此使用“使用”——我创建了一个包装类——即

      sealed class TempFile : IDisposable { // formatted for space
          string path;
          public string Path {
              get {
                  if(path==null) throw new ObjectDisposedException(GetType().Name);
                  return path;
              }
          }
          public TempFile() : this(System.IO.Path.GetTempFileName()) { }
      
          public TempFile(string path) {
              if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
              this.path = path;
          }
      
          private void Dispose(bool disposing) {
              if (path != null) {
                  try {
                      File.Delete(path);
                  } catch { } // best endeavours...
                  path = null;
              }
          }
          public void Dispose() {
              GC.SuppressFinalize(this);
              Dispose(true);
          }
          ~TempFile() {
              Dispose(false);
          }
      }
      

      【讨论】:

      • 我在这里吹毛求疵,但不应该将File.Delete 移出空检查并在空检查之前将空检查本身替换为disposing 检查吗? try { File.Delete(path); } catch { } if (disposing) { path = null; } 当然,它不会以任何方式改变结果。
      【解决方案5】:

      有一个 FileOptions.DeleteOnClose 选项可以满足您的需求。

      这是MSDN页面的链接。

      【讨论】:

        【解决方案6】:

        不,由软件(阅读:开发人员)负责创建一个临时文件来处理它。

        查看您自己的临时文件夹,看看效果如何;-)

        【讨论】:

          【解决方案7】:

          不,这不是真的。基本上,您的应用程序负责清理自己的烂摊子。否则,临时文件会随着时间的推移而累积。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-09-29
            • 1970-01-01
            • 2012-06-27
            • 2016-02-07
            • 2012-04-11
            • 1970-01-01
            • 2013-02-02
            相关资源
            最近更新 更多