view liboctave/system/lo-sysdep.cc @ 33034:49128bdb9eb2

use explicit lambda-expression captures (bug #65318) Previously, we recommended using implicit capture by value (if possible) in all lambda expressions in Octave. However, this choice causes trouble in the transition period leading up to C++20, when the meaning changes for capturing 'this' by reference when the default capture is '='. Since all lambda expressions in Octave only need to capture a few variables it seems better and relatively easy to simply name all captured variable explicitly. (The maximum number of captured variables currently appears to be seven, including 'this', but the vast majority are capturing just one or two.) Affected files: Canvas.cc, GLCanvas.cc, QTerminal.cc, command-widget.cc, documentation.cc, files-dock-widget.cc, interpreter-qobject.cc, file-editor-tab.cc, file-editor.cc, octave-qscintilla.cc, main-window.cc, octave-dock-widget.cc, octave-qobject.cc, set-path-model.cc, settings-dialog.cc, variable-editor-model.cc, variable-editor.cc, call-stack.cc, gl2ps-print.cc, graphics.cc, input.cc, interpreter.cc, load-path.cc, mex.cc, pr-output.cc, strfns.cc, sysdep.cc, __delaunayn__.cc, audiodevinfo.cc, audioread.cc, oct-parse.yy, pt-eval.cc, Array-util.cc, Range.h, lo-sysdep.cc, lo-regexp.cc, oct-glob.cc, oct-string.cc, and url-transfer.cc.
author John W. Eaton <jwe@octave.org>
date Fri, 16 Feb 2024 14:42:54 -0500
parents 2e484f9f1f18
children 5b54c2fb69bb
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1996-2024 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <cstdlib>
#include <locale>
#include <codecvt>

#include "dir-ops.h"
#include "file-ops.h"
#include "file-stat.h"
#include "lo-error.h"
#include "lo-sysdep.h"
#include "localcharset-wrapper.h"
#include "putenv-wrapper.h"
#include "unistd-wrappers.h"
#include "unsetenv-wrapper.h"

#if defined (OCTAVE_USE_WINDOWS_API)
#  include <windows.h>
#  include <wchar.h>

#  include "filepos-wrappers.h"
#  include "lo-hash.h"
#  include "oct-locbuf.h"
#  include "uniconv-wrappers.h"
#  include "unwind-prot.h"
#endif

OCTAVE_BEGIN_NAMESPACE(octave)

OCTAVE_BEGIN_NAMESPACE(sys)

int
system (const std::string& cmd_str)
{
#if defined (OCTAVE_USE_WINDOWS_API)
  const std::wstring wcmd_str =  u8_to_wstring (cmd_str);

  return _wsystem (wcmd_str.c_str ());
#else
  return ::system (cmd_str.c_str ());
#endif
}

std::string
getcwd ()
{
  std::string retval;

#if defined (OCTAVE_USE_WINDOWS_API)
  wchar_t *tmp = _wgetcwd (nullptr, 0);

  if (! tmp)
    (*current_liboctave_error_handler) ("unable to find current directory");

  std::wstring tmp_wstr (tmp);
  free (tmp);

  std::string tmp_str = u8_from_wstring (tmp_wstr);

  retval = tmp_str;

#else
  // Using octave_getcwd_wrapper ensures that we have a getcwd that
  // will allocate a buffer as large as necessary if buf and size are
  // both 0.

  char *tmp = octave_getcwd_wrapper (nullptr, 0);

  if (! tmp)
    (*current_liboctave_error_handler) ("unable to find current directory");

  retval = tmp;
  free (tmp);
#endif

  return retval;
}

int
chdir (const std::string& path_arg)
{
  std::string path = sys::file_ops::tilde_expand (path_arg);

#if defined (OCTAVE_USE_WINDOWS_API)
  if (path.length () == 2 && path[1] == ':')
    path += '\\';
#endif

  return octave_chdir_wrapper (path.c_str ());
}

bool
get_dirlist (const std::string& dirname, string_vector& dirlist,
             std::string& msg)
{
  dirlist = "";
  msg = "";
#if defined (OCTAVE_USE_WINDOWS_API)
  _WIN32_FIND_DATAW ffd;

  std::string path_name (dirname);
  if (path_name.empty ())
    return true;

  if (path_name.back () == '\\' || path_name.back () == '/')
    path_name.push_back ('*');
  else
    path_name.append (R"(\*)");

  // Find first file in directory.
  std::wstring wpath_name = u8_to_wstring (path_name);
  HANDLE hFind = FindFirstFileW (wpath_name.c_str (), &ffd);
  if (INVALID_HANDLE_VALUE == hFind)
    {
      DWORD errCode = GetLastError ();
      char *errorText = nullptr;
      FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_ALLOCATE_BUFFER |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                      nullptr, errCode,
                      MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
                      reinterpret_cast <char *> (&errorText), 0, nullptr);
      if (errorText != nullptr)
        {
          msg = std::string (errorText);
          LocalFree (errorText);
        }
      return false;
    }

  std::list<std::string> dirlist_str;
  do
    dirlist_str.push_back (u8_from_wstring (ffd.cFileName));
  while (FindNextFileW (hFind, &ffd) != 0);

  FindClose(hFind);

  dirlist = string_vector (dirlist_str);

#else

  dir_entry dir (dirname);

  if (! dir)
    {
      msg = dir.error ();
      return false;
    }

  dirlist = dir.read ();

  dir.close ();
#endif

  return true;
}

#if defined (OCTAVE_USE_WINDOWS_API)

static bool
check_fseek_ftell_workaround_needed (bool set_nonbuffered_mode)
{
  // To check whether the workaround is needed:
  //
  //   * Create a tmp file with LF line endings only.
  //
  //   * Open that file for reading in text mode.
  //
  //   * Read a line.
  //
  //   * Use ftello to record the position of the beginning of the
  //     second line.
  //
  //   * Read and save the contents of the second line.
  //
  //   * Use fseeko to return to the saved position.
  //
  //   * Read the second line again and compare to the previously
  //     saved text.
  //
  //   * If the lines are different, we need to set non-buffered
  //     input mode for files opened in text mode.

  std::string tmpname = sys::tempnam ("", "oct-");

  if (tmpname.empty ())
    {
      (*current_liboctave_warning_handler)
        ("fseek/ftell bug check failed (tmp name creation)!");
      return false;
    }

  std::FILE *fptr = std::fopen (tmpname.c_str (), "wb");

  if (! fptr)
    {
      (*current_liboctave_warning_handler)
        ("fseek/ftell bug check failed (opening tmp file for writing)!");
      return false;
    }

  fprintf (fptr, "%s", "foo\nbar\nbaz\n");

  std::fclose (fptr);

  fptr = std::fopen (tmpname.c_str (), "rt");

  if (! fptr)
    {
      (*current_liboctave_warning_handler)
        ("fseek/ftell bug check failed (opening tmp file for reading)!");
      return false;
    }

  unwind_action act ([] ()
  {
    std::fclose (fptr);
    sys::unlink (tmpname);
  });

  if (set_nonbuffered_mode)
    ::setvbuf (fptr, nullptr, _IONBF, 0);

  while (true)
    {
      int c = fgetc (fptr);

      if (c == EOF)
        {
          (*current_liboctave_warning_handler)
            ("fseek/ftell bug check failed (skipping first line)!");
          return false;
        }

      if (c == '\n')
        break;
    }

  off_t pos = octave_ftello_wrapper (fptr);

  char buf1[8];
  int i = 0;
  while (true)
    {
      int c = fgetc (fptr);

      if (c == EOF)
        {
          (*current_liboctave_warning_handler)
            ("fseek/ftell bug check failed (reading second line)!");
          return false;
        }

      if (c == '\n')
        break;

      buf1[i++] = static_cast<char> (c);
    }
  buf1[i] = '\0';

  octave_fseeko_wrapper (fptr, pos, SEEK_SET);

  char buf2[8];
  i = 0;
  while (true)
    {
      int c = fgetc (fptr);

      if (c == EOF)
        {
          (*current_liboctave_warning_handler)
            ("fseek/ftell bug check failed (reading after repositioning)!");
          return false;
        }

      if (c == '\n')
        break;

      buf2[i++] = static_cast<char> (c);
    }
  buf2[i] = '\0';

  return strcmp (buf1, buf2);
}

static std::string
get_formatted_last_error ()
{
  std::string msg = "";

  DWORD last_error = GetLastError ();

  wchar_t *error_text = nullptr;
  FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM |
                  FORMAT_MESSAGE_ALLOCATE_BUFFER |
                  FORMAT_MESSAGE_IGNORE_INSERTS,
                  nullptr, last_error,
                  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
                  reinterpret_cast <wchar_t *> (&error_text), 0, nullptr);

  if (error_text != nullptr)
    {
      msg = u8_from_wstring (error_text);
      LocalFree (error_text);
    }
  else
    msg = "Unknown error.";

  return msg;
}
#endif

bool
file_exists (const std::string& filename, bool is_dir)
{
  // Check if a file with the given name exists on the file system.  If is_dir
  // is true (the default), also return true if filename refers to a directory.
#if defined (OCTAVE_USE_WINDOWS_API)
  std::wstring w_fn = u8_to_wstring (filename);

  DWORD f_attr = GetFileAttributesW (w_fn.c_str ());

  return ((f_attr != INVALID_FILE_ATTRIBUTES)
          && (is_dir || ! (f_attr & FILE_ATTRIBUTE_DIRECTORY)));

#else
  file_stat fs (filename);

  return (fs && (fs.is_reg () || (is_dir && fs.is_dir ())));

#endif
}

bool
file_exists (const std::string& filename, bool is_dir, std::string& msg)
{
  // Check if a file with the given name exists on the file system.  If is_dir
  // is true (the default), also return true if filename refers to a directory.
#if defined (OCTAVE_USE_WINDOWS_API)
  std::wstring w_fn = u8_to_wstring (filename);

  DWORD f_attr = GetFileAttributesW (w_fn.c_str ());

  if (f_attr == INVALID_FILE_ATTRIBUTES)
    msg = get_formatted_last_error ();

  return ((f_attr != INVALID_FILE_ATTRIBUTES)
          && (is_dir || ! (f_attr & FILE_ATTRIBUTE_DIRECTORY)));

#else
  file_stat fs (filename);

  if (! fs)
    msg = fs.error ();

  return (fs && (fs.is_reg () || (is_dir && fs.is_dir ())));

#endif
}

bool
dir_exists (const std::string& dirname)
{
  // Check if a directory with the given name exists on the file system.
#if defined (OCTAVE_USE_WINDOWS_API)
  std::wstring w_dn = u8_to_wstring (dirname);

  DWORD f_attr = GetFileAttributesW (w_dn.c_str ());

  return ((f_attr != INVALID_FILE_ATTRIBUTES)
          && (f_attr & FILE_ATTRIBUTE_DIRECTORY));

#else
  file_stat fs (dirname);

  return (fs && fs.is_dir ());

#endif
}

bool
dir_exists (const std::string& dirname, std::string& msg)
{
  // Check if a directory with the given name exists on the file system.
#if defined (OCTAVE_USE_WINDOWS_API)
  std::wstring w_dn = u8_to_wstring (dirname);

  DWORD f_attr = GetFileAttributesW (w_dn.c_str ());

  if (f_attr == INVALID_FILE_ATTRIBUTES)
    msg = get_formatted_last_error ();

  return ((f_attr != INVALID_FILE_ATTRIBUTES)
          && (f_attr & FILE_ATTRIBUTE_DIRECTORY));

#else
  file_stat fs (dirname);

  if (! fs)
    msg = fs.error ();

  return (fs && fs.is_dir ());

#endif
}

// Return TRUE if FILE1 and FILE2 refer to the same (physical) file.

bool
same_file (const std::string& file1, const std::string& file2)
{
#if defined (OCTAVE_USE_WINDOWS_API)

  // FIXME: When Octave switches to C++17, consider replacing this function
  //        by https://en.cppreference.com/w/cpp/filesystem/equivalent.

  bool retval = false;

  std::wstring file1w = sys::u8_to_wstring (file1);
  std::wstring file2w = sys::u8_to_wstring (file2);
  const wchar_t *f1 = file1w.c_str ();
  const wchar_t *f2 = file2w.c_str ();

  bool f1_is_dir = GetFileAttributesW (f1) & FILE_ATTRIBUTE_DIRECTORY;
  bool f2_is_dir = GetFileAttributesW (f2) & FILE_ATTRIBUTE_DIRECTORY;

  // Windows native code
  // Reference: http://msdn2.microsoft.com/en-us/library/aa363788.aspx

  DWORD share = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE;

  HANDLE hfile1
    = CreateFileW (f1, 0, share, 0, OPEN_EXISTING,
                   f1_is_dir ? FILE_FLAG_BACKUP_SEMANTICS : 0, 0);

  if (hfile1 != INVALID_HANDLE_VALUE)
    {
      HANDLE hfile2
        = CreateFileW (f2, 0, share, 0, OPEN_EXISTING,
                       f2_is_dir ? FILE_FLAG_BACKUP_SEMANTICS : 0, 0);

      if (hfile2 != INVALID_HANDLE_VALUE)
        {
          BY_HANDLE_FILE_INFORMATION hfi1;
          BY_HANDLE_FILE_INFORMATION hfi2;

          if (GetFileInformationByHandle (hfile1, &hfi1)
              && GetFileInformationByHandle (hfile2, &hfi2))
            {
              retval = (hfi1.dwVolumeSerialNumber == hfi2.dwVolumeSerialNumber
                        && hfi1.nFileIndexHigh == hfi2.nFileIndexHigh
                        && hfi1.nFileIndexLow == hfi2.nFileIndexLow
                        && hfi1.nFileSizeHigh == hfi2.nFileSizeHigh
                        && hfi1.nFileSizeLow == hfi2.nFileSizeLow
                        && hfi1.ftLastWriteTime.dwLowDateTime
                        == hfi2.ftLastWriteTime.dwLowDateTime
                        && hfi1.ftLastWriteTime.dwHighDateTime
                        == hfi2.ftLastWriteTime.dwHighDateTime);
            }

          CloseHandle (hfile2);
        }

      CloseHandle (hfile1);
    }

  return retval;

#else

  // POSIX Code

  sys::file_stat fs_file1 (file1);
  sys::file_stat fs_file2 (file2);

  return (fs_file1 && fs_file2
          && fs_file1.ino () == fs_file2.ino ()
          && fs_file1.dev () == fs_file2.dev ());

#endif
}

std::FILE *
fopen (const std::string& filename, const std::string& mode)
{
#if defined (OCTAVE_USE_WINDOWS_API)

  std::wstring wfilename = u8_to_wstring (filename);
  std::wstring wmode = u8_to_wstring (mode);

  std::FILE *fptr = _wfopen (wfilename.c_str (), wmode.c_str ());

  static bool fseek_ftell_bug_workaround_needed = false;
  static bool fseek_ftell_bug_checked = false;

  if (! fseek_ftell_bug_checked && mode.find ('t') != std::string::npos)
    {
      // FIXME: Is the following workaround needed for all files
      // opened in text mode, or only for files opened for reading?

      // Try to avoid fseek/ftell bug on Windows systems by setting
      // non-buffered input mode for files opened in text mode, but
      // only if it appears that the workaround is needed.  See
      // Octave bug #58055.

      // To check whether the workaround is needed:
      //
      //   * Create a tmp file with LF line endings only.
      //
      //   * Open that file for reading in text mode.
      //
      //   * Read a line.
      //
      //   * Use ftello to record the position of the beginning of
      //     the second line.
      //
      //   * Read and save the contents of the second line.
      //
      //   * Use fseeko to return to the saved position.
      //
      //   * Read the second line again and compare to the
      //     previously saved text.
      //
      //   * If the lines are different, we need to set non-buffered
      //     input mode for files opened in text mode.
      //
      //   * To verify that the workaround solves the problem,
      //     repeat the above test with non-buffered input mode.  If
      //     that fails, warn that there may be trouble with
      //     ftell/fseek when reading files opened in text mode.

      if (check_fseek_ftell_workaround_needed (false))
        {
          if (check_fseek_ftell_workaround_needed (true))
            (*current_liboctave_warning_handler)
              ("fseek/ftell may fail for files opened in text mode");
          else
            fseek_ftell_bug_workaround_needed = true;
        }

      fseek_ftell_bug_checked = true;
    }

  if (fseek_ftell_bug_workaround_needed
      && mode.find ('t') != std::string::npos)
    ::setvbuf (fptr, nullptr, _IONBF, 0);

  return fptr;

#else
  return std::fopen (filename.c_str (), mode.c_str ());
#endif
}

std::FILE *
fopen_tmp (const std::string& name, const std::string& mode)
{
#if defined (OCTAVE_USE_WINDOWS_API)

  // Append "D" to the mode string to indicate that this is a temporary
  // file that should be deleted when the last open handle is closed.
  std::string tmp_mode = mode + "D";

  return std::fopen (name.c_str (), tmp_mode.c_str ());

#else

  std::FILE *fptr = std::fopen (name.c_str (), mode.c_str ());

  // From gnulib: This relies on the Unix semantics that a file is not
  // really removed until it is closed.
  octave_unlink_wrapper (name.c_str ());

  return fptr;

#endif
}

std::fstream
fstream (const std::string& filename, const std::ios::openmode mode)
{
#if defined (OCTAVE_USE_WINDOWS_API)

  std::wstring wfilename = u8_to_wstring (filename);

  return std::fstream (wfilename.c_str (), mode);

#else
  return std::fstream (filename.c_str (), mode);
#endif
}

std::ifstream
ifstream (const std::string& filename, const std::ios::openmode mode)
{
#if defined (OCTAVE_USE_WINDOWS_API)

  std::wstring wfilename = u8_to_wstring (filename);

  return std::ifstream (wfilename.c_str (), mode);

#else
  return std::ifstream (filename.c_str (), mode);
#endif
}

std::ofstream
ofstream (const std::string& filename, const std::ios::openmode mode)
{
#if defined (OCTAVE_USE_WINDOWS_API)

  std::wstring wfilename = u8_to_wstring (filename);

  return std::ofstream (wfilename.c_str (), mode);

#else
  return std::ofstream (filename.c_str (), mode);
#endif
}

void
putenv_wrapper (const std::string& name, const std::string& value)
{
  std::string new_env = name + "=" + value;

  // FIXME: The malloc leaks memory, but so would a call to setenv.
  // Short of extreme measures to track memory, altering the environment
  // always leaks memory, but the saving grace is that the leaks are small.

  // As far as I can see there's no way to distinguish between the
  // various errors; putenv doesn't have errno values.

#if defined (OCTAVE_USE_WINDOWS_API)
  std::wstring new_wenv = u8_to_wstring (new_env);

  int len = (new_wenv.length () + 1) * sizeof (wchar_t);

  wchar_t *new_item = static_cast<wchar_t *> (std::malloc (len));

  wcscpy (new_item, new_wenv.c_str());

  if (_wputenv (new_item) < 0)
    (*current_liboctave_error_handler)
      ("putenv (%s) failed", new_env.c_str());
#else
  int len = new_env.length () + 1;

  char *new_item = static_cast<char *> (std::malloc (len));

  std::strcpy (new_item, new_env.c_str());

  if (octave_putenv_wrapper (new_item) < 0)
    (*current_liboctave_error_handler) ("putenv (%s) failed", new_item);
#endif
}

std::string
getenv_wrapper (const std::string& name)
{
#if defined (OCTAVE_USE_WINDOWS_API)
  std::wstring wname = u8_to_wstring (name);
  wchar_t *env = _wgetenv (wname.c_str ());
  return env ? u8_from_wstring (env) : "";
#else
  char *env = ::getenv (name.c_str ());
  return env ? env : "";
#endif
}

int
unsetenv_wrapper (const std::string& name)
{
#if defined (OCTAVE_USE_WINDOWS_API)
  putenv_wrapper (name, "");

  std::wstring wname = u8_to_wstring (name);
  return (SetEnvironmentVariableW (wname.c_str (), nullptr) ? 0 : -1);
#else
  return octave_unsetenv_wrapper (name.c_str ());
#endif
}

bool
isenv_wrapper (const std::string& name)
{
#if defined (OCTAVE_USE_WINDOWS_API)
  std::wstring wname = u8_to_wstring (name);
  wchar_t *env = _wgetenv (wname.c_str ());
#else
  char *env = ::getenv (name.c_str ());
#endif
  return env != 0;
}

std::wstring
u8_to_wstring (const std::string& utf8_string)
{
  // convert multibyte UTF-8 string to wide character string
  static std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>
  wchar_conv;

  std::wstring retval = L"";

  try
    {
      retval = wchar_conv.from_bytes (utf8_string);
    }
  catch (const std::range_error& e)
    {
      // What to do in case of error?
      // error ("u8_to_wstring: converting from UTF-8 to wchar_t: %s",
      //        e.what ());
    }

  return retval;
}

std::string
u8_from_wstring (const std::wstring& wchar_string)
{
  // convert wide character string to multibyte UTF-8 string
  static std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>
  wchar_conv;

  std::string retval = "";

  try
    {
      retval = wchar_conv.to_bytes (wchar_string);
    }
  catch (const std::range_error& e)
    {
      // What to do in case of error?
      // error ("u8_from_wstring: converting from wchar_t to UTF-8: %s",
      //        e.what ());
    }

  return retval;
}

// At quite a few places in the code we are passing file names as
// char arrays to external library functions.

// When these functions try to locate the corresponding file on the
// disc, they need to use the wide character API on Windows to
// correctly open files with non-ASCII characters.

// But they have no way of knowing which encoding we are using for
// the passed string.  So they have no way of reliably converting to
// a wchar_t array.  (I.e. there is no possible fix for these
// functions with current C or C++.)

// To solve the dilemma, the function "get_ASCII_filename" first
// checks whether there are any non-ASCII characters in the passed
// file name.  If there are not, it returns the original name.

// Otherwise, it optionally tries to convert the file name to the locale
// charset.

// If the file name contains characters that cannot be converted to the
// locale charset (or that step is skipped), it tries to obtain the short
// file name (8.3 naming scheme) which only consists of ASCII characters
// and are safe to pass.  However, short file names can be disabled for
// performance reasons on the file system level with NTFS and they are not
// stored on other file systems (e.g. ExFAT).  So there is no guarantee
// that these exist.

// If short file names are not stored, a hard link to the file is
// created.  For this the path to the file is split at the deepest
// possible level that doesn't contain non-ASCII characters.  At
// that level a hidden folder is created that holds the hard links.
// That means we need to have write access on that location.  A path
// to that hard link is returned.

// If the file system is FAT32, there are no hard links.  But FAT32
// always stores short file names.  So we are safe.

// ExFAT that is occasionally used on USB sticks and SD cards stores
// neither short file names nor does it support hard links.  So for
// exFAT with this function, there is (currently) no way to generate
// a file name that is stripped from non-ASCII characters but still
// is valid.

// For Unixy systems, this function does nothing.

std::string
get_ASCII_filename (const std::string& orig_file_name,
                    const bool allow_locale)
{
#if defined (OCTAVE_USE_WINDOWS_API)

  // Return file name that only contains ASCII characters that can
  // be used to access the file orig_file_name.  The original file
  // must exist in the file system before calling this function.
  // This is useful for passing file names to functions that are not
  // aware of the character encoding we are using.

  // 0. Check whether filename contains non-ASCII (UTF-8) characters.

  std::string::const_iterator first_non_ASCII
    = std::find_if (orig_file_name.begin (), orig_file_name.end (),
  [](char c) { return (c < 0 || c >= 128); });

  if (first_non_ASCII == orig_file_name.end ())
    return orig_file_name;

  // 1. Optionally, check if all characters in the path can be successfully
  // converted to the locale charset
  if (allow_locale)
    {
      const char *locale = octave_locale_charset_wrapper ();
      if (locale)
        {
          const uint8_t *name_u8 = reinterpret_cast<const uint8_t *>
                                   (orig_file_name.c_str ());
          std::size_t length = 0;
          char *name_locale = octave_u8_conv_to_encoding_strict
                              (locale, name_u8,
                               orig_file_name.length () + 1, &length);
          if (name_locale)
            {
              std::string file_name_locale (name_locale, length);
              free (name_locale);
              return file_name_locale;
            }
        }
    }

  // 2. Check if file system stores short filenames (might be ASCII-only).

  std::wstring w_orig_file_name_str = u8_to_wstring (orig_file_name);
  const wchar_t *w_orig_file_name = w_orig_file_name_str.c_str ();

  // Get full path to file
  wchar_t w_full_file_name[_MAX_PATH];
  if (_wfullpath (w_full_file_name, w_orig_file_name, _MAX_PATH) == nullptr)
    return orig_file_name;

  std::wstring w_full_file_name_str = w_full_file_name;

  // Get short filename (8.3) from UTF-16 filename.

  long length = GetShortPathNameW (w_full_file_name, nullptr, 0);

  if (length > 0)
    {
      // Dynamically allocate the correct size (terminating null char
      // was included in length).

      OCTAVE_LOCAL_BUFFER (wchar_t, w_short_file_name, length);
      GetShortPathNameW (w_full_file_name, w_short_file_name, length);

      std::wstring w_short_file_name_str
        = std::wstring (w_short_file_name, length);

      if (w_short_file_name_str.compare (0, length-1, w_full_file_name_str) != 0)
        {
          // Check whether short file name contains non-ASCII characters
          std::string short_file_name
            = u8_from_wstring (w_short_file_name_str);
          first_non_ASCII
            = std::find_if (short_file_name.begin (),
                            short_file_name.end (),
          [](char c) { return (c < 0 || c >= 128); });
          if (first_non_ASCII == short_file_name.end ())
            return short_file_name;
        }
    }

  // 3. Create hard link with only-ASCII characters.
  // Get longest possible part of path that only contains ASCII chars.

  std::wstring::iterator w_first_non_ASCII
    = std::find_if (w_full_file_name_str.begin (), w_full_file_name_str.end (),
  [](wchar_t c) { return (c < 0 || c >= 128); });
  std::wstring tmp_substr
    = std::wstring (w_full_file_name_str.begin (), w_first_non_ASCII);

  std::size_t pos
    = tmp_substr.find_last_of (u8_to_wstring (file_ops::dir_sep_chars ()));

  std::string par_dir
    = u8_from_wstring (w_full_file_name_str.substr (0, pos+1));

  // Create .oct_ascii directory.
  // FIXME: We need to have write permission in this location.

  std::string oct_ascii_dir = par_dir + ".oct_ascii";
  std::string test_dir = canonicalize_file_name (oct_ascii_dir);

  if (test_dir.empty ())
    {
      std::string msg;
      int status = sys::mkdir (oct_ascii_dir, 0777, msg);

      if (status < 0)
        return orig_file_name;

      // Set hidden property.
      SetFileAttributesA (oct_ascii_dir.c_str (), FILE_ATTRIBUTE_HIDDEN);
    }

  // Create file from hash of full filename.
  std::string filename_hash
    = (oct_ascii_dir + file_ops::dir_sep_str ()
       + crypto::hash ("SHA1", orig_file_name));

  // FIXME: This is just to check if the file exists.  Use a more efficient
  // method.
  std::string abs_filename_hash = canonicalize_file_name (filename_hash);

  if (! abs_filename_hash.empty ())
    sys::unlink (filename_hash);

  // At this point, we know that we have only ASCII characters.
  // So instead of converting, just copy the characters to std::wstring.
  std::wstring w_filename_hash (filename_hash.begin (),
                                filename_hash.end ());

  if (CreateHardLinkW (w_filename_hash.c_str (), w_orig_file_name, nullptr))
    return filename_hash;

#else

  octave_unused_parameter (allow_locale);

#endif

  return orig_file_name;
}

OCTAVE_END_NAMESPACE(sys)
OCTAVE_END_NAMESPACE(octave)