20

Will Path.GetRandomFileName generate a unique filename every single time? Also, what about Path.GetTempFileName - will that generate a unique name?

Simon
  • 33,714
  • 21
  • 133
  • 202
SandhraPrakash
  • 442
  • 2
  • 6
  • 15
  • 1
    You'll get collisions at around a hundred million filenames. Depending on which collision probability you're willing to accept, it can become uncomfortable around a million filenames. – CodesInChaos Feb 10 '14 at 20:59
  • Personally I'd use 128 bit random values instead of just 55 bit. May not fit 8.3 filenames, but DOS is pretty dead, so who cares? – CodesInChaos Feb 10 '14 at 21:01
  • 2
    GetRandomFileName has a secondary motivation which is not entropy-related. Despite the NTFS supporting very long path names, most versions of .NET except the latest one are confined to MAX_PATH. In a lot of use cases, it is necessary to create several levels of randomized folders underneath the environmentally-defined TEMP folder. GetRandomFileName encodes the 50+ bits of entropy in a short name, which makes the path too long error less likely to happen. Other than that, I agree that one could probably follow the same logic and design a custom implementation that uses more entropy. – rwong Dec 19 '15 at 18:52
  • DOS is dead? I didn't realize... – Greg Gum Dec 02 '22 at 02:52
  • 1
    You can also check out [System.Guid.NewGuid()](https://stackoverflow.com/q/8477664/7232335). From MSDN docs: "A GUID is a 128-bit integer (16 bytes) that can be used across all computers and networks wherever a unique identifier is required. Such an identifier has a very low probability of being duplicated." – Nathaniel Jones Dec 29 '22 at 21:23

5 Answers5

13

On my system Path.GetRandomFileName() returns a short name in the 8.3 format.

Is it guaranteed never to return the same name twice? No, you can't guarantee that, just like you can't for any hashing algorithm either. There are only a finite amount of names so eventually you get a duplicate.

However the chance for that is very low since Path.GetRandomFileName() uses the RNGCryptoServiceProvider which is a cryptographically strong random number generator.

To summarize it, you can't guarantee in a strict way that it will be unique. But the chance for a duplicate is very low so you can assume it is.

Dirk
  • 10,668
  • 2
  • 35
  • 49
  • could retry till you get a non-existing one but could still have a data race with other threads (could make it a guarded block) and even worse with other procs – George Birbilis Nov 26 '20 at 15:42
9

The short answer is yes in both cases.
In reality get it will generate 11 random chars that means there are (26 +10)^11 possible names (1.316217e+17) so chances of creating the same name twice are none existand for all practical purposes.

For more information I suggest you read this

and the relevant MSDN pages

Sibster
  • 3,081
  • 21
  • 18
  • 2
    in fact there are only 32^11 possible names because [`GetRandomFileName()` uses base 32 instead of base 36](https://referencesource.microsoft.com/#mscorlib/system/io/path.cs,efb113f637a6bb47,references) – phuclv Jun 20 '19 at 03:36
  • Can we say an ABSOLUTE yes since the GetRandomFIleName doesn't check the previous names before generating the new one? – Reza Taba May 31 '23 at 15:12
1

From : https://gist.github.com/samcorcos/e65a0f5a5d641dd3a6b5f513b6a911f8

const calculate = (n, k) => {
  const exponent = (-k * (k - 1)) / (2 * n)
  return 1 - Math.E ** exponent
}

// where `n` is the number of possible unique hashes
// where `k` is the number of values created

// calculate((26 + 10) ** 11, 51436220) => 0.010000000067703074

> Hit 1% chance of collision for 51,436,220 generated Path.GetRandomFileName.

Valentin P
  • 348
  • 2
  • 10
0

Copying from my fiddle at https://dotnetfiddle.net/bmFVSX the Path_SafeGetTempFilename method should give you a bit more safety (but still is prone to low probability data race that could be minimized by using your own temp subfolder):

//.NET's Path.GetTempFilename (https://learn.microsoft.com/en-us/dotnet/api/system.io.path.gettempfilename?view=net-5.0) raises an IOException if the system's temp folder has more than 65535 files

//Following a suggestion from https://github.com/dotnet/sdk/issues/8439
//to use Path.GetTempPath (https://learn.microsoft.com/en-us/dotnet/api/system.io.path.gettemppath?view=net-5.0) to get the system temp folder
//combined via Path.Combine (https://learn.microsoft.com/en-us/dotnet/api/system.io.path.combine?view=net-5.0)
//with GetRandomFilename (https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getrandomfilename?view=net-5.0) instead

//For extra safety cross-checking (in a do/while loop) with File.Exists (https://learn.microsoft.com/en-us/dotnet/api/system.io.file.exists?view=net-5.0) before returning the file

//Using File.WriteAllLines (https://learn.microsoft.com/en-us/dotnet/api/system.io.file.writealllines?view=net-5.0) to create the temp file since that is the original behaviour of GetTempFilename in contrast to GetRandomFilename

//Note: "Path.GetRandomFileName() uses the RNGCryptoServiceProvider which is a cryptographically strong random number generator", quoting https://stackoverflow.com/questions/21684696/will-path-getrandomfilename-generate-a-unique-filename-every-time/21684881#21684881

using System;
using System.Diagnostics;
using System.IO;

public class Program
{
    public static string Path_SafeGetTempFileName() {
        string filepath; //no need to initialize with empty/null value, the do/while code flow construct will always set a value to filepath
        do
        {
            filepath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
        } while (File.Exists(filepath));

        try
        {
            File.WriteAllLines(filepath, new string[]{}); //generate an empty file
        }
        catch
        {
            Debug.WriteLine($"Couldn't create empty temporary file: {filepath}");
            //TODO: log any failure to generate an empty temp file here
        }
        //Note: don't do a loop if it fails to create the temp file since it might be failing to access the filesystem for other reasons, which would cause infinite recursion

        return filepath;
    }

    public static void Main()
    {
        string tempFilePath1 = Path.GetTempFileName();
        string tempFilePath2 = Path_SafeGetTempFileName();

        Console.WriteLine(tempFilePath1);
        Console.WriteLine(tempFilePath2);       
    }
}
George Birbilis
  • 2,782
  • 2
  • 33
  • 35
0

Folder-based Solution

In my opinion categorizing files into their specified folders guarantees no duplicated names almost absolutely since it reduces the number of files in each folder significantly. Let's consider Google Drive/Photos structure. I can imagine they store files of each user in a separated folder (if not in database). So here's how I do it based on my users since my app is user based.(you can categorize yours based on your application).

Also using Guid.NewGuid() since it produces a longer value and less likely to be duplicated.

// send in formFile and userId 
// return filePath to store in DB
public async Task<string?> SingleFile(IFormFile formFile, string? userId)
{
    string? filePath = null;

    // find the existing path OR create a folder if doesn't exist by userId
    string uploadsFolder = Path.Combine(_webHostEnvironment.WebRootPath, "Storage/Photos/" + userId + "/");
    if (!Directory.Exists(uploadsFolder)) // create folder
    {
        Directory.CreateDirectory(uploadsFolder);
    }

    // copy file to the folder
    if (formFile.Length > 0)
    {
        string uniqueFileName = Guid.NewGuid().ToString() + "_" + formFile.FileName;

        filePath = Path.Combine(uploadsFolder + uniqueFileName);

        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await formFile.CopyToAsync(stream);
        }

    }

    return filePath;
}

NOTE: In order to use _webHostEnvironment.WebRootPath you must have wwwroot folder in your project's/api root folder along with app.UseStaticFiles(); added to the Program.cs

Reza Taba
  • 1,151
  • 11
  • 15