unsafe static extern FTSENT *fts_set(IntPtr fts_handle, FTSENT *ent, int options);
unsafe static IList <DirectoryEntry> Scan(string path, bool recurse, Predicate <string> cb_shouldskipdir) { if (recurse) { bool first = true; IntPtr str = Marshal.StringToHGlobalAnsi(path); try { IntPtr[] arr = new IntPtr[] { str, IntPtr.Zero }; IntPtr handle; fixed(IntPtr *arg = arr) handle = fts_open(new IntPtr(arg), FTS_NOCHDIR | FTS_PHYSICAL, IntPtr.Zero); if (handle == IntPtr.Zero) { throw new Exception("failed to fts_open"); } try { FTSENT *ent = fts_read(handle); if (ent == null) { return(__empty); } List <DirectoryEntry> ret = new List <DirectoryEntry>(); while (ent != null) { //Console.WriteLine("ent: " + ent->ToString()); bool isfile; if (ent->fts_info == FTS_F) { isfile = true; } else if (ent->fts_info == FTS_D) { isfile = false; } else { ent = fts_read(handle); continue; } bool skip = false; string fullpath = Marshal.PtrToStringAnsi(ent->fts_path); if (!isfile && cb_shouldskipdir != null) { if (cb_shouldskipdir != null && cb_shouldskipdir(fullpath)) { fts_set(handle, ent, FTS_SKIP); skip = true; } } if (!skip) { if (first) { first = false; } else { long mtime = (long)ent->fts_statp->st_mtimespec.tv_sec; ret.Add(new DirectoryEntry(fullpath, mtime, isfile)); } } ent = fts_read(handle); } return(ret); } finally { fts_close(handle); } } finally { Marshal.FreeHGlobal(str); } } else { #if !HAVE_MONO_POSIX // I've verified that this returns the exact same timestamps as the other branch of code // on linux + mac. In theory, this should perform just as well, but unsure. We can optimize // further later with p/invoke if necessary var ret = new List <DirectoryEntry>(); DirectoryInfo info = new DirectoryInfo(path); foreach (var entry in info.EnumerateFileSystemInfos()) { var attrs = entry.Attributes & ~(FileAttributes.Hidden | FileAttributes.ReadOnly); if ((attrs & FileAttributes.Directory) != 0) { ret.Add(new DirectoryEntry(path, entry.FullName, 0, false)); } else { ret.Add(new DirectoryEntry(path, entry.FullName, (entry.LastWriteTimeUtc.Ticks - EPOCH_TICKS) / 10000000, true)); } } return(ret); #else var ret = new List <DirectoryEntry>(); IntPtr dirp = Syscall.opendir(path); if (dirp == IntPtr.Zero) { throw new Exception("opendir: error " + Stdlib.GetLastError()); } try { do { Dirent d = Syscall.readdir(dirp); if (d == null) { Errno rc = Stdlib.GetLastError(); if (rc == Errno.ENOENT || rc == 0) { break; // done } throw new Exception("readdir: error " + rc); } if (d.d_type == READDIR_ISFILE) { Stat st; string fn = Path.Combine(path, d.d_name); if (Syscall.stat(fn, out st) != 0) { throw new Exception("stat \"" + fn + "\": error " + Stdlib.GetLastError()); } ret.Add(new DirectoryEntry(path, d.d_name, st.st_mtime, true)); } else if (d.d_type == READDIR_ISDIRECTORY) { if (d.d_name == "." || d.d_name == "..") { continue; } ret.Add(new DirectoryEntry(path, d.d_name, 0, false)); } }while (dirp != IntPtr.Zero); } finally { Syscall.closedir(dirp); } return(ret); #endif } }