Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 2c823df

Browse files
committed
Merge pull request #2272 from stephentoub/scrub_filesystem
Separate platform-specifics out of shared FileSystem code
2 parents cd21daa + 2450018 commit 2c823df

File tree

7 files changed

+100
-98
lines changed

7 files changed

+100
-98
lines changed

src/System.IO.FileSystem/src/System/IO/Directory.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -315,28 +315,6 @@ private static String[] InternalGetFileSystemEntries(String path, String searchP
315315
return InternalGetFileDirectoryNames(path, path, searchPattern, true, true, searchOption);
316316
}
317317

318-
319-
// Private class that holds search data that is passed around
320-
// in the heap based stack recursion
321-
internal sealed class SearchData
322-
{
323-
public SearchData(String fullPath, String userPath, SearchOption searchOption)
324-
{
325-
Contract.Requires(fullPath != null && fullPath.Length > 0);
326-
Contract.Requires(userPath != null && userPath.Length > 0);
327-
Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
328-
329-
this.fullPath = fullPath;
330-
this.userPath = userPath;
331-
this.searchOption = searchOption;
332-
}
333-
334-
public readonly string fullPath; // Fully qualified search path excluding the search criteria in the end (ex, c:\temp\bar\foo)
335-
public readonly string userPath; // User specified path (ex, bar\foo)
336-
public readonly SearchOption searchOption;
337-
}
338-
339-
340318
// Returns fully qualified user path of dirs/files that matches the search parameters.
341319
// For recursive search this method will search through all the sub dirs and execute
342320
// the given search criteria against every dir.

src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs

Lines changed: 23 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
using System;
5-
using System.Collections;
64
using System.Collections.Generic;
75
using System.Diagnostics;
86
using System.Diagnostics.Contracts;
9-
using System.Globalization;
10-
using System.Runtime.InteropServices;
11-
using System.Runtime.Versioning;
127
using System.Security;
13-
using System.Text;
14-
15-
using Microsoft.Win32;
168

179
namespace System.IO
1810
{
@@ -25,35 +17,18 @@ public DirectoryInfo(String path)
2517
throw new ArgumentNullException("path");
2618
Contract.EndContractBlock();
2719

28-
Init(path);
29-
}
30-
31-
[System.Security.SecurityCritical]
32-
private void Init(String path)
33-
{
34-
// Special case "<DriveLetter>:" to point to "<CurrentDirectory>" instead
35-
if ((path.Length == 2) && (path[1] == ':'))
36-
{
37-
OriginalPath = ".";
38-
}
39-
else
40-
{
41-
OriginalPath = path;
42-
}
43-
44-
String fullPath = PathHelpers.GetFullPathInternal(path);
45-
46-
FullPath = fullPath;
20+
OriginalPath = PathHelpers.ShouldReviseDirectoryPathToCurrent(path) ? "." : path;
21+
FullPath = PathHelpers.GetFullPathInternal(path);
4722
DisplayPath = GetDisplayName(OriginalPath, FullPath);
4823
}
4924

5025
[System.Security.SecuritySafeCritical]
5126
internal DirectoryInfo(String fullPath, IFileSystemObject fileSystemObject) : base(fileSystemObject)
5227
{
5328
Debug.Assert(PathHelpers.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!");
29+
5430
// Fast path when we know a DirectoryInfo exists.
5531
OriginalPath = Path.GetFileName(fullPath);
56-
5732
FullPath = fullPath;
5833
DisplayPath = GetDisplayName(OriginalPath, FullPath);
5934
}
@@ -74,17 +49,20 @@ public DirectoryInfo Parent
7449
[System.Security.SecuritySafeCritical]
7550
get
7651
{
77-
String parentName;
78-
// FullPath might be either "c:\bar" or "c:\bar\". Handle
79-
// those cases, as well as avoiding mangling "c:\".
80-
String s = FullPath;
81-
if (s.Length > 3 && s[s.Length - 1] == Path.DirectorySeparatorChar)
82-
s = FullPath.Substring(0, FullPath.Length - 1);
83-
parentName = Path.GetDirectoryName(s);
84-
if (parentName == null)
85-
return null;
86-
DirectoryInfo dir = new DirectoryInfo(parentName, null);
87-
return dir;
52+
string s = FullPath;
53+
54+
// FullPath might end in either "parent\child" or "parent\child", and in either case we want
55+
// the parent of child, not the child. Trim off an ending directory separator if there is one,
56+
// but don't mangle the root.
57+
if (!PathHelpers.IsRoot(s))
58+
{
59+
s = PathHelpers.TrimEndingDirectorySeparator(s);
60+
}
61+
62+
string parentName = Path.GetDirectoryName(s);
63+
return parentName != null ?
64+
new DirectoryInfo(parentName, null) :
65+
null;
8866
}
8967
}
9068

@@ -457,40 +435,18 @@ private static String GetDisplayName(String originalPath, String fullPath)
457435
Debug.Assert(originalPath != null);
458436
Debug.Assert(fullPath != null);
459437

460-
String displayName = "";
461-
462-
// Special case "<DriveLetter>:" to point to "<CurrentDirectory>" instead
463-
if ((originalPath.Length == 2) && (originalPath[1] == ':'))
464-
{
465-
displayName = ".";
466-
}
467-
else
468-
{
469-
displayName = GetDirName(fullPath);
470-
}
471-
return displayName;
438+
return PathHelpers.ShouldReviseDirectoryPathToCurrent(originalPath) ?
439+
"." :
440+
GetDirName(fullPath);
472441
}
473442

474443
private static String GetDirName(String fullPath)
475444
{
476445
Debug.Assert(fullPath != null);
477446

478-
String dirName = null;
479-
if (fullPath.Length > 3)
480-
{
481-
String s = fullPath;
482-
if (fullPath[fullPath.Length - 1] == Path.DirectorySeparatorChar)
483-
{
484-
s = fullPath.Substring(0, fullPath.Length - 1);
485-
}
486-
dirName = Path.GetFileName(s);
487-
}
488-
else
489-
{
490-
dirName = fullPath; // For rooted paths, like "c:\"
491-
}
492-
return dirName;
447+
return PathHelpers.IsRoot(fullPath) ?
448+
fullPath :
449+
Path.GetFileName(PathHelpers.TrimEndingDirectorySeparator(fullPath));
493450
}
494451
}
495452
}
496-

src/System.IO.FileSystem/src/System/IO/PathHelpers.Unix.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ internal static int GetRootLength(string path)
1111
return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
1212
}
1313

14+
internal static bool ShouldReviseDirectoryPathToCurrent(string path)
15+
{
16+
// Unlike on Windows, there are no special cases on Unix where we'd want to ignore
17+
// user-provided path and instead automatically use the current directory.
18+
return false;
19+
}
20+
1421
internal static void CheckSearchPattern(string searchPattern)
1522
{
1623
// ".." should not be used to move up directories. On Windows, this is more strict, and ".."

src/System.IO.FileSystem/src/System/IO/PathHelpers.Windows.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ internal static int GetRootLength(String path)
4040
return i;
4141
}
4242

43+
internal static bool ShouldReviseDirectoryPathToCurrent(string path)
44+
{
45+
// In situations where this method is invoked, "<DriveLetter>:" should be special-cased
46+
// to instead go to the current directory.
47+
return path.Length == 2 && path[1] == ':';
48+
}
49+
4350
// ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
4451
// the user being able to use it to move up directories. Here are some examples eg
4552
// Valid: a..b abc..d

src/System.IO.FileSystem/src/System/IO/PathHelpers.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,21 @@ internal static void ThrowIfEmptyOrRootedPath(string path2)
3737
throw new ArgumentException(SR.Arg_Path2IsRooted, "path2");
3838
}
3939

40+
internal static bool IsRoot(string path)
41+
{
42+
return path.Length == GetRootLength(path);
43+
}
44+
4045
internal static bool EndsInDirectorySeparator(String path)
4146
{
4247
return path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
4348
}
49+
50+
internal static string TrimEndingDirectorySeparator(string path)
51+
{
52+
return EndsInDirectorySeparator(path) ?
53+
path.Substring(0, path.Length - 1) :
54+
path;
55+
}
4456
}
4557
}

src/System.IO.FileSystem/src/System/IO/Win32FileSystemEnumerable.cs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ internal class Win32FileSystemEnumerableIterator<TSource> : Iterator<TSource>
8888
private const int STATE_FINISH = 4;
8989

9090
private SearchResultHandler<TSource> _resultHandler;
91-
private List<Directory.SearchData> _searchStack;
92-
private Directory.SearchData _searchData;
91+
private List<SearchData> _searchStack;
92+
private SearchData _searchData;
9393
private String _searchCriteria;
9494
[System.Security.SecurityCritical]
9595
private SafeFindHandle _hnd = null;
@@ -119,7 +119,7 @@ internal Win32FileSystemEnumerableIterator(String path, String originalUserPath,
119119

120120
_oldMode = Interop.mincore.SetErrorMode(Interop.mincore.SEM_FAILCRITICALERRORS);
121121

122-
_searchStack = new List<Directory.SearchData>();
122+
_searchStack = new List<SearchData>();
123123

124124
String normalizedSearchPattern = NormalizeSearchPattern(searchPattern);
125125

@@ -148,7 +148,7 @@ internal Win32FileSystemEnumerableIterator(String path, String originalUserPath,
148148
}
149149
this._userPath = userPathTemp;
150150

151-
_searchData = new Directory.SearchData(_normalizedSearchPath, this._userPath, searchOption);
151+
_searchData = new SearchData(_normalizedSearchPath, this._userPath, searchOption);
152152

153153
CommonInit();
154154
}
@@ -218,13 +218,13 @@ private Win32FileSystemEnumerableIterator(String fullPath, String normalizedSear
218218
this._userPath = userPath;
219219
this._searchOption = searchOption;
220220

221-
_searchStack = new List<Directory.SearchData>();
221+
_searchStack = new List<SearchData>();
222222

223223
if (searchCriteria != null)
224224
{
225225
PathHelpers.CheckInvalidPathChars(fullPath, true);
226226

227-
_searchData = new Directory.SearchData(normalizedSearchPath, userPath, searchOption);
227+
_searchData = new SearchData(normalizedSearchPath, userPath, searchOption);
228228
CommonInit();
229229
}
230230
else
@@ -381,7 +381,7 @@ public override bool MoveNext()
381381
}
382382

383383
[System.Security.SecurityCritical]
384-
private SearchResult CreateSearchResult(Directory.SearchData localSearchData, Interop.mincore.WIN32_FIND_DATA findData)
384+
private SearchResult CreateSearchResult(SearchData localSearchData, Interop.mincore.WIN32_FIND_DATA findData)
385385
{
386386
string findData_fileName = findData.cFileName;
387387
Contract.Requires(findData_fileName.Length != 0 && !Path.IsPathRooted(findData_fileName),
@@ -400,7 +400,7 @@ private void HandleError(int errorCode, String path)
400400
}
401401

402402
[System.Security.SecurityCritical] // auto-generated
403-
private void AddSearchableDirsToStack(Directory.SearchData localSearchData)
403+
private void AddSearchableDirsToStack(SearchData localSearchData)
404404
{
405405
Contract.Requires(localSearchData != null);
406406

@@ -440,7 +440,7 @@ private void AddSearchableDirsToStack(Directory.SearchData localSearchData)
440440
SearchOption option = localSearchData.searchOption;
441441

442442
// Setup search data for the sub directory and push it into the stack
443-
Directory.SearchData searchDataSubDir = new Directory.SearchData(tempFullPath, tempUserPath, option);
443+
SearchData searchDataSubDir = new SearchData(tempFullPath, tempUserPath, option);
444444

445445
_searchStack.Insert(incr++, searchDataSubDir);
446446
}
@@ -618,6 +618,26 @@ internal override FileSystemInfo CreateObject(SearchResult result)
618618
}
619619
}
620620

621+
// Holds search data that is passed around
622+
// in the heap based stack recursion
623+
internal sealed class SearchData
624+
{
625+
public SearchData(String fullPath, String userPath, SearchOption searchOption)
626+
{
627+
Contract.Requires(fullPath != null && fullPath.Length > 0);
628+
Contract.Requires(userPath != null && userPath.Length > 0);
629+
Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
630+
631+
this.fullPath = fullPath;
632+
this.userPath = userPath;
633+
this.searchOption = searchOption;
634+
}
635+
636+
public readonly string fullPath; // Fully qualified search path excluding the search criteria in the end (ex, c:\temp\bar\foo)
637+
public readonly string userPath; // User specified path (ex, bar\foo)
638+
public readonly SearchOption searchOption;
639+
}
640+
621641
internal sealed class SearchResult
622642
{
623643
private String _fullPath; // fully-qualified path

src/System.IO.FileSystem/tests/DirectoryInfo/ctor_str.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ public void CDriveCase()
5353
Assert.NotEqual(dir.FullName, dir2.FullName);
5454
}
5555

56+
[Fact]
57+
[PlatformSpecific(PlatformID.Windows)]
58+
public void DriveLetter_Windows()
59+
{
60+
// On Windows, DirectoryInfo will replace "<DriveLetter>:" with "."
61+
var driveLetter = new DirectoryInfo(Directory.GetCurrentDirectory()[0] + ":");
62+
var current = new DirectoryInfo(".");
63+
Assert.Equal(current.Name, driveLetter.Name);
64+
Assert.Equal(current.FullName, driveLetter.FullName);
65+
}
66+
67+
[Fact]
68+
[PlatformSpecific(PlatformID.AnyUnix)]
69+
public void DriveLetter_Unix()
70+
{
71+
// On Unix, there's no special casing for drive letters, which are valid file names
72+
var driveLetter = new DirectoryInfo("C:");
73+
var current = new DirectoryInfo(".");
74+
Assert.Equal("C:", driveLetter.Name);
75+
Assert.Equal(Path.Combine(current.FullName, "C:"), driveLetter.FullName);
76+
}
77+
5678
[Fact]
5779
[ActiveIssue(1222)]
5880
public void TrailingWhitespace()

0 commit comments

Comments
 (0)