guile-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Fix 'dirname' and 'basename' on MS-Windows


From: Eli Zaretskii
Subject: Fix 'dirname' and 'basename' on MS-Windows
Date: Wed, 02 Jul 2014 19:13:05 +0300

These 2 functions don't deal correctly with Windows file names with
drive letters and with UNCs.  The patch below fixes that.

Incidentally, isn't the line in scm_basename marked below wrong?

  if (i == end)
    {
      if (len > 0 && is_file_name_separator (scm_c_string_ref (filename, 0)))
        return scm_c_substring (filename, 0, 1);
      else
        return scm_dot_string;  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
  else
    return scm_c_substring (filename, i+1, end+1);

It is responsible for the following strange results:

   (basename ".foo" ".foo")  => "."
   (basename "_foo" "_foo")  => "."

Also, isn't the following result wrong as well?

   (basename "/")  => "/"

I think all of these should return the empty string, "".

Here's the proposed patch for supporting Windows file names.

--- libguile/filesys.c~1        2014-06-29 16:13:30 +0300
+++ libguile/filesys.c  2014-07-02 14:03:08 +0300
@@ -448,6 +448,18 @@ is_file_name_separator (SCM c)
   return 0;
 }
 
+static int
+is_drive_letter (SCM c)
+{
+#ifdef __MINGW32__
+  if (SCM_CHAR (c) >= 'a' && SCM_CHAR (c) <= 'z')
+    return 1;
+  else if (SCM_CHAR (c) >= 'A' && SCM_CHAR (c) <= 'Z')
+    return 1;
+#endif
+  return 0;
+}
+
 SCM_DEFINE (scm_stat, "stat", 1, 1, 0, 
             (SCM object, SCM exception_on_error),
            "Return an object containing various information about the file\n"
@@ -1518,24 +1530,60 @@ SCM_DEFINE (scm_dirname, "dirname", 1, 0
 {
   long int i;
   unsigned long int len;
+  /* Length of prefix before the top-level slash.  Always zero on
+     Posix hosts, but may be non-zero on Windows.  */
+  long prefix_len = 0;
+  int is_unc = 0;
+  unsigned long unc_end = 0;
 
   SCM_VALIDATE_STRING (1, filename);
 
   len = scm_i_string_length (filename);
+  if (len >= 2
+      && is_drive_letter (scm_c_string_ref (filename, 0))
+      && scm_is_eq (scm_c_string_ref (filename, 1), SCM_MAKE_CHAR (':')))
+    {
+      prefix_len = 1;
+      if (len > 2 && is_file_name_separator (scm_c_string_ref (filename, 2)))
+       prefix_len++;
+    }
+#ifdef __MINGW32__
+  if (len > 1
+      && is_file_name_separator (scm_c_string_ref (filename, 0))
+      && is_file_name_separator (scm_c_string_ref (filename, 1)))
+    {
+      is_unc = 1;
+      prefix_len = 1;
+    }
+#endif
 
   i = len - 1;
 
-  while (i >= 0 && is_file_name_separator (scm_c_string_ref (filename, i)))
+  while (i >= prefix_len
+        && is_file_name_separator (scm_c_string_ref (filename, i)))
     --i;
-  while (i >= 0 && !is_file_name_separator (scm_c_string_ref (filename, i)))
+  if (is_unc)
+    unc_end = i + 1;
+  while (i >= prefix_len
+        && !is_file_name_separator (scm_c_string_ref (filename, i)))
     --i;
-  while (i >= 0 && is_file_name_separator (scm_c_string_ref (filename, i)))
+  while (i >= prefix_len
+        && is_file_name_separator (scm_c_string_ref (filename, i)))
     --i;
 
-  if (i < 0)
+  if (i < prefix_len)
     {
-      if (len > 0 && is_file_name_separator (scm_c_string_ref (filename, 0)))
-       return scm_c_substring (filename, 0, 1);
+      if (is_unc)
+       return scm_c_substring (filename, 0, unc_end);
+      else if (len > prefix_len
+         && is_file_name_separator (scm_c_string_ref (filename, prefix_len)))
+       return scm_c_substring (filename, 0, prefix_len + 1);
+#ifdef __MINGW32__
+      else if (len > prefix_len
+              && scm_is_eq (scm_c_string_ref (filename, 1),
+                            SCM_MAKE_CHAR (':')))
+       return scm_c_substring (filename, 0, prefix_len + 1);
+#endif
       else
        return scm_dot_string;
     }
@@ -1553,6 +1601,9 @@ SCM_DEFINE (scm_basename, "basename", 1,
 #define FUNC_NAME s_scm_basename
 {
   int i, j, len, end;
+  /* Length of prefix before the top-level slash.  Always zero on
+     Posix hosts, but may be non-zero on Windows.  */
+  long prefix_len = 0;
 
   SCM_VALIDATE_STRING (1, filename);
   len = scm_i_string_length (filename);
@@ -1564,11 +1615,17 @@ SCM_DEFINE (scm_basename, "basename", 1,
       SCM_VALIDATE_STRING (2, suffix);
       j = scm_i_string_length (suffix) - 1;
     }
+  if (len >= 2
+      && is_drive_letter (scm_c_string_ref (filename, 0))
+      && scm_is_eq (scm_c_string_ref (filename, 1), SCM_MAKE_CHAR (':')))
+    prefix_len = 2;
+
   i = len - 1;
-  while (i >= 0 && is_file_name_separator (scm_c_string_ref (filename, i)))
+  while (i >= prefix_len
+        && is_file_name_separator (scm_c_string_ref (filename, i)))
     --i;
   end = i;
-  while (i >= 0 && j >= 0 
+  while (i >= prefix_len && j >= 0
         && (scm_i_string_ref (filename, i)
             == scm_i_string_ref (suffix, j)))
     {
@@ -1577,12 +1634,20 @@ SCM_DEFINE (scm_basename, "basename", 1,
     }
   if (j == -1)
     end = i;
-  while (i >= 0 && !is_file_name_separator (scm_c_string_ref (filename, i)))
+  while (i >= prefix_len
+        && !is_file_name_separator (scm_c_string_ref (filename, i)))
     --i;
   if (i == end)
     {
-      if (len > 0 && is_file_name_separator (scm_c_string_ref (filename, 0)))
-        return scm_c_substring (filename, 0, 1);
+      if (len > prefix_len
+         && is_file_name_separator (scm_c_string_ref (filename, prefix_len)))
+        return scm_c_substring (filename, 0, prefix_len + 1);
+#ifdef __MINGW32__
+      else if (len > prefix_len
+              && scm_is_eq (scm_c_string_ref (filename, 1),
+                            SCM_MAKE_CHAR (':')))
+       return scm_c_substring (filename, 0, prefix_len + 1);
+#endif
       else
        return scm_dot_string;
     }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]