2 // System.IO.Directory.cs
5 // Jim Richardson (develop@wtfo-guru.com)
6 // Miguel de Icaza (miguel@ximian.com)
7 // Dan Lewis (dihlewis@yahoo.co.uk)
8 // Eduardo Garcia (kiwnix@yahoo.es)
9 // Ville Palo (vi64pa@kolumbus.fi)
11 // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
12 // Copyright (C) 2002 Ximian, Inc.
14 // Created: Monday, August 13, 2001
16 //------------------------------------------------------------------------------
19 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
21 // Permission is hereby granted, free of charge, to any person obtaining
22 // a copy of this software and associated documentation files (the
23 // "Software"), to deal in the Software without restriction, including
24 // without limitation the rights to use, copy, modify, merge, publish,
25 // distribute, sublicense, and/or sell copies of the Software, and to
26 // permit persons to whom the Software is furnished to do so, subject to
27 // the following conditions:
29 // The above copyright notice and this permission notice shall be
30 // included in all copies or substantial portions of the Software.
32 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
36 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
37 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
38 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using System.Collections;
42 using System.Security;
43 using System.Security.Permissions;
45 using System.Runtime.InteropServices;
47 #if !NET_2_1 || MONOTOUCH
48 using System.Security.AccessControl;
54 public static class Directory
57 public static DirectoryInfo CreateDirectory (string path)
60 throw new ArgumentNullException ("path");
63 throw new ArgumentException ("Path is empty");
65 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
66 throw new ArgumentException ("Path contains invalid chars");
68 if (path.Trim ().Length == 0)
69 throw new ArgumentException ("Only blank characters in path");
71 if (File.Exists(path))
72 throw new IOException ("Cannot create " + path + " because a file with the same name already exists.");
74 // LAMESPEC: with .net 1.0 version this throw NotSupportedException and msdn says so too
75 // but v1.1 throws ArgumentException.
77 throw new ArgumentException ("Only ':' In path");
79 return CreateDirectoriesInternal (path);
82 #if !NET_2_1 || MONOTOUCH
83 [MonoLimitation ("DirectorySecurity not implemented")]
84 public static DirectoryInfo CreateDirectory (string path, DirectorySecurity directorySecurity)
86 return(CreateDirectory (path));
90 static DirectoryInfo CreateDirectoriesInternal (string path)
92 #if !NET_2_1 || MONOTOUCH
93 if (SecurityManager.SecurityEnabled) {
94 new FileIOPermission (FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, path).Demand ();
97 DirectoryInfo info = new DirectoryInfo (path, true);
98 if (info.Parent != null && !info.Parent.Exists)
99 info.Parent.Create ();
102 if (!MonoIO.CreateDirectory (path, out error)) {
103 // LAMESPEC: 1.1 and 1.2alpha allow CreateDirectory on a file path.
104 // So CreateDirectory ("/tmp/somefile") will succeed if 'somefile' is
105 // not a directory. However, 1.0 will throw an exception.
106 // We behave like 1.0 here (emulating 1.1-like behavior is just a matter
107 // of comparing error to ERROR_FILE_EXISTS, but it's lame to do:
108 // DirectoryInfo di = Directory.CreateDirectory (something);
109 // and having di.Exists return false afterwards.
110 // I hope we don't break anyone's code, as they should be catching
111 // the exception anyway.
112 if (error != MonoIOError.ERROR_ALREADY_EXISTS &&
113 error != MonoIOError.ERROR_FILE_EXISTS)
114 throw MonoIO.GetException (path, error);
120 public static void Delete (string path)
123 throw new ArgumentNullException ("path");
125 if (path.Length == 0)
126 throw new ArgumentException ("Path is empty");
128 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
129 throw new ArgumentException ("Path contains invalid chars");
131 if (path.Trim().Length == 0)
132 throw new ArgumentException ("Only blank characters in path");
135 throw new NotSupportedException ("Only ':' In path");
140 if (MonoIO.ExistsSymlink (path, out error)) {
141 /* RemoveDirectory maps to rmdir()
142 * which fails on symlinks (ENOTDIR)
144 success = MonoIO.DeleteFile (path, out error);
146 success = MonoIO.RemoveDirectory (path, out error);
152 * In io-layer/io.c rmdir returns error_file_not_found if directory does not exists.
153 * So maybe this could be handled somewhere else?
155 if (error == MonoIOError.ERROR_FILE_NOT_FOUND) {
156 if (File.Exists (path))
157 throw new IOException ("Directory does not exist, but a file of the same name exist.");
159 throw new DirectoryNotFoundException ("Directory does not exist.");
161 throw MonoIO.GetException (path, error);
165 static void RecursiveDelete (string path)
169 foreach (string dir in GetDirectories (path)) {
170 if (MonoIO.ExistsSymlink (dir, out error)) {
171 MonoIO.DeleteFile (dir, out error);
173 RecursiveDelete (dir);
177 foreach (string file in GetFiles (path))
180 Directory.Delete (path);
183 public static void Delete (string path, bool recursive)
185 CheckPathExceptions (path);
188 RecursiveDelete (path);
193 public static bool Exists (string path)
201 exists = MonoIO.ExistsDirectory (path, out error);
202 if (error != MonoIOError.ERROR_SUCCESS &&
203 error != MonoIOError.ERROR_PATH_NOT_FOUND &&
204 error != MonoIOError.ERROR_INVALID_HANDLE &&
205 error != MonoIOError.ERROR_ACCESS_DENIED) {
207 // INVALID_HANDLE might happen if the file is moved
208 // while testing for the existence, a kernel issue
209 // according to Larry Ewing.
211 throw MonoIO.GetException (path, error);
217 public static DateTime GetLastAccessTime (string path)
219 return File.GetLastAccessTime (path);
222 public static DateTime GetLastAccessTimeUtc (string path)
224 return GetLastAccessTime (path).ToUniversalTime ();
227 public static DateTime GetLastWriteTime (string path)
229 return File.GetLastWriteTime (path);
232 public static DateTime GetLastWriteTimeUtc (string path)
234 return GetLastWriteTime (path).ToUniversalTime ();
237 public static DateTime GetCreationTime (string path)
239 return File.GetCreationTime (path);
242 public static DateTime GetCreationTimeUtc (string path)
244 return GetCreationTime (path).ToUniversalTime ();
247 public static string GetCurrentDirectory ()
251 string result = MonoIO.GetCurrentDirectory (out error);
252 if (error != MonoIOError.ERROR_SUCCESS)
253 throw MonoIO.GetException (error);
254 #if !NET_2_1 || MONOTOUCH
255 if ((result != null) && (result.Length > 0) && SecurityManager.SecurityEnabled) {
256 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, result).Demand ();
262 public static string [] GetDirectories (string path)
264 return GetDirectories (path, "*");
267 public static string [] GetDirectories (string path, string searchPattern)
269 return GetFileSystemEntries (path, searchPattern, FileAttributes.Directory, FileAttributes.Directory);
272 #if !NET_2_1 || MONOTOUCH
273 public static string [] GetDirectories (string path, string searchPattern, SearchOption searchOption)
275 if (searchOption == SearchOption.TopDirectoryOnly)
276 return GetDirectories (path, searchPattern);
277 ArrayList all = new ArrayList ();
278 GetDirectoriesRecurse (path, searchPattern, all);
279 return (string []) all.ToArray (typeof (string));
282 static void GetDirectoriesRecurse (string path, string searchPattern, ArrayList all)
284 all.AddRange (GetDirectories (path, searchPattern));
285 foreach (string dir in GetDirectories (path))
286 GetDirectoriesRecurse (dir, searchPattern, all);
290 public static string GetDirectoryRoot (string path)
292 return new String(Path.DirectorySeparatorChar,1);
295 public static string [] GetFiles (string path)
297 return GetFiles (path, "*");
300 public static string [] GetFiles (string path, string searchPattern)
302 return GetFileSystemEntries (path, searchPattern, FileAttributes.Directory, 0);
305 #if !NET_2_1 || MONOTOUCH
306 public static string[] GetFiles (string path, string searchPattern, SearchOption searchOption)
308 if (searchOption == SearchOption.TopDirectoryOnly)
309 return GetFiles (path, searchPattern);
310 ArrayList all = new ArrayList ();
311 GetFilesRecurse (path, searchPattern, all);
312 return (string []) all.ToArray (typeof (string));
315 static void GetFilesRecurse (string path, string searchPattern, ArrayList all)
317 all.AddRange (GetFiles (path, searchPattern));
318 foreach (string dir in GetDirectories (path))
319 GetFilesRecurse (dir, searchPattern, all);
323 public static string [] GetFileSystemEntries (string path)
325 return GetFileSystemEntries (path, "*");
328 public static string [] GetFileSystemEntries (string path, string searchPattern)
330 return GetFileSystemEntries (path, searchPattern, 0, 0);
333 public static string[] GetLogicalDrives ()
335 return Environment.GetLogicalDrives ();
338 static bool IsRootDirectory (string path)
341 if (Path.DirectorySeparatorChar == '/' && path == "/")
345 if (Path.DirectorySeparatorChar == '\\')
346 if (path.Length == 3 && path.EndsWith (":\\"))
352 public static DirectoryInfo GetParent (string path)
355 throw new ArgumentNullException ("path");
356 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
357 throw new ArgumentException ("Path contains invalid characters");
358 if (path.Length == 0)
359 throw new ArgumentException ("The Path do not have a valid format");
361 // return null if the path is the root directory
362 if (IsRootDirectory (path))
365 string parent_name = Path.GetDirectoryName (path);
366 if (parent_name.Length == 0)
367 parent_name = GetCurrentDirectory();
369 return new DirectoryInfo (parent_name);
372 public static void Move (string sourceDirName, string destDirName)
374 if (sourceDirName == null)
375 throw new ArgumentNullException ("sourceDirName");
377 if (destDirName == null)
378 throw new ArgumentNullException ("destDirName");
380 if (sourceDirName.Trim ().Length == 0 || sourceDirName.IndexOfAny (Path.InvalidPathChars) != -1)
381 throw new ArgumentException ("Invalid source directory name: " + sourceDirName, "sourceDirName");
383 if (destDirName.Trim ().Length == 0 || destDirName.IndexOfAny (Path.InvalidPathChars) != -1)
384 throw new ArgumentException ("Invalid target directory name: " + destDirName, "destDirName");
386 if (sourceDirName == destDirName)
387 throw new IOException ("Source and destination path must be different.");
389 if (Exists (destDirName))
390 throw new IOException (destDirName + " already exists.");
392 if (!Exists (sourceDirName) && !File.Exists (sourceDirName))
393 throw new DirectoryNotFoundException (sourceDirName + " does not exist");
396 if (!MonoIO.MoveFile (sourceDirName, destDirName, out error))
397 throw MonoIO.GetException (error);
400 #if !NET_2_1 || MONOTOUCH
401 public static void SetAccessControl (string path, DirectorySecurity directorySecurity)
403 throw new NotImplementedException ();
407 public static void SetCreationTime (string path, DateTime creationTime)
409 File.SetCreationTime (path, creationTime);
412 public static void SetCreationTimeUtc (string path, DateTime creationTimeUtc)
414 SetCreationTime (path, creationTimeUtc.ToLocalTime ());
417 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
418 public static void SetCurrentDirectory (string path)
421 throw new ArgumentNullException ("path");
422 if (path.Trim ().Length == 0)
423 throw new ArgumentException ("path string must not be an empty string or whitespace string");
428 throw new DirectoryNotFoundException ("Directory \"" +
429 path + "\" not found.");
431 MonoIO.SetCurrentDirectory (path, out error);
432 if (error != MonoIOError.ERROR_SUCCESS)
433 throw MonoIO.GetException (path, error);
436 public static void SetLastAccessTime (string path, DateTime lastAccessTime)
438 File.SetLastAccessTime (path, lastAccessTime);
441 public static void SetLastAccessTimeUtc (string path, DateTime lastAccessTimeUtc)
443 SetLastAccessTime (path, lastAccessTimeUtc.ToLocalTime ());
446 public static void SetLastWriteTime (string path, DateTime lastWriteTime)
448 File.SetLastWriteTime (path, lastWriteTime);
451 public static void SetLastWriteTimeUtc (string path, DateTime lastWriteTimeUtc)
453 SetLastWriteTime (path, lastWriteTimeUtc.ToLocalTime ());
458 private static void CheckPathExceptions (string path)
461 throw new System.ArgumentNullException("path");
462 if (path.Length == 0)
463 throw new System.ArgumentException("Path is Empty");
464 if (path.Trim().Length == 0)
465 throw new ArgumentException ("Only blank characters in path");
466 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
467 throw new ArgumentException ("Path contains invalid chars");
470 // Does the common validation, searchPattern has already been checked for not-null
471 static string ValidateDirectoryListing (string path, string searchPattern, out bool stop)
474 throw new ArgumentNullException ("path");
476 if (path.Trim ().Length == 0)
477 throw new ArgumentException ("The Path does not have a valid format");
479 string wild = Path.Combine (path, searchPattern);
480 string wildpath = Path.GetDirectoryName (wild);
481 if (wildpath.IndexOfAny (Path.InvalidPathChars) != -1)
482 throw new ArgumentException ("Path contains invalid characters");
484 if (wildpath.IndexOfAny (Path.InvalidPathChars) != -1) {
485 if (path.IndexOfAny (SearchPattern.InvalidChars) == -1)
486 throw new ArgumentException ("Path contains invalid characters", "path");
488 throw new ArgumentException ("Pattern contains invalid characters", "pattern");
492 if (!MonoIO.ExistsDirectory (wildpath, out error)) {
493 if (error == MonoIOError.ERROR_SUCCESS) {
494 MonoIOError file_error;
495 if (MonoIO.ExistsFile (wildpath, out file_error)) {
501 if (error != MonoIOError.ERROR_PATH_NOT_FOUND)
502 throw MonoIO.GetException (wildpath, error);
504 if (wildpath.IndexOfAny (SearchPattern.WildcardChars) == -1)
505 throw new DirectoryNotFoundException ("Directory '" + wildpath + "' not found.");
507 if (path.IndexOfAny (SearchPattern.WildcardChars) == -1)
508 throw new ArgumentException ("Pattern is invalid", "searchPattern");
510 throw new ArgumentException ("Path is invalid", "path");
514 return Path.Combine (wildpath, searchPattern);
517 private static string [] GetFileSystemEntries (string path, string searchPattern, FileAttributes mask, FileAttributes attrs)
519 if (searchPattern == null)
520 throw new ArgumentNullException ("searchPattern");
521 if (searchPattern.Length == 0)
522 return new string [] {};
524 string path_with_pattern = ValidateDirectoryListing (path, searchPattern, out stop);
526 return new string [] { path_with_pattern };
529 string [] result = MonoIO.GetFileSystemEntries (path, path_with_pattern, (int) attrs, (int) mask, out error);
531 throw MonoIO.GetException (Path.GetDirectoryName (Path.Combine (path, searchPattern)), error);
537 public static string[] GetFileSystemEntries (string path, string searchPattern, SearchOption searchOption)
539 // Take the simple way home:
540 return new System.Collections.Generic.List<string> (EnumerateFileSystemEntries (path, searchPattern, searchOption)).ToArray ();
543 internal static System.Collections.Generic.IEnumerable<string> EnumerateKind (string path, string searchPattern, SearchOption searchOption, FileAttributes kind)
545 if (searchPattern == null)
546 throw new ArgumentNullException ("searchPattern");
548 if (searchPattern.Length == 0)
551 if (searchOption != SearchOption.TopDirectoryOnly && searchOption != SearchOption.AllDirectories)
552 throw new ArgumentOutOfRangeException ("searchoption");
555 string path_with_pattern = ValidateDirectoryListing (path, searchPattern, out stop);
557 yield return path_with_pattern;
563 FileAttributes rattr;
564 bool subdirs = searchOption == SearchOption.AllDirectories;
566 string s = MonoIO.FindFirst (path, path_with_pattern, out rattr, out error, out handle);
570 throw MonoIO.GetException (Path.GetDirectoryName (Path.Combine (path, searchPattern)), (MonoIOError) error);
573 if (((rattr & FileAttributes.ReparsePoint) == 0) && ((rattr & kind) != 0))
576 while ((s = MonoIO.FindNext (handle, out rattr, out error)) != null){
577 if ((rattr & FileAttributes.ReparsePoint) != 0)
579 if ((rattr & kind) != 0)
582 if (((rattr & FileAttributes.Directory) != 0) && subdirs)
583 foreach (string child in EnumerateKind (s, searchPattern, searchOption, kind))
587 MonoIO.FindClose (handle);
591 public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories (string path, string searchPattern, SearchOption searchOption)
593 return EnumerateKind (path, searchPattern, searchOption, FileAttributes.Directory);
596 public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories (string path, string searchPattern)
598 return EnumerateKind (path, searchPattern, SearchOption.TopDirectoryOnly, FileAttributes.Directory);
601 public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories (string path)
603 return EnumerateKind (path, "*", SearchOption.TopDirectoryOnly, FileAttributes.Directory);
606 public static System.Collections.Generic.IEnumerable<string> EnumerateFiles (string path, string searchPattern, SearchOption searchOption)
608 return EnumerateKind (path, searchPattern, searchOption, FileAttributes.Normal);
611 public static System.Collections.Generic.IEnumerable<string> EnumerateFiles (string path, string searchPattern)
613 return EnumerateKind (path, searchPattern, SearchOption.TopDirectoryOnly, FileAttributes.Normal);
616 public static System.Collections.Generic.IEnumerable<string> EnumerateFiles (string path)
618 return EnumerateKind (path, "*", SearchOption.TopDirectoryOnly, FileAttributes.Normal);
621 public static System.Collections.Generic.IEnumerable<string> EnumerateFileSystemEntries (string path, string searchPattern, SearchOption searchOption)
623 return EnumerateKind (path, searchPattern, searchOption, FileAttributes.Normal | FileAttributes.Directory);
626 public static System.Collections.Generic.IEnumerable<string> EnumerateFileSystemEntries (string path, string searchPattern)
628 return EnumerateKind (path, searchPattern, SearchOption.TopDirectoryOnly, FileAttributes.Normal | FileAttributes.Directory);
631 public static System.Collections.Generic.IEnumerable<string> EnumerateFileSystemEntries (string path)
633 return EnumerateKind (path, "*", SearchOption.TopDirectoryOnly, FileAttributes.Normal | FileAttributes.Directory);
638 #if !NET_2_1 || MONOTOUCH
639 [MonoNotSupported ("DirectorySecurity isn't implemented")]
640 public static DirectorySecurity GetAccessControl (string path, AccessControlSections includeSections)
642 throw new PlatformNotSupportedException ();
645 [MonoNotSupported ("DirectorySecurity isn't implemented")]
646 public static DirectorySecurity GetAccessControl (string path)
648 throw new PlatformNotSupportedException ();