is_file/is_dir/read_dir: more uniform treatment of errors and boundary cases, notably for symlinks in ssh;
authorwenzelm
Wed, 14 Nov 2018 16:26:58 +0100
changeset 69300 8b6ab9989bcd
parent 69299 2fd070377c99
child 69301 f6c17b9e1104
is_file/is_dir/read_dir: more uniform treatment of errors and boundary cases, notably for symlinks in ssh;
src/Pure/General/file.ML
src/Pure/General/file.scala
src/Pure/General/ssh.scala
--- a/src/Pure/General/file.ML	Wed Nov 14 11:51:03 2018 +0100
+++ b/src/Pure/General/file.ML	Wed Nov 14 16:26:58 2018 +0100
@@ -15,6 +15,7 @@
   val exists: Path.T -> bool
   val rm: Path.T -> unit
   val is_dir: Path.T -> bool
+  val is_file: Path.T -> bool
   val check_dir: Path.T -> Path.T
   val check_file: Path.T -> Path.T
   val open_dir: (OS.FileSys.dirstream -> 'a) -> Path.T -> 'a
@@ -74,15 +75,16 @@
 
 val rm = OS.FileSys.remove o platform_path;
 
-fun is_dir path =
-  the_default false (try OS.FileSys.isDir (platform_path path));
+fun test_dir path = the_default false (try OS.FileSys.isDir (platform_path path));
+fun is_dir path = exists path andalso test_dir path;
+fun is_file path = exists path andalso not (test_dir path);
 
 fun check_dir path =
-  if exists path andalso is_dir path then path
+  if is_dir path then path
   else error ("No such directory: " ^ Path.print (Path.expand path));
 
 fun check_file path =
-  if exists path andalso not (is_dir path) then path
+  if is_file path then path
   else error ("No such file: " ^ Path.print (Path.expand path));
 
 
@@ -106,13 +108,14 @@
 
 (* directory content *)
 
-fun fold_dir f path a = open_dir (fn stream =>
-  let
-    fun read x =
-      (case OS.FileSys.readDir stream of
-        NONE => x
-      | SOME entry => read (f entry x));
-  in read a end) path;
+fun fold_dir f path a =
+  check_dir path |> open_dir (fn stream =>
+    let
+      fun read x =
+        (case OS.FileSys.readDir stream of
+          NONE => x
+        | SOME entry => read (f entry x));
+    in read a end);
 
 fun read_dir path = rev (fold_dir cons path []);
 
--- a/src/Pure/General/file.scala	Wed Nov 14 11:51:03 2018 +0100
+++ b/src/Pure/General/file.scala	Wed Nov 14 16:26:58 2018 +0100
@@ -141,7 +141,7 @@
 
   def read_dir(dir: Path): List[String] =
   {
-    if (!dir.is_dir) error("Bad directory: " + dir.toString)
+    if (!dir.is_dir) error("No such directory: " + dir.toString)
     val files = dir.file.listFiles
     if (files == null) Nil
     else files.toList.map(_.getName)
--- a/src/Pure/General/ssh.scala	Wed Nov 14 11:51:03 2018 +0100
+++ b/src/Pure/General/ssh.scala	Wed Nov 14 16:26:58 2018 +0100
@@ -201,10 +201,9 @@
 
   type Attrs = SftpATTRS
 
-  sealed case class Dir_Entry(name: Path, attrs: Attrs)
+  sealed case class Dir_Entry(name: Path, is_dir: Boolean)
   {
-    def is_file: Boolean = attrs.isReg
-    def is_dir: Boolean = attrs.isDir
+    def is_file: Boolean = !is_dir
   }
 
 
@@ -341,12 +340,24 @@
     def mkdir(path: Path): Unit = sftp.mkdir(remote_path(path))
     def rmdir(path: Path): Unit = sftp.rmdir(remote_path(path))
 
-    def stat(path: Path): Option[Dir_Entry] =
-      try { Some(Dir_Entry(expand_path(path), sftp.stat(remote_path(path)))) }
+    private def try_stat(name: String): Option[Attrs] =
+      try { Some(sftp.stat(name)) }
       catch { case _: SftpException => None }
 
-    override def is_file(path: Path): Boolean = stat(path).map(_.is_file) getOrElse false
-    override def is_dir(path: Path): Boolean = stat(path).map(_.is_dir) getOrElse false
+    private def test_dir(name: String, attrs: Attrs): Boolean =
+      if (attrs.isLink) try_stat(name + "/.").isDefined else attrs.isDir
+
+    private def check_entry(path: Path, as_dir: Boolean): Boolean =
+    {
+      val name = remote_path(path)
+      try_stat(name) match {
+        case None => false
+        case Some(attrs) => if (as_dir) test_dir(name, attrs) else !test_dir(name, attrs)
+      }
+    }
+
+    override def is_dir(path: Path): Boolean = check_entry(path, true)
+    override def is_file(path: Path): Boolean = check_entry(path, false)
 
     override def mkdirs(path: Path): Unit =
       if (!is_dir(path)) {
@@ -357,14 +368,17 @@
 
     def read_dir(path: Path): List[Dir_Entry] =
     {
-      val dir = sftp.ls(remote_path(path))
+      if (!is_dir(path)) error("No such directory: " + path.toString)
+
+      val dir_name = remote_path(path)
+      val dir = sftp.ls(dir_name)
       (for {
         i <- (0 until dir.size).iterator
         a = dir.get(i).asInstanceOf[AnyRef]
         name = Untyped.get[String](a, "filename")
         attrs = Untyped.get[Attrs](a, "attrs")
         if name != "." && name != ".."
-      } yield Dir_Entry(Path.basic(name), attrs)).toList
+      } yield Dir_Entry(Path.basic(name), test_dir(dir_name + "/" + name, attrs))).toList
     }
 
     def find_files(root: Path, pred: Dir_Entry => Boolean = _ => true): List[Dir_Entry] =
@@ -440,8 +454,8 @@
 
     def expand_path(path: Path): Path = path.expand
     def bash_path(path: Path): String = File.bash_path(path)
+    def is_dir(path: Path): Boolean = path.is_dir
     def is_file(path: Path): Boolean = path.is_file
-    def is_dir(path: Path): Boolean = path.is_dir
     def mkdirs(path: Path): Unit = Isabelle_System.mkdirs(path)
 
     def execute(command: String,