[corlib] Enumerate all subdirectories including the first one. Fixes #22857
[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 namespace System.IO {
45         
46         [Serializable]
47         [ComVisible (true)]
48         public sealed class DirectoryInfo : FileSystemInfo {
49
50                 private string current;
51                 private string parent;
52         
53                 public DirectoryInfo (string path) : this (path, false)
54                 {
55                 }
56
57                 internal DirectoryInfo (string path, bool simpleOriginalPath)
58                 {
59                         CheckPath (path);
60
61                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
62
63                         FullPath = Path.GetFullPath (path);
64                         if (simpleOriginalPath)
65                                 OriginalPath = Path.GetFileName (path);
66                         else
67                                 OriginalPath = path;
68
69                         Initialize ();
70                 }
71
72                 private DirectoryInfo (SerializationInfo info, StreamingContext context)
73                         : base (info, context)
74                 {
75                         Initialize ();
76                 }
77
78                 void Initialize ()
79                 {
80                         int len = FullPath.Length - 1;
81                         if ((len > 1) && (FullPath [len] == Path.DirectorySeparatorChar))
82                                 len--;
83                         int last = FullPath.LastIndexOf (Path.DirectorySeparatorChar, len);
84                         if ((last == -1) || ((last == 0) && (len == 0))) {
85                                 current = FullPath;
86                                 parent = null;
87                         } else {
88                                 current = FullPath.Substring (last + 1, len - last);
89                                 if (last == 0 && !Environment.IsRunningOnWindows)
90                                         parent = Path.DirectorySeparatorStr;
91                                 else
92                                         parent = FullPath.Substring (0, last);
93                                 // adjust for drives, i.e. a special case for windows
94                                 if (Environment.IsRunningOnWindows) {
95                                         if ((parent.Length == 2) && (parent [1] == ':') && Char.IsLetter (parent [0]))
96                                                 parent += Path.DirectorySeparatorChar;
97                                 }
98                         }
99                 }
100
101                 // properties
102
103                 public override bool Exists {
104                         get {
105                                 Refresh (false);
106
107                                 if (stat.Attributes == MonoIO.InvalidFileAttributes)
108                                         return false;
109
110                                 if ((stat.Attributes & FileAttributes.Directory) == 0)
111                                         return false;
112
113                                 return true;
114                         }
115                 }
116
117                 public override string Name {
118                         get { return current; }
119                 }
120
121                 public DirectoryInfo Parent {
122                         get {
123                                 if ((parent == null) || (parent.Length == 0))
124                                         return null;
125                                 return new DirectoryInfo (parent);
126                         }
127                 }
128
129                 public DirectoryInfo Root {
130                         get {
131                                 string root = Path.GetPathRoot (FullPath);
132                                 if (root == null)
133                                         return null;
134
135                                 return new DirectoryInfo (root);
136                         }
137                 }
138
139                 // creational methods
140
141                 public void Create ()
142                 {
143                         Directory.CreateDirectory (FullPath);
144                 }
145
146                 public DirectoryInfo CreateSubdirectory (string path)
147                 {
148                         CheckPath (path);
149
150                         path = Path.Combine (FullPath, path);
151                         Directory.CreateDirectory (path);
152                         return new DirectoryInfo (path);
153                 }
154
155                 // directory listing methods
156
157                 public FileInfo [] GetFiles ()
158                 {
159                         return GetFiles ("*");
160                 }
161
162                 public FileInfo [] GetFiles (string searchPattern)
163                 {
164                         if (searchPattern == null)
165                                 throw new ArgumentNullException ("searchPattern");
166
167                         string [] names = Directory.GetFiles (FullPath, searchPattern);
168
169                         FileInfo[] infos = new FileInfo [names.Length];
170                         int i = 0;
171                         foreach (string name in names)
172                                 infos [i++] = new FileInfo (name);
173
174                         return infos;
175                 }
176
177                 public DirectoryInfo [] GetDirectories ()
178                 {
179                         return GetDirectories ("*");
180                 }
181
182                 public DirectoryInfo [] GetDirectories (string searchPattern)
183                 {
184                         if (searchPattern == null)
185                                 throw new ArgumentNullException ("searchPattern");
186
187                         string [] names = Directory.GetDirectories (FullPath, searchPattern);
188
189                         DirectoryInfo[] infos = new DirectoryInfo [names.Length];
190                         int i = 0;
191                         foreach (string name in names)
192                                 infos [i++] = new DirectoryInfo (name);
193
194                         return infos;
195                 }
196
197                 public FileSystemInfo [] GetFileSystemInfos ()
198                 {
199                         return GetFileSystemInfos ("*");
200                 }
201
202                 public FileSystemInfo [] GetFileSystemInfos (string searchPattern)
203                 {
204                         return GetFileSystemInfos (searchPattern, SearchOption.TopDirectoryOnly);
205                 }
206
207 #if NET_4_0
208                 public
209 #endif
210                 FileSystemInfo [] GetFileSystemInfos (string searchPattern, SearchOption searchOption)
211                 {
212                         if (searchPattern == null)
213                                 throw new ArgumentNullException ("searchPattern");
214                         if (searchOption != SearchOption.TopDirectoryOnly && searchOption != SearchOption.AllDirectories)
215                                 throw new ArgumentOutOfRangeException ("searchOption", "Must be TopDirectoryOnly or AllDirectories");
216                         if (!Directory.Exists (FullPath))
217                                 throw new IOException ("Invalid directory");
218
219                         List<FileSystemInfo> infos = new List<FileSystemInfo> ();
220                         InternalGetFileSystemInfos (searchPattern, searchOption, infos);
221                         return infos.ToArray ();
222                 }
223
224                 void InternalGetFileSystemInfos (string searchPattern, SearchOption searchOption, List<FileSystemInfo> infos)
225                 {
226                         // UnauthorizedAccessExceptions might happen here and break everything for SearchOption.AllDirectories
227                         string [] dirs = Directory.GetDirectories (FullPath, searchPattern);
228                         string [] files = Directory.GetFiles (FullPath, searchPattern);
229
230                         Array.ForEach<string> (dirs, (dir) => { infos.Add (new DirectoryInfo (dir)); });
231                         Array.ForEach<string> (files, (file) => { infos.Add (new FileInfo (file)); });
232                         if (dirs.Length == 0 || searchOption == SearchOption.TopDirectoryOnly)
233                                 return;
234
235                         foreach (string dir in dirs) {
236                                 DirectoryInfo dinfo = new DirectoryInfo (dir);
237                                 dinfo.InternalGetFileSystemInfos (searchPattern, searchOption, infos);
238                         }
239                 }
240
241                 // directory management methods
242
243                 public override void Delete ()
244                 {
245                         Delete (false);
246                 }
247
248                 public void Delete (bool recursive)
249                 {
250                         Directory.Delete (FullPath, recursive);
251                 }
252
253                 public void MoveTo (string destDirName)
254                 {
255                         if (destDirName == null)
256                                 throw new ArgumentNullException ("destDirName");
257                         if (destDirName.Length == 0)
258                                 throw new ArgumentException ("An empty file name is not valid.", "destDirName");
259
260                         Directory.Move (FullPath, Path.GetFullPath (destDirName));
261                         FullPath = OriginalPath = destDirName;
262                         Initialize ();
263                 }
264
265                 public override string ToString ()
266                 {
267                         return OriginalPath;
268                 }
269
270                 public DirectoryInfo[] GetDirectories (string searchPattern, SearchOption searchOption)
271                 {
272                     //NULL-check of searchPattern is done in Directory.GetDirectories
273                         string [] names = Directory.GetDirectories (FullPath, searchPattern, searchOption);
274                         //Convert the names to DirectoryInfo instances
275                         DirectoryInfo[] infos = new DirectoryInfo [names.Length];
276                         for (int i = 0; i<names.Length; ++i){
277                                 string name = names[i];
278                                 infos [i] = new DirectoryInfo (name);
279                         }
280                         return infos;
281                 }       
282
283                 internal int GetFilesSubdirs (ArrayList l, string pattern)
284                 {
285                         int count;
286                         FileInfo [] thisdir = null;
287
288                         try {
289                                 thisdir = GetFiles (pattern);
290                         } catch (System.UnauthorizedAccessException){
291                                 return 0;
292                         }
293                         
294                         count = thisdir.Length;
295                         l.Add (thisdir);
296
297                         foreach (DirectoryInfo subdir in GetDirectories ()){
298                                 count += subdir.GetFilesSubdirs (l, pattern);
299                         }
300                         return count;
301                 }
302                 
303                 public FileInfo[] GetFiles (string searchPattern, SearchOption searchOption)
304                 {
305                         switch (searchOption) {
306                         case SearchOption.TopDirectoryOnly:
307                                 return GetFiles (searchPattern);
308                         case SearchOption.AllDirectories: {
309                                 ArrayList groups = new ArrayList ();
310                                 int count = GetFilesSubdirs (groups, searchPattern);
311                                 int current = 0;
312                                 
313                                 FileInfo [] all = new FileInfo [count];
314                                 foreach (FileInfo [] p in groups){
315                                         p.CopyTo (all, current);
316                                         current += p.Length;
317                                 }
318                                 return all;
319                         }
320                         default:
321                                 string msg = Locale.GetText ("Invalid enum value '{0}' for '{1}'.", searchOption, "SearchOption");
322                                 throw new ArgumentOutOfRangeException ("searchOption", msg);
323                         }
324                 }
325
326                 // access control methods
327
328                 [MonoLimitation ("DirectorySecurity isn't implemented")]
329                 public void Create (DirectorySecurity directorySecurity)
330                 {
331                         if (directorySecurity != null)
332                                 throw new UnauthorizedAccessException ();
333                         Create ();
334                 }
335
336                 [MonoLimitation ("DirectorySecurity isn't implemented")]
337                 public DirectoryInfo CreateSubdirectory (string path, DirectorySecurity directorySecurity)
338                 {
339                         if (directorySecurity != null)
340                                 throw new UnauthorizedAccessException ();
341                         return CreateSubdirectory (path);
342                 }
343
344                 public DirectorySecurity GetAccessControl ()
345                 {
346                         return Directory.GetAccessControl (FullPath);
347                 }
348
349                 public DirectorySecurity GetAccessControl (AccessControlSections includeSections)
350                 {
351                         return Directory.GetAccessControl (FullPath, includeSections);
352                 }
353
354                 public void SetAccessControl (DirectorySecurity directorySecurity)
355                 {
356                         Directory.SetAccessControl (FullPath, directorySecurity);
357                 }
358
359 #if NET_4_0
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 full, string searchPattern, SearchOption searchOption)
430                 {
431                         string path_with_pattern = Path.Combine (full, searchPattern);
432                         IntPtr handle;
433                         MonoIOError error;
434                         FileAttributes rattr;
435                         bool subdirs = searchOption == SearchOption.AllDirectories;
436
437                         Path.Validate (full);
438                         
439                         string s = MonoIO.FindFirst (full, path_with_pattern, out rattr, out error, out handle);
440                         if (s == null)
441                                 yield break;
442                         if (error != 0)
443                                 throw MonoIO.GetException (Path.GetDirectoryName (path_with_pattern), (MonoIOError) error);
444
445                         try {
446                                 do {
447                                         if (((rattr & FileAttributes.ReparsePoint) == 0)){
448                                                 if ((rattr & FileAttributes.Directory) != 0)
449                                                         yield return new DirectoryInfo (s);
450                                                 else
451                                                         yield return new FileInfo (s);
452                                         }
453
454                                         if (((rattr & FileAttributes.Directory) != 0) && subdirs)
455                                                 foreach (FileSystemInfo child in EnumerateFileSystemInfos (s, searchPattern, searchOption))
456                                                         yield return child;
457
458                                 } while ((s = MonoIO.FindNext (handle, out rattr, out error)) != null);
459                         } finally {
460                                 MonoIO.FindClose (handle);
461                         }
462                 }
463                 
464                 
465 #endif
466         }
467 }