singrdk/base/Libraries/System.IO/Directory.cs

687 lines
29 KiB
C#

// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: Directory
**
**
** Purpose: Exposes routines for enumerating through a
** directory.
**
** Date: March 5, 2000
** April 11,2000
**
===========================================================*/
using System;
using System.Collections;
using Microsoft.Singularity;
using System.Text;
using System.Runtime.InteropServices;
using System.Globalization;
using Microsoft.Singularity.Directory;
namespace System.IO {
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory"]/*' />
public sealed class Directory {
private Directory()
{
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetParent"]/*' />
public static DirectoryInfo GetParent(String path)
{
if (path==null)
throw new ArgumentNullException("path");
if (path.Length==0)
throw new ArgumentException("Argument_PathEmpty", "path");
String fullPath = Path.GetFullPathInternal(path);
String s = Path.GetDirectoryName(fullPath);
if (s==null)
return null;
return new DirectoryInfo(s);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.CreateDirectory"]/*' />
public static DirectoryInfo CreateDirectory(String path) {
if (path==null)
throw new ArgumentNullException("path");
if (path.Length == 0)
throw new ArgumentException("Argument_PathEmpty");
String fullPath = Path.GetFullPathInternal(path);
// You need read access to the directory to be returned back and write access to all the directories
// that you need to create. If we fail any security checks we will not create any directories at all.
// We attempt to create directories only after all the security checks have passed. This is avoid doing
// a demand at every level.
InternalCreateDirectory(fullPath,path);
return new DirectoryInfo(fullPath, false);
}
internal static void InternalCreateDirectory(String! fullPath, String path) {
int length = fullPath.Length;
// We need to trim the trailing slash or the code will try to create 2 directories of the same name.
if (length >= 2 && Path.IsDirectorySeparator(fullPath[length - 1]))
length--;
int i = Path.GetRootLength(fullPath);
// For UNC paths that are only // or ///
if (length == 2 && Path.IsDirectorySeparator(fullPath[1]))
throw new IOException(String.Format("IO.IO_CannotCreateDirectory",path));
ArrayList list = new ArrayList();
while (i < length) {
i++;
while (i < length && fullPath[i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) i++;
String dir = fullPath.Substring(0, i);
if (!InternalExists(dir)) { // Create only the ones missing
list.Add(dir);
}
}
if (list.Count != 0)
{
String [] securityList = (String[])list.ToArray(typeof(String));
for (int j = 0 ; j < securityList.Length; j++)
securityList[j] += "\\."; // leafs will never has a slash at the end
}
// We need this check to mask OS differences
// Handle CreateDirectory("X:\\") when X: doesn't exist. Similarly for n/w paths.
String root = InternalGetDirectoryRoot(fullPath);
if (!InternalExists(root)) {
// Extract the root from the passed in path again for security.
__Error.WinIOError(Native.ERROR_PATH_NOT_FOUND, InternalGetDirectoryRoot(path));
}
bool r = true;
int firstError = 0;
// If all the security checks succeeded create all the directories
for (int j = 0; j < list.Count; j++)
{
String name = (String!)list[j];
if (name.Length > Path.MAX_DIRECTORY_PATH)
throw new PathTooLongException("IO.PathTooLong");
r = Native.CreateDirectory(name, 0);
if (!r && (firstError == 0)) {
firstError = Native.ERROR_PATH_EXISTS;
}
}
if (!r && (firstError != 0)) {
__Error.WinIOError(firstError, path);
}
}
// Tests if the given path refers to an existing DirectoryInfo on disk.
//
// Your application must have Read permission to the directory's
// contents.
//
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.Exists"]/*' />
public static bool Exists(String path) {
try
{ if (path==null)
return false;
if (path.Length==0)
return false;
// Get fully qualified file name ending in \* for security check
String fullPath = Path.GetFullPathInternal(path);
return InternalExists(fullPath);
}
catch(ArgumentException) {}
catch(NotSupportedException) {} // To deal with the fact that security can now throw this on :
catch(IOException) {}
return false;
}
// Determine whether path describes an existing directory
// on disk, avoiding security checks.
internal static bool InternalExists(String path) {
Native.FILE_ATTRIBUTE_DATA data = new Native.FILE_ATTRIBUTE_DATA();
int dataInitialised = File.FillAttributeInfo(path,ref data);
if (dataInitialised != 0)
return false;
return data.fileAttributes != -1 && (data.fileAttributes & Native.FILE_ATTRIBUTE_DIRECTORY) != 0;
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.SetCreationTime"]/*' />
public static void SetCreationTime(String! path, DateTime creationTime)
{
SetCreationTimeUtc(path, creationTime.ToUniversalTime());
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.SetCreationTimeUtc"]/*' />
public static void SetCreationTimeUtc(String! path, DateTime creationTimeUtc)
{
IntPtr handle = Directory.OpenHandle(path);
bool r = Native.SetFileTime(handle, new long[] {creationTimeUtc.ToFileTimeUtc()}, null, null);
if (!r)
{
Native.CloseHandle(handle);
__Error.WinIOError(1, path);
}
Native.CloseHandle(handle);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetCreationTimeUtc"]/*' />
public static DateTime GetCreationTimeUtc(String! path)
{
return File.GetCreationTimeUtc(path);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.SetLastWriteTimeUtc"]/*' />
public static void SetLastWriteTimeUtc(String! path, DateTime lastWriteTimeUtc)
{
IntPtr handle = Directory.OpenHandle(path);
bool r = Native.SetFileTime(handle, null, null, new long[] {lastWriteTimeUtc.ToFileTimeUtc()});
if (!r)
{
Native.CloseHandle(handle);
__Error.WinIOError(1, path);
}
Native.CloseHandle(handle);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetLastWriteTimeUtc"]/*' />
public static DateTime GetLastWriteTimeUtc(String! path)
{
return File.GetLastWriteTimeUtc(path);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.SetLastAccessTimeUtc"]/*' />
public static void SetLastAccessTimeUtc(String! path, DateTime lastAccessTimeUtc)
{
IntPtr handle = Directory.OpenHandle(path);
bool r = Native.SetFileTime(handle, null, new long[] {lastAccessTimeUtc.ToFileTimeUtc()}, null);
if (!r)
{
Native.CloseHandle(handle);
__Error.WinIOError(1, path);
}
Native.CloseHandle(handle);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetLastAccessTimeUtc"]/*' />
public static DateTime GetLastAccessTimeUtc(String! path)
{
return File.GetLastAccessTimeUtc(path);
}
// Returns an array of Files in the DirectoryInfo specified by path
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetFiles"]/*' />
public static String[] GetFiles(String path)
{
return GetFiles(path,"*");
}
// Returns an array of Files in the current DirectoryInfo matching the
// given search criteria (ie, "*.txt").
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetFiles1"]/*' />
public static String[] GetFiles(String path,String searchPattern)
{
if (path==null)
throw new ArgumentNullException("path");
if (searchPattern==null)
throw new ArgumentNullException("searchPattern");
searchPattern = searchPattern.TrimEnd();
if (searchPattern.Length == 0)
return new String[0];
// Must fully qualify the path for the security check
String fullPath = Path.GetFullPathInternal(path);
String searchPath = Path.GetDirectoryName(searchPattern);
if (searchPath != null && searchPath != String.Empty) // For filters like foo\*.cs we need to verify if the directory foo is not denied access.
{
path = Path.Combine(path,searchPath); // Need to add the searchPath to return correct path and for right security checks
}
// Note - fileNames returned by InternalGetFiles are not fully qualified.
String [] fileNames = InternalGetFiles(fullPath, path, searchPattern);
for(int i=0; i<fileNames.Length; i++)
fileNames[i] = Path.InternalCombine(path, fileNames[i]);
return fileNames;
}
// Internal helper function with no security checks
// Note - fileNames returned by InternalGetFiles are not fully qualified.
// Path should be fully qualified. userPath is used in exceptions.
internal static String[]! InternalGetFiles(String path, String userPath, String searchPattern)
{
String fullPath = Path.InternalCombine(path, searchPattern);
String[] fileNames = InternalGetFileDirectoryNames(fullPath, userPath, true);
return fileNames;
}
// Returns an array of Directories in the current directory.
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetDirectories"]/*' />
public static String[] GetDirectories(String path)
{
return GetDirectories(path,"*");
}
// Returns an array of Directories in the current DirectoryInfo matching the
// given search criteria (ie, "*.txt").
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetDirectories1"]/*' />
public static String[] GetDirectories(String path,String searchPattern)
{
if (path==null)
throw new ArgumentNullException("path");
if (searchPattern==null)
throw new ArgumentNullException("searchPattern");
searchPattern = searchPattern.TrimEnd();
if (searchPattern.Length == 0)
return new String[0];
// Must fully qualify the path for the security check
String fullPath = Path.GetFullPathInternal(path);
String searchPath = Path.GetDirectoryName(searchPattern);
if (searchPath != null && searchPath != String.Empty) // For filters like foo\*.cs we need to verify if the directory foo is not denied access.
{
path = Path.Combine(path,searchPath); // Need to add the searchPath to return correct path and for right security checks
}
String [] dirNames = InternalGetDirectories(fullPath, path, searchPattern);
for(int i=0; i<dirNames.Length; i++)
dirNames[i] = Path.InternalCombine(path, dirNames[i]);
return dirNames;
}
// Internal helper function that has no security checks
// Note - InternalGetDirectories returns non-qualified directory names.
// Path should be fully qualified. userPath is used in exceptions.
internal static String[]! InternalGetDirectories(String path, String userPath, String searchPattern)
{
String fullPath = Path.InternalCombine(path, searchPattern);
String[] dirNames = InternalGetFileDirectoryNames(fullPath, userPath, false);
return dirNames;
}
// Returns an array of strongly typed FileSystemInfo entries in the path
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetFileSystemEntries"]/*' />
public static String[] GetFileSystemEntries(String path)
{
return GetFileSystemEntries(path,"*");
}
// Returns an array of strongly typed FileSystemInfo entries in the path with the
// given search criteria (ie, "*.txt"). We disallow .. as a part of the search criteria
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetFileSystemEntries1"]/*' />
public static String[] GetFileSystemEntries(String path,String searchPattern)
{
if (path==null)
throw new ArgumentNullException("path");
if (searchPattern==null)
throw new ArgumentNullException("searchPattern");
searchPattern = searchPattern.TrimEnd();
if (searchPattern.Length == 0)
return new String[0];
// Must fully qualify the path for the security check
String fullPath = Path.GetFullPathInternal(path);
String searchPath = Path.GetDirectoryName(searchPattern);
if (searchPath != null && searchPath != String.Empty) // For filters like foo\*.cs we need to verify if the directory foo is not denied access.
{
path = Path.Combine(path,searchPath); // Need to add the searchPath to return correct path and for right security checks
}
String [] dirs = InternalGetDirectories(fullPath, path, searchPattern);
String [] files = InternalGetFiles(fullPath, path, searchPattern);
String [] fileSystemEntries = new String[dirs.Length + files.Length];
int count = 0;
for (int i = 0;i<dirs.Length;i++)
fileSystemEntries[count++] = Path.InternalCombine(path, dirs[i]);
for (int i = 0;i<files.Length;i++)
fileSystemEntries[count++] = Path.InternalCombine(path, files[i]);
return fileSystemEntries;
}
// Private helper function that does not do any security checks
internal static String[]! InternalGetFileDirectoryNames(String! fullPath, String userPath, bool file)
{
// If path ends in a trailing slash (\), append a * or we'll
// get a "Cannot find the file specified" exception
char lastChar = fullPath[fullPath.Length-1];
if (lastChar == Path.DirectorySeparatorChar || lastChar == Path.AltDirectorySeparatorChar || lastChar == Path.VolumeSeparatorChar)
fullPath = fullPath + '*';
String[] list = new String[10];
int listSize = 0;
Native.FIND_DATA data = new Native.FIND_DATA();
// Open a Find handle (Win32 is weird)
IntPtr hnd = Native.FindFirstFile(fullPath, out data);
if (hnd == IntPtr.Zero) {
return new String[0];
}
// Keep asking for more matching files, adding file names to list
int numEntries = 0; // Number of DirectoryInfo entities we see.
do {
assert data != null; // implied by result of FindFirst/Next not being Zero
bool includeThis; // Should this file/DirectoryInfo be included in the output?
if (file)
includeThis = (0==(data.dwFileAttributes & Native.FILE_ATTRIBUTE_DIRECTORY));
else {
includeThis = (0!=(data.dwFileAttributes & Native.FILE_ATTRIBUTE_DIRECTORY));
// Don't add "." nor ".."
if (includeThis && (data.cFileName.Equals(".") || data.cFileName.Equals("..")))
includeThis = false;
}
if (includeThis) {
numEntries++;
if (listSize==list.Length) {
String[] newList = new String[list.Length*2];
Array.Copy(list, 0, newList, 0, listSize);
list = newList;
}
list[listSize++] = data.cFileName;
}
} while (Native.FindNextFile(hnd, out data));
// Make sure we quit with a sensible error.
Native.FindClose(hnd); // Close Find handle in all cases.
// Check for a string such as "C:\tmp", in which case we return
// just the DirectoryInfo name. FindNextFile fails first time, and
// data still contains a directory.
// the above comment seems wrong. I don't see why data should still be
// non-null here.
assert data != null;
if (!file && numEntries==1 && (0!=(data.dwFileAttributes & Native.FILE_ATTRIBUTE_DIRECTORY))) {
String[] sa = new String[1];
sa[0] = data.cFileName;
return sa;
}
// Return list of files/directories as an array of strings
if (listSize == list.Length)
return list;
String[] items = new String[listSize];
Array.Copy(list, 0, items, 0, listSize);
return items;
}
// Retrieves the names of the logical drives on this machine in the
// form "C:\".
//
// Your application must have System Info permission.
//
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetLogicalDrives"]/*' />
public static String[] GetLogicalDrives()
{
int drives = Native.GetLogicalDrives();
if (drives==0)
__Error.WinIOError();
uint d = (uint)drives;
int count = 0;
while (d != 0) {
if (((int)d & 1) != 0) count++;
d >>= 1;
}
String[] result = new String[count];
char[] root = new char[] {'A', ':', '\\'};
d = (uint)drives;
count = 0;
while (d != 0) {
if (((int)d & 1) != 0) {
result[count++] = new String(root);
}
d >>= 1;
root[0]++;
}
return result;
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetDirectoryRoot"]/*' />
public static String GetDirectoryRoot(String path) {
if (path==null)
throw new ArgumentNullException("path");
String fullPath = Path.GetFullPathInternal(path);
return fullPath.Substring(0, Path.GetRootLength(fullPath));
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.InternalGetDirectoryRoot"]/*' />
internal static String InternalGetDirectoryRoot(String path) {
if (path == null) return null;
return path.Substring(0, Path.GetRootLength(path));
}
/*===============================CurrentDirectory===============================
**Action: Provides a getter and setter for the current directory. The original
** current DirectoryInfo is the one from which the process was started.
**Returns: The current DirectoryInfo (from the getter). Void from the setter.
**Arguments: The current DirectoryInfo to which to switch to the setter.
**Exceptions:
==============================================================================*/
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.GetCurrentDirectory"]/*' />
public static String GetCurrentDirectory()
{
StringBuilder sb = new StringBuilder(Path.MAX_PATH + 1);
if (Native.GetCurrentDirectory(sb.Capacity, sb) == 0)
System.IO.__Error.WinIOError();
String currentDirectory = sb.ToString();
return currentDirectory;
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.SetCurrentDirectory"]/*' />
public static void SetCurrentDirectory(String path)
{
if (path==null)
throw new ArgumentNullException("value");
if (path.Length==0)
throw new ArgumentException("Argument_PathEmpty");
if (path.Length >= Path.MAX_PATH)
throw new PathTooLongException("IO.PathTooLong");
// This will have some huge effects on the rest of the runtime
// and every other application. Make sure app is highly trusted.
String fulldestDirName = Path.GetFullPathInternal(path);
// If path doesn't exist, this sets last error to 3 (Path not Found).
if (!Native.SetCurrentDirectory(fulldestDirName))
System.IO.__Error.WinIOError(1, path);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.Move"]/*' />
public static void Move(String sourceDirName,String destDirName) {
if (sourceDirName==null)
throw new ArgumentNullException("sourceDirName");
if (sourceDirName.Length==0)
throw new ArgumentException("Argument_EmptyFileName", "sourceDirName");
if (destDirName==null)
throw new ArgumentNullException("destDirName");
if (destDirName.Length==0)
throw new ArgumentException("Argument_EmptyFileName", "destDirName");
String fullsourceDirName = Path.GetFullPathInternal(sourceDirName);
String fulldestDirName = Path.GetFullPathInternal(destDirName);
String sourcePath;
if (fullsourceDirName.EndsWith( '\\' ))
sourcePath = fullsourceDirName;
else
sourcePath = fullsourceDirName + "\\";
String destPath;
if (fulldestDirName.EndsWith( '\\' ))
destPath = fulldestDirName;
else
destPath = fulldestDirName + "\\";
if (CompareInfo.Compare(sourcePath, destPath, CompareOptions.IgnoreCase) == 0)
throw new IOException("IO.IO_SourceDestMustBeDifferent");
String sourceRoot = Path.GetPathRoot(sourcePath);
String destinationRoot = Path.GetPathRoot(destPath);
if (CompareInfo.Compare(sourceRoot, destinationRoot, CompareOptions.IgnoreCase) != 0)
throw new IOException("IO.IO_SourceDestMustHaveSameRoot");
if (!Native.MoveFile(sourceDirName, destDirName))
{
__Error.WinIOError(1,String.Empty);
}
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.Delete"]/*' />
public static void Delete(String! path)
{
String fullPath = Path.GetFullPathInternal(path);
Delete(fullPath, path, false);
}
//| <include file='doc\Directory.uex' path='docs/doc[@for="Directory.Delete1"]/*' />
public static void Delete(String! path, bool recursive)
{
String fullPath = Path.GetFullPathInternal(path);
Delete(fullPath, path, recursive);
}
// Called from DirectoryInfo as well. FullPath is fully qualified,
// while the user path is used for feedback in exceptions.
internal static void Delete(String fullPath, String userPath, bool recursive)
{
DeleteHelper(fullPath, userPath, recursive);
}
// Note that fullPath is fully qualified, while userPath may be
// relative. Use userPath for all exception messages to avoid leaking
// fully qualified path information.
private static void DeleteHelper(String fullPath, String userPath, bool recursive)
{
bool r;
Exception ex = null;
ErrorCode error;
if (recursive) {
Native.FIND_DATA data = new Native.FIND_DATA();
// Open a Find handle (Win32 is weird)
IntPtr hnd = Native.FindFirstFile(fullPath+Path.DirectorySeparatorChar+"*", out data);
if (hnd == IntPtr.Zero) {
__Error.WinIOError(1, userPath);
}
do {
assert data != null; // implied by FindFirst/NextFile and return status
bool isDir = (0!=(data.dwFileAttributes & Native.FILE_ATTRIBUTE_DIRECTORY));
if (isDir) {
if (data.cFileName.Equals(".") || data.cFileName.Equals(".."))
continue;
// recurse
String newFullPath = Path.InternalCombine(fullPath, data.cFileName);
String newUserPath = Path.InternalCombine(userPath, data.cFileName);
try {
DeleteHelper(newFullPath, newUserPath, recursive);
}
catch(Exception e) {
if (ex == null) {
ex = e;
}
}
}
else {
String fileName = fullPath + Path.DirectorySeparatorChar + data.cFileName;
r = Native.DeleteFile(fileName, out error);
if (!r) {
// __Error.WinIOError(1, data.cFileName);
__Error.SingularityIOError(error,data.cFileName);
}
}
} while (Native.FindNextFile(hnd, out data));
// Make sure we quit with a sensible error.
Native.FindClose(hnd); // Close Find handle in all cases.
if (ex != null)
throw ex;
}
r = Native.RemoveDirectory(fullPath, out error);
if (!r)
//__Error.WinIOError(1, userPath);
__Error.SingularityIOError(error,fullPath);
}
internal static void VerifyDriveExists(String! root)
{
int drives = Native.GetLogicalDrives();
if (drives==0)
__Error.WinIOError();
uint d = (uint)drives;
char drive = Char.ToLower(root[0]);
if ((d & (1 << (drive - 'a'))) == 0)
throw new DirectoryNotFoundException(String.Format("IO.DriveNotFound_Drive", root));
}
private static IntPtr OpenHandle(String! path)
{
String fullPath = Path.GetFullPathInternal(path);
String root = Path.GetPathRoot(fullPath);
assert root != null;
if (root == fullPath && root[1] == Path.VolumeSeparatorChar)
throw new ArgumentException("Arg_PathIsVolume");
IntPtr handle = Native.CreateFile(
fullPath,
GENERIC_WRITE,
FILE_SHARE_WRITE|FILE_SHARE_DELETE,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS
);
if (handle == IntPtr.Zero) {
__Error.WinIOError(1, path);
}
return handle;
}
private const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
private const int GENERIC_WRITE = unchecked((int)0x40000000);
private const int FILE_SHARE_WRITE = 0x00000002;
private const int FILE_SHARE_DELETE = 0x00000004;
private const int OPEN_EXISTING = 0x00000003;
private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
}
}