[w32file] Move MonoIO.Find{First,Next,Close} to managed
[mono.git] / mcs / class / corlib / System.IO / DirectoryInfo.cs
1 // 
2 // System.IO.DirectoryInfo.cs 
3 //
4 // Authors:
5 //   Miguel de Icaza, miguel@ximian.com
6 //   Jim Richardson, develop@wtfo-guru.com
7 //   Dan Lewis, dihlewis@yahoo.co.uk
8 //   Sebastien Pouliot  <sebastien@ximian.com>
9 //   Marek Safar  <marek.safar@gmail.com>
10 //
11 // Copyright (C) 2002 Ximian, Inc.
12 // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
13 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
14 // Copyright (C) 2014 Xamarin, Inc (http://www.xamarin.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System.Collections;
37 using System.Collections.Generic;
38 using System.Runtime.InteropServices;
39 using System.Runtime.Serialization;
40 using System.Security;
41 using System.Text;
42 using System.Security.AccessControl;
43
44 using Microsoft.Win32.SafeHandles;
45
46 namespace System.IO {
47         
48         [Serializable]
49         [ComVisible (true)]
50         public sealed class DirectoryInfo : FileSystemInfo {
51
52                 private string current;
53                 private string parent;
54         
55                 public DirectoryInfo (string path) : this (path, false)
56                 {
57                 }
58
59                 internal DirectoryInfo (string path, bool simpleOriginalPath)
60                 {
61                         CheckPath (path);
62
63                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
64
65                         FullPath = Path.GetFullPath (path);
66                         if (simpleOriginalPath)
67                                 OriginalPath = Path.GetFileName (FullPath);
68                         else
69                                 OriginalPath = path;
70
71                         Initialize ();
72                 }
73
74                 private DirectoryInfo (SerializationInfo info, StreamingContext context)
75                         : base (info, context)
76                 {
77                         Initialize ();
78                 }
79
80                 void Initialize ()
81                 {
82                         int len = FullPath.Length - 1;
83                         if ((len > 1) && (FullPath [len] == Path.DirectorySeparatorChar))
84                                 len--;
85                         int last = FullPath.LastIndexOf (Path.DirectorySeparatorChar, len);
86                         if ((last == -1) || ((last == 0) && (len == 0))) {
87                                 current = FullPath;
88                                 parent = null;
89                         } else {
90                                 current = FullPath.Substring (last + 1, len - last);
91                                 if (last == 0 && !Environment.IsRunningOnWindows)
92                                         parent = Path.DirectorySeparatorStr;
93                                 else
94                                         parent = FullPath.Substring (0, last);
95                                 // adjust for drives, i.e. a special case for windows
96                                 if (Environment.IsRunningOnWindows) {
97                                         if ((parent.Length == 2) && (parent [1] == ':') && Char.IsLetter (parent [0]))
98                                                 parent += Path.DirectorySeparatorChar;
99                                 }
100                         }
101                 }
102
103                 // properties
104
105                 public override bool Exists {
106                         get {
107                                 if (_dataInitialised == -1)
108                                         Refresh ();
109
110                                 if (_data.fileAttributes == MonoIO.InvalidFileAttributes)
111                                         return false;
112
113                                 if ((_data.fileAttributes & FileAttributes.Directory) == 0)
114                                         return false;
115
116                                 return true;
117                         }
118                 }
119
120                 public override string Name {
121                         get { return current; }
122                 }
123
124                 public DirectoryInfo Parent {
125                         get {
126                                 if ((parent == null) || (parent.Length == 0))
127                                         return null;
128                                 return new DirectoryInfo (parent);
129                         }
130                 }
131
132                 public DirectoryInfo Root {
133                         get {
134                                 string root = Path.GetPathRoot (FullPath);
135                                 if (root == null)
136                                         return null;
137
138                                 return new DirectoryInfo (root);
139                         }
140                 }
141
142                 // creational methods
143
144                 public void Create ()
145                 {
146                         Directory.CreateDirectory (FullPath);
147                 }
148
149                 public DirectoryInfo CreateSubdirectory (string path)
150                 {
151                         CheckPath (path);
152
153                         path = Path.Combine (FullPath, path);
154                         Directory.CreateDirectory (path);
155                         return new DirectoryInfo (path);
156                 }
157
158                 // directory listing methods
159
160                 public FileInfo [] GetFiles ()
161                 {
162                         return GetFiles ("*");
163                 }
164
165                 public FileInfo [] GetFiles (string searchPattern)
166                 {
167                         if (searchPattern == null)
168                                 throw new ArgumentNullException ("searchPattern");
169
170                         string [] names = Directory.GetFiles (FullPath, searchPattern);
171
172                         FileInfo[] infos = new FileInfo [names.Length];
173                         int i = 0;
174                         foreach (string name in names)
175                                 infos [i++] = new FileInfo (name);
176
177                         return infos;
178                 }
179
180                 public DirectoryInfo [] GetDirectories ()
181                 {
182                         return GetDirectories ("*");
183                 }
184
185                 public DirectoryInfo [] GetDirectories (string searchPattern)
186                 {
187                         if (searchPattern == null)
188                                 throw new ArgumentNullException ("searchPattern");
189
190                         string [] names = Directory.GetDirectories (FullPath, searchPattern);
191
192                         DirectoryInfo[] infos = new DirectoryInfo [names.Length];
193                         int i = 0;
194                         foreach (string name in names)
195                                 infos [i++] = new DirectoryInfo (name);
196
197                         return infos;
198                 }
199
200                 public FileSystemInfo [] GetFileSystemInfos ()
201                 {
202                         return GetFileSystemInfos ("*");
203                 }
204
205                 public FileSystemInfo [] GetFileSystemInfos (string searchPattern)
206                 {
207                         return GetFileSystemInfos (searchPattern, SearchOption.TopDirectoryOnly);
208                 }
209
210                 public
211                 FileSystemInfo [] GetFileSystemInfos (string searchPattern, SearchOption searchOption)
212                 {
213                         if (searchPattern == null)
214                                 throw new ArgumentNullException ("searchPattern");
215                         if (searchOption != SearchOption.TopDirectoryOnly && searchOption != SearchOption.AllDirectories)
216                                 throw new ArgumentOutOfRangeException ("searchOption", "Must be TopDirectoryOnly or AllDirectories");
217                         if (!Directory.Exists (FullPath))
218                                 throw new IOException ("Invalid directory");
219
220                         List<FileSystemInfo> infos = new List<FileSystemInfo> ();
221                         InternalGetFileSystemInfos (searchPattern, searchOption, infos);
222                         return infos.ToArray ();
223                 }
224
225                 void InternalGetFileSystemInfos (string searchPattern, SearchOption searchOption, List<FileSystemInfo> infos)
226                 {
227                         // UnauthorizedAccessExceptions might happen here and break everything for SearchOption.AllDirectories
228                         string [] dirs = Directory.GetDirectories (FullPath, searchPattern);
229                         string [] files = Directory.GetFiles (FullPath, searchPattern);
230
231                         Array.ForEach<string> (dirs, (dir) => { infos.Add (new DirectoryInfo (dir)); });
232                         Array.ForEach<string> (files, (file) => { infos.Add (new FileInfo (file)); });
233                         if (dirs.Length == 0 || searchOption == SearchOption.TopDirectoryOnly)
234                                 return;
235
236                         foreach (string dir in dirs) {
237                                 DirectoryInfo dinfo = new DirectoryInfo (dir);
238                                 dinfo.InternalGetFileSystemInfos (searchPattern, searchOption, infos);
239                         }
240                 }
241
242                 // directory management methods
243
244                 public override void Delete ()
245                 {
246                         Delete (false);
247                 }
248
249                 public void Delete (bool recursive)
250                 {
251                         Directory.Delete (FullPath, recursive);
252                 }
253
254                 public void MoveTo (string destDirName)
255                 {
256                         if (destDirName == null)
257                                 throw new ArgumentNullException ("destDirName");
258                         if (destDirName.Length == 0)
259                                 throw new ArgumentException ("An empty file name is not valid.", "destDirName");
260
261                         Directory.Move (FullPath, Path.GetFullPath (destDirName));
262                         FullPath = OriginalPath = destDirName;
263                         Initialize ();
264                 }
265
266                 public override string ToString ()
267                 {
268                         return OriginalPath;
269                 }
270
271                 public DirectoryInfo[] GetDirectories (string searchPattern, SearchOption searchOption)
272                 {
273                     //NULL-check of searchPattern is done in Directory.GetDirectories
274                         string [] names = Directory.GetDirectories (FullPath, searchPattern, searchOption);
275                         //Convert the names to DirectoryInfo instances
276                         DirectoryInfo[] infos = new DirectoryInfo [names.Length];
277                         for (int i = 0; i<names.Length; ++i){
278                                 string name = names[i];
279                                 infos [i] = new DirectoryInfo (name);
280                         }
281                         return infos;
282                 }       
283
284                 internal int GetFilesSubdirs (ArrayList l, string pattern)
285                 {
286                         int count;
287                         FileInfo [] thisdir = null;
288
289                         try {
290                                 thisdir = GetFiles (pattern);
291                         } catch (System.UnauthorizedAccessException){
292                                 return 0;
293                         }
294                         
295                         count = thisdir.Length;
296                         l.Add (thisdir);
297
298                         foreach (DirectoryInfo subdir in GetDirectories ()){
299                                 count += subdir.GetFilesSubdirs (l, pattern);
300                         }
301                         return count;
302                 }
303                 
304                 public FileInfo[] GetFiles (string searchPattern, SearchOption searchOption)
305                 {
306                         switch (searchOption) {
307                         case SearchOption.TopDirectoryOnly:
308                                 return GetFiles (searchPattern);
309                         case SearchOption.AllDirectories: {
310                                 ArrayList groups = new ArrayList ();
311                                 int count = GetFilesSubdirs (groups, searchPattern);
312                                 int current = 0;
313                                 
314                                 FileInfo [] all = new FileInfo [count];
315                                 foreach (FileInfo [] p in groups){
316                                         p.CopyTo (all, current);
317                                         current += p.Length;
318                                 }
319                                 return all;
320                         }
321                         default:
322                                 string msg = Locale.GetText ("Invalid enum value '{0}' for '{1}'.", searchOption, "SearchOption");
323                                 throw new ArgumentOutOfRangeException ("searchOption", msg);
324                         }
325                 }
326
327                 // access control methods
328
329                 [MonoLimitation ("DirectorySecurity isn't implemented")]
330                 public void Create (DirectorySecurity directorySecurity)
331                 {
332                         if (directorySecurity != null)
333                                 throw new UnauthorizedAccessException ();
334                         Create ();
335                 }
336
337                 [MonoLimitation ("DirectorySecurity isn't implemented")]
338                 public DirectoryInfo CreateSubdirectory (string path, DirectorySecurity directorySecurity)
339                 {
340                         if (directorySecurity != null)
341                                 throw new UnauthorizedAccessException ();
342                         return CreateSubdirectory (path);
343                 }
344
345                 public DirectorySecurity GetAccessControl ()
346                 {
347                         return Directory.GetAccessControl (FullPath);
348                 }
349
350                 public DirectorySecurity GetAccessControl (AccessControlSections includeSections)
351                 {
352                         return Directory.GetAccessControl (FullPath, includeSections);
353                 }
354
355                 public void SetAccessControl (DirectorySecurity directorySecurity)
356                 {
357                         Directory.SetAccessControl (FullPath, directorySecurity);
358                 }
359
360
361                 public IEnumerable<DirectoryInfo> EnumerateDirectories ()
362                 {
363                         return EnumerateDirectories ("*", SearchOption.TopDirectoryOnly);
364                 }
365
366                 public IEnumerable<DirectoryInfo> EnumerateDirectories (string searchPattern)
367                 {
368                         return EnumerateDirectories (searchPattern, SearchOption.TopDirectoryOnly);
369                 }
370
371                 public IEnumerable<DirectoryInfo> EnumerateDirectories (string searchPattern, SearchOption searchOption)
372                 {
373                         if (searchPattern == null)
374                                 throw new ArgumentNullException ("searchPattern");
375
376                         return CreateEnumerateDirectoriesIterator (searchPattern, searchOption);
377                 }
378
379                 IEnumerable<DirectoryInfo> CreateEnumerateDirectoriesIterator (string searchPattern, SearchOption searchOption)
380                 {
381                         foreach (string name in Directory.EnumerateDirectories (FullPath, searchPattern, searchOption))
382                                 yield return new DirectoryInfo (name);
383                 }
384
385                 public IEnumerable<FileInfo> EnumerateFiles ()
386                 {
387                         return EnumerateFiles ("*", SearchOption.TopDirectoryOnly);
388                 }
389
390                 public IEnumerable<FileInfo> EnumerateFiles (string searchPattern)
391                 {
392                         return EnumerateFiles (searchPattern, SearchOption.TopDirectoryOnly);
393                 }
394
395                 public IEnumerable<FileInfo> EnumerateFiles (string searchPattern, SearchOption searchOption)
396                 {
397                         if (searchPattern == null)
398                                 throw new ArgumentNullException ("searchPattern");
399
400                         return CreateEnumerateFilesIterator (searchPattern, searchOption);
401                 }
402
403                 IEnumerable<FileInfo> CreateEnumerateFilesIterator (string searchPattern, SearchOption searchOption)
404                 {
405                         foreach (string name in Directory.EnumerateFiles (FullPath, searchPattern, searchOption))
406                                 yield return new FileInfo (name);
407                 }
408
409                 public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos ()
410                 {
411                         return EnumerateFileSystemInfos ("*", SearchOption.TopDirectoryOnly);
412                 }
413
414                 public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos (string searchPattern)
415                 {
416                         return EnumerateFileSystemInfos (searchPattern, SearchOption.TopDirectoryOnly);
417                 }
418
419                 public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos (string searchPattern, SearchOption searchOption)
420                 {
421                         if (searchPattern == null)
422                                 throw new ArgumentNullException ("searchPattern");
423                         if (searchOption != SearchOption.TopDirectoryOnly && searchOption != SearchOption.AllDirectories)
424                                 throw new ArgumentOutOfRangeException ("searchoption");
425
426                         return EnumerateFileSystemInfos (FullPath, searchPattern, searchOption);
427                 }
428
429                 static internal IEnumerable<FileSystemInfo> EnumerateFileSystemInfos (string basePath, string searchPattern, SearchOption searchOption)
430                 {
431                         Path.Validate (basePath);
432
433                         SafeFindHandle findHandle = null;
434
435                         try {
436                                 string filePath;
437                                 int nativeAttrs;
438
439                                 string basePathWithPattern = Path.Combine (basePath, searchPattern);
440
441                                 int nativeError;
442                                 try {} finally {
443                                         findHandle = new SafeFindHandle (MonoIO.FindFirstFile (basePathWithPattern, out filePath, out nativeAttrs, out nativeError));
444                                 }
445
446                                 if (findHandle.IsInvalid) {
447                                         MonoIOError error = (MonoIOError) nativeError;
448                                         if (error != MonoIOError.ERROR_FILE_NOT_FOUND)
449                                                 throw MonoIO.GetException (Path.GetDirectoryName (basePathWithPattern), error);
450
451                                         yield break;
452                                 }
453
454                                 do {
455                                         if (filePath == null)
456                                                 yield break;
457
458                                         if (filePath == "." || filePath == "..")
459                                                 continue;
460
461                                         FileAttributes attrs = (FileAttributes) nativeAttrs;
462
463                                         string fullPath = Path.Combine (basePath, filePath);
464
465                                         if ((attrs & FileAttributes.ReparsePoint) == 0) {
466                                                 if ((attrs & FileAttributes.Directory) != 0)
467                                                         yield return new DirectoryInfo (fullPath);
468                                                 else
469                                                         yield return new FileInfo (fullPath);
470                                         }
471
472                                         if ((attrs & FileAttributes.Directory) != 0 && searchOption == SearchOption.AllDirectories) {
473                                                 foreach (FileSystemInfo child in EnumerateFileSystemInfos (fullPath, searchPattern, searchOption))
474                                                         yield return child;
475                                         }
476                                 } while (MonoIO.FindNextFile (findHandle.DangerousGetHandle (), out filePath, out nativeAttrs, out int _));
477                         } finally {
478                                 if (findHandle != null)
479                                         findHandle.Dispose ();
480                         }
481                 }
482                 
483                 internal void CheckPath (string path)
484                 {
485                         if (path == null)
486                                 throw new ArgumentNullException ("path");
487                         if (path.Length == 0)
488                                 throw new ArgumentException ("An empty file name is not valid.");
489                         if (path.IndexOfAny (Path.InvalidPathChars) != -1)
490                                 throw new ArgumentException ("Illegal characters in path.");
491                         if (Environment.IsRunningOnWindows) {
492                                 int idx = path.IndexOf (':');
493                                 if (idx >= 0 && idx != 1)
494                                         throw new ArgumentException ("path");
495                         }
496                 }
497         }
498 }