Mono.Unix.Native.Syscall.readdir_rを使ってディレクトリを再帰的に走査する。

// gmcs DirectoryEnumeration.cs /r:Mono.Posix.dll
// mono DirectoryEnumeration.exe ./

using System;
using Mono.Unix.Native;

public class DirectoryEnumeration {
  private enum DirentType : byte {
    DT_UNKNOWN = 0,
    DT_FIFO = 1,
    DT_CHR = 2,
    DT_DIR = 4,
    DT_BLK = 6,
    DT_REG = 8,
    DT_LNK = 10,
    DT_SOCK = 12,
    DT_WHT = 14
  };

  static void Main(string[] args)
  {
    Enumerate(args[0], 0);
  }

  private static void Enumerate(string dir, int depth)
  {
    IntPtr dirp = IntPtr.Zero;
    string indent = new string(' ', depth);

    try
    {
      dirp = Syscall.opendir(dir);

      // ディレクトリを開けなかった場合
      if (IntPtr.Zero.Equals(dirp)) return;

      for (; ; )
      {
        IntPtr result;
        Dirent dirent = new Dirent();

        if (0 != Syscall.readdir_r(dirp, dirent, out result)) break;

        // すべてのエントリを読んだ場合
        if (IntPtr.Zero.Equals(result)) break;

        // 削除されたエントリだった場合
        if (0 == dirent.d_ino) continue;

        // エントリの種類を調べる
        switch((DirentType)dirent.d_type)
        {
          case DirentType.DT_DIR:
            // ディレクトリ
            Console.WriteLine("{0}{1}/", indent, dirent.d_name);

            if ("." == dirent.d_name)
            {
              // 自分自身
            }
            else if (".." == dirent.d_name)
            {
              // 親ディレクトリ
            }
            else
            {
              string subdir = dir;

              if (!subdir.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) subdir += System.IO.Path.DirectorySeparatorChar;

              subdir += dirent.d_name;

              // 子ディレクトリを再帰的に走査する
              Enumerate(subdir, depth + 1);
            }

            break;

          case DirentType.DT_REG:
            // 普通のファイル
            Console.WriteLine("{0}{1}", indent, dirent.d_name);
            break;

          default:
            // それ以外(シンボリックリンク、ブロックデバイス、ソケット等)
            Console.WriteLine("{0}{1}", indent, dirent.d_name);
            break;
        }
      }
    }
    finally
    {
      if (!IntPtr.Zero.Equals(dirp))
      {
        Syscall.closedir(dirp);
      }
    }
  }
}