Mono为什么取消兼容文件名大小写功能

前言

在传统Asp.Net WebForm/MVC项目从Windows系统迁移到Linux系统,大多数会使用Jexus来代替Windows上的IIS,在Jexus早期的shell运行脚本中,是有一项配置的.开启之后,可以忽略文件名大小写.

重要的事情要标红,Windows系统文件名不区分大小写,Linux/类Unix文件名都是区分大小写的.
#export MONO_IOMAP="all" #iomap为all时,忽略文件名大小写

在从Windows系统迁移时,这个功能还是很好的,因为不需要考虑文件名大小写的问题了,后面为什么会去取消这个功能呢?说到底还是因为性能.在Linux系统,如果开启忽略文件名大小写配置后.

比如在加载index.aspx时:

  1. 最好的情况,index.aspx文件名就是这个,找到这个文件就行了
  2. 假设这个文件名是indeX.aspx,就需要多次尝试是否可以打开这个index.aspx

这种多次尝试打开文件,是很影响性能的,所以在2021年的时候,Mono对这个功能的源码进行了移除.我们可以看看这一块的源码:
#include "config.h"

#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <mono/metadata/profiler-private.h>
#include <mono/utils/mono-compiler.h>
#include <mono/utils/mono-io-portability.h>

#ifndef DISABLE_PORTABILITY

#include <dirent.h>

int mono_io_portability_helpers = PORTABILITY_UNKNOWN;

static gchar *mono_portability_find_file_internal(const gchar *pathname,
                                                  gboolean last_exists);

void mono_portability_helpers_init(void) {
  gchar *env;

  if (mono_io_portability_helpers != PORTABILITY_UNKNOWN)
    return;

  mono_io_portability_helpers = PORTABILITY_NONE;

  env = g_getenv("MONO_IOMAP");  //从环境变量中获取MONO_IOMAP
  if (env != NULL) {
    /* parse the environment setting and set up some vars
     * here
     */
    gchar **options = g_strsplit(env, ":", 0);
    int i;

    if (options == NULL) {
      /* This shouldn't happen */
      return;
    }

    for (i = 0; options[i] != NULL; i++) {
#ifdef DEBUG
      g_message("%s: Setting option [%s]", __func__, options[i]);
#endif
      if (!strncasecmp(options[i], "drive", 5)) {
        mono_io_portability_helpers |= PORTABILITY_DRIVE;
      } else if (!strncasecmp(options[i], "case", 4)) {
        mono_io_portability_helpers |= PORTABILITY_CASE;
      } else if (!strncasecmp(options[i], "all", 3)) {  //如果是all
        mono_io_portability_helpers |= (PORTABILITY_DRIVE | PORTABILITY_CASE);
      }
    }
    g_free(env);
  }
}

/* Returns newly allocated string, or NULL on failure */
static gchar *find_in_dir(DIR *current, const gchar *name) {
  struct dirent *entry;

#ifdef DEBUG
  g_message("%s: looking for [%s]\n", __func__, name);
#endif

  while ((entry = readdir(current)) != NULL) {
#ifdef DEBUGX
    g_message("%s: found [%s]\n", __func__, entry->d_name);
#endif

    if (!g_ascii_strcasecmp(name, entry->d_name)) {
      char *ret;

#ifdef DEBUG
      g_message("%s: matched [%s] to [%s]\n", __func__, entry->d_name, name);
#endif

      ret = g_strdup(entry->d_name);
      closedir(current);
      return ret;
    }
  }

#ifdef DEBUG
  g_message("%s: returning NULL\n", __func__);
#endif

  closedir(current);

  return (NULL);
}

//mono调用这个方法,查找文件
gchar *mono_portability_find_file(const gchar *pathname, gboolean last_exists) {
  gchar *ret;

  if (!pathname || !pathname[0])
    return NULL;
  ret = mono_portability_find_file_internal(pathname, last_exists);

  return ret;
}

//真正干活的
/* Returns newly-allocated string or NULL on failure */
static gchar *mono_portability_find_file_internal(const gchar *pathname,
                                                  gboolean last_exists) {
  gchar *new_pathname, **components, **new_components;
  int num_components = 0, component = 0;
  DIR *scanning = NULL;
  size_t len;

  if (IS_PORTABILITY_NONE) {
    return (NULL);
  }

  new_pathname = g_strdup(pathname);

#ifdef DEBUG
  g_message("%s: Finding [%s] last_exists: %s\n", __func__, pathname,
            last_exists ? "TRUE" : "FALSE");
#endif

  //******先用access判断文件是否存在,如果存在直接返回
  if (last_exists && access(new_pathname, F_OK) == 0) {
#ifdef DEBUG
    g_message("%s: Found it without doing anything\n", __func__);
#endif
    return (new_pathname);
  }

  /* First turn '\' into '/' and strip any drive letters */
  g_strdelimit(new_pathname, '\\', '/');

#ifdef DEBUG
  g_message("%s: Fixed slashes, now have [%s]\n", __func__, new_pathname);
#endif

  if (IS_PORTABILITY_DRIVE && g_ascii_isalpha(new_pathname[0]) &&
      (new_pathname[1] == ':')) {
    int len = strlen(new_pathname);

    g_memmove(new_pathname, new_pathname + 2, len - 2);
    new_pathname[len - 2] = '\0';
#ifdef DEBUG
    g_message("%s: Stripped drive letter, now looking for [%s]\n", __func__,
              new_pathname);
#endif
  }

  len = strlen(new_pathname);
  if (len > 1 && new_pathname[len - 1] == '/') {
    new_pathname[len - 1] = 0;
#ifdef DEBUG
    g_message("%s: requested name had a trailing /, rewritten to '%s'\n",
              __func__, new_pathname);
#endif
  }

  //继续判断文件是否存在
  if (last_exists && access(new_pathname, F_OK) == 0) {
#ifdef DEBUG
    g_message("%s: Found it\n", __func__);
#endif

    return (new_pathname);
  }

  /* OK, have to work harder.  Take each path component in turn
   * and do a case-insensitive directory scan for it
   */

  if (!(IS_PORTABILITY_CASE)) {
    g_free(new_pathname);
    return (NULL);
  }

  components = g_strsplit(new_pathname, "/", 0);
  if (components == NULL) {
    /* This shouldn't happen */
    g_free(new_pathname);
    return (NULL);
  }

  while (components[num_components] != NULL) {
    num_components++;
  }
  g_free(new_pathname);

  if (num_components == 0) {
    return NULL;
  }

  new_components = (gchar **)g_new0(gchar **, num_components + 1);

  if (num_components > 1) {
    if (strcmp(components[0], "") == 0) {
      /* first component blank, so start at / */
      scanning = opendir("/");
      if (scanning == NULL) {
#ifdef DEBUG
        g_message("%s: opendir 1 error: %s", __func__, g_strerror(errno));
#endif
        g_strfreev(new_components);
        g_strfreev(components);
        return (NULL);
      }

      new_components[component++] = g_strdup("");
    } else {
      DIR *current;
      gchar *entry;

      current = opendir(".");
      if (current == NULL) {
#ifdef DEBUG
        g_message("%s: opendir 2 error: %s", __func__, g_strerror(errno));
#endif
        g_strfreev(new_components);
        g_strfreev(components);
        return (NULL);
      }

      entry = find_in_dir(current, components[0]);
      if (entry == NULL) {
        g_strfreev(new_components);
        g_strfreev(components);
        return (NULL);
      }

      scanning = opendir(entry);
      if (scanning == NULL) {
#ifdef DEBUG
        g_message("%s: opendir 3 error: %s", __func__, g_strerror(errno));
#endif
        g_free(entry);
        g_strfreev(new_components);
        g_strfreev(components);
        return (NULL);
      }

      new_components[component++] = entry;
    }
  } else {
    if (last_exists) {
      if (strcmp(components[0], "") == 0) {
        /* First and only component blank */
        new_components[component++] = g_strdup("");
      } else {
        DIR *current;
        gchar *entry;

        current = opendir(".");
        if (current == NULL) {
#ifdef DEBUG
          g_message("%s: opendir 4 error: %s", __func__, g_strerror(errno));
#endif
          g_strfreev(new_components);
          g_strfreev(components);
          return (NULL);
        }

        entry = find_in_dir(current, components[0]);
        if (entry == NULL) {
          g_strfreev(new_components);
          g_strfreev(components);
          return (NULL);
        }

        new_components[component++] = entry;
      }
    } else {
      new_components[component++] = g_strdup(components[0]);
    }
  }

#ifdef DEBUG
  g_message("%s: Got first entry: [%s]\n", __func__, new_components[0]);
#endif

  g_assert(component == 1);
  // 继续尝试查找
  for (; component < num_components; component++) {
    gchar *entry;
    gchar *path_so_far;

    if (!last_exists && component == num_components - 1) {
      entry = g_strdup(components[component]);
      closedir(scanning);
    } else {
      entry = find_in_dir(scanning, components[component]);
      if (entry == NULL) {
        g_strfreev(new_components);
        g_strfreev(components);
        return (NULL);
      }
    }

    new_components[component] = entry;

    if (component < num_components - 1) {
      path_so_far = g_strjoinv("/", new_components);

      scanning = opendir(path_so_far);
      g_free(path_so_far);
      if (scanning == NULL) {
        g_strfreev(new_components);
        g_strfreev(components);
        return (NULL);
      }
    }
  }

  g_strfreev(components);

  new_pathname = g_strjoinv("/", new_components);

#ifdef DEBUG
  g_message("%s: pathname [%s] became [%s]\n", __func__, pathname,
            new_pathname);
#endif

  g_strfreev(new_components);

  //最后在看文件是否存在
  if ((last_exists && access(new_pathname, F_OK) == 0) || (!last_exists)) {
    return (new_pathname);
  }

  g_free(new_pathname);
  return (NULL);
}

#else /* DISABLE_PORTABILITY */

MONO_EMPTY_SOURCE_FILE(mono_io_portability);

#endif /* DISABLE_PORTABILITY */

移除MONO_IOMAP功能的代码,都在这里,有兴趣的可以去学习一下:Mono移除MONO_IOMAP代码 

秋风 2023-02-18