GNU Octave  6.2.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
lo-sysdep.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 1996-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include "dir-ops.h"
31 #include "file-ops.h"
32 #include "lo-error.h"
33 #include "lo-sysdep.h"
34 #include "putenv-wrapper.h"
35 #include "uniconv-wrappers.h"
36 #include "unistd-wrappers.h"
37 #include "unsetenv-wrapper.h"
38 
39 #if defined (OCTAVE_USE_WINDOWS_API)
40 # include <windows.h>
41 # include <wchar.h>
42 
43 # include "lo-hash.h"
44 # include "filepos-wrappers.h"
45 # include "unwind-prot.h"
46 #endif
47 
48 namespace octave
49 {
50  namespace sys
51  {
52  std::string
53  getcwd (void)
54  {
55  std::string retval;
56 
57 #if defined (OCTAVE_USE_WINDOWS_API)
58  wchar_t *tmp = _wgetcwd (nullptr, 0);
59 
60  if (! tmp)
61  (*current_liboctave_error_handler) ("unable to find current directory");
62 
63  std::wstring tmp_wstr (tmp);
64  free (tmp);
65 
66  std::string tmp_str = u8_from_wstring (tmp_wstr);
67 
68  retval = tmp_str;
69 
70 #else
71  // Using octave_getcwd_wrapper ensures that we have a getcwd that
72  // will allocate a buffer as large as necessary if buf and size are
73  // both 0.
74 
75  char *tmp = octave_getcwd_wrapper (nullptr, 0);
76 
77  if (! tmp)
78  (*current_liboctave_error_handler) ("unable to find current directory");
79 
80  retval = tmp;
81  free (tmp);
82 #endif
83 
84  return retval;
85  }
86 
87  int
88  chdir (const std::string& path_arg)
89  {
90  std::string path = sys::file_ops::tilde_expand (path_arg);
91 
92 #if defined (OCTAVE_USE_WINDOWS_API)
93  if (path.length () == 2 && path[1] == ':')
94  path += '\\';
95 #endif
96 
97  return octave_chdir_wrapper (path.c_str ());
98  }
99 
100  bool
101  get_dirlist (const std::string& dirname, string_vector& dirlist,
102  std::string& msg)
103  {
104  dirlist = "";
105  msg = "";
106 #if defined (OCTAVE_USE_WINDOWS_API)
107  _WIN32_FIND_DATAW ffd;
108 
109  std::string path_name (dirname);
110  if (path_name.empty ())
111  return true;
112 
113  if (path_name.back () == '\\' || path_name.back () == '/')
114  path_name.push_back ('*');
115  else
116  path_name.append (R"(\*)");
117 
118  // Find first file in directory.
119  std::wstring wpath_name = u8_to_wstring (path_name);
120  HANDLE hFind = FindFirstFileW (wpath_name.c_str (), &ffd);
121  if (INVALID_HANDLE_VALUE == hFind)
122  {
123  DWORD errCode = GetLastError ();
124  char *errorText = nullptr;
125  FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM |
126  FORMAT_MESSAGE_ALLOCATE_BUFFER |
127  FORMAT_MESSAGE_IGNORE_INSERTS,
128  nullptr, errCode,
129  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
130  reinterpret_cast <char *> (&errorText), 0, nullptr);
131  if (errorText != nullptr)
132  {
133  msg = std::string (errorText);
134  LocalFree (errorText);
135  }
136  return false;
137  }
138 
139  std::list<std::string> dirlist_str;
140  do
141  dirlist_str.push_back (u8_from_wstring (ffd.cFileName));
142  while (FindNextFileW (hFind, &ffd) != 0);
143 
144  FindClose(hFind);
145 
146  dirlist = string_vector (dirlist_str);
147 
148 #else
149 
150  dir_entry dir (dirname);
151 
152  if (! dir)
153  {
154  msg = dir.error ();
155  return false;
156  }
157 
158  dirlist = dir.read ();
159 
160  dir.close ();
161 #endif
162 
163  return true;
164  }
165 
166 #if defined (OCTAVE_USE_WINDOWS_API)
167 
168  static bool check_fseek_ftell_workaround_needed (bool set_nonbuffered_mode)
169  {
170  // To check whether the workaround is needed:
171  //
172  // * Create a tmp file with LF line endings only.
173  //
174  // * Open that file for reading in text mode.
175  //
176  // * Read a line.
177  //
178  // * Use ftello to record the position of the beginning of the
179  // second line.
180  //
181  // * Read and save the contents of the second line.
182  //
183  // * Use fseeko to return to the saved position.
184  //
185  // * Read the second line again and compare to the previously
186  // saved text.
187  //
188  // * If the lines are different, we need to set non-buffered
189  // input mode for files opened in text mode.
190 
191  std::string tmpname = sys::tempnam ("", "oct-");
192 
193  if (tmpname.empty ())
194  {
195  (*current_liboctave_warning_handler)
196  ("fseek/ftell bug check failed (tmp name creation)!");
197  return false;
198  }
199 
200  std::FILE *fptr = std::fopen (tmpname.c_str (), "wb");
201 
202  if (! fptr)
203  {
204  (*current_liboctave_warning_handler)
205  ("fseek/ftell bug check failed (opening tmp file for writing)!");
206  return false;
207  }
208 
209  fprintf (fptr, "%s", "foo\nbar\nbaz\n");
210 
211  std::fclose (fptr);
212 
213  fptr = std::fopen (tmpname.c_str (), "rt");
214 
215  if (! fptr)
216  {
217  (*current_liboctave_warning_handler)
218  ("fseek/ftell bug check failed (opening tmp file for reading)!");
219  return false;
220  }
221 
222  unwind_action act ([fptr, tmpname] () {
223  std::fclose (fptr);
224  sys::unlink (tmpname);
225  });
226 
227  if (set_nonbuffered_mode)
228  ::setvbuf (fptr, nullptr, _IONBF, 0);
229 
230  while (true)
231  {
232  int c = fgetc (fptr);
233 
234  if (c == EOF)
235  {
236  (*current_liboctave_warning_handler)
237  ("fseek/ftell bug check failed (skipping first line)!");
238  return false;
239  }
240 
241  if (c == '\n')
242  break;
243  }
244 
245  off_t pos = octave_ftello_wrapper (fptr);
246 
247  char buf1[8];
248  int i = 0;
249  while (true)
250  {
251  int c = fgetc (fptr);
252 
253  if (c == EOF)
254  {
255  (*current_liboctave_warning_handler)
256  ("fseek/ftell bug check failed (reading second line)!");
257  return false;
258  }
259 
260  if (c == '\n')
261  break;
262 
263  buf1[i++] = static_cast<char> (c);
264  }
265  buf1[i] = '\0';
266 
268 
269  char buf2[8];
270  i = 0;
271  while (true)
272  {
273  int c = fgetc (fptr);
274 
275  if (c == EOF)
276  {
277  (*current_liboctave_warning_handler)
278  ("fseek/ftell bug check failed (reading after repositioning)!");
279  return false;
280  }
281 
282  if (c == '\n')
283  break;
284 
285  buf2[i++] = static_cast<char> (c);
286  }
287  buf2[i] = '\0';
288 
289  return strcmp (buf1, buf2);
290  }
291 
292 #endif
293 
294  std::FILE *
295  fopen (const std::string& filename, const std::string& mode)
296  {
297 #if defined (OCTAVE_USE_WINDOWS_API)
298 
299  std::wstring wfilename = u8_to_wstring (filename);
300  std::wstring wmode = u8_to_wstring (mode);
301 
302  std::FILE *fptr = _wfopen (wfilename.c_str (), wmode.c_str ());
303 
304  static bool fseek_ftell_bug_workaround_needed = false;
305  static bool fseek_ftell_bug_checked = false;
306 
307  if (! fseek_ftell_bug_checked && mode.find ('t') != std::string::npos)
308  {
309  // FIXME: Is the following workaround needed for all files
310  // opened in text mode, or only for files opened for reading?
311 
312  // Try to avoid fseek/ftell bug on Windows systems by setting
313  // non-buffered input mode for files opened in text mode, but
314  // only if it appears that the workaround is needed. See
315  // Octave bug #58055.
316 
317  // To check whether the workaround is needed:
318  //
319  // * Create a tmp file with LF line endings only.
320  //
321  // * Open that file for reading in text mode.
322  //
323  // * Read a line.
324  //
325  // * Use ftello to record the position of the beginning of
326  // the second line.
327  //
328  // * Read and save the contents of the second line.
329  //
330  // * Use fseeko to return to the saved position.
331  //
332  // * Read the second line again and compare to the
333  // previously saved text.
334  //
335  // * If the lines are different, we need to set non-buffered
336  // input mode for files opened in text mode.
337  //
338  // * To verify that the workaround solves the problem,
339  // repeat the above test with non-buffered input mode. If
340  // that fails, warn that there may be trouble with
341  // ftell/fseek when reading files opened in text mode.
342 
343  if (check_fseek_ftell_workaround_needed (false))
344  {
345  if (check_fseek_ftell_workaround_needed (true))
346  (*current_liboctave_warning_handler)
347  ("fseek/ftell may fail for files opened in text mode");
348  else
349  fseek_ftell_bug_workaround_needed = true;
350  }
351 
352  fseek_ftell_bug_checked = true;
353  }
354 
355  if (fseek_ftell_bug_workaround_needed
356  && mode.find ('t') != std::string::npos)
357  ::setvbuf (fptr, nullptr, _IONBF, 0);
358 
359  return fptr;
360 
361 #else
362  return std::fopen (filename.c_str (), mode.c_str ());
363 #endif
364  }
365 
367  fstream (const std::string& filename, const std::ios::openmode mode)
368  {
369 #if defined (OCTAVE_USE_WINDOWS_API)
370 
371  std::wstring wfilename = u8_to_wstring (filename);
372 
373  return std::fstream (wfilename.c_str (), mode);
374 
375 #else
376  return std::fstream (filename.c_str (), mode);
377 #endif
378  }
379 
381  ifstream (const std::string& filename, const std::ios::openmode mode)
382  {
383 #if defined (OCTAVE_USE_WINDOWS_API)
384 
385  std::wstring wfilename = u8_to_wstring (filename);
386 
387  return std::ifstream (wfilename.c_str (), mode);
388 
389 #else
390  return std::ifstream (filename.c_str (), mode);
391 #endif
392  }
393 
395  ofstream (const std::string& filename, const std::ios::openmode mode)
396  {
397 #if defined (OCTAVE_USE_WINDOWS_API)
398 
399  std::wstring wfilename = u8_to_wstring (filename);
400 
401  return std::ofstream (wfilename.c_str (), mode);
402 
403 #else
404  return std::ofstream (filename.c_str (), mode);
405 #endif
406  }
407 
408  void
409  putenv_wrapper (const std::string& name, const std::string& value)
410  {
411  // This function was adapted from xputenv from Karl Berry's kpathsearch
412  // library.
413  // FIXME: make this do the right thing if we don't have a SMART_PUTENV.
414 
415  int new_len = name.length () + value.length () + 2;
416 
417  // FIXME: This leaks memory, but so would a call to setenv.
418  // Short of extreme measures to track memory, altering the environment
419  // always leaks memory, but the saving grace is that the leaks are small.
420 
421  char *new_item = static_cast<char *> (std::malloc (new_len));
422 
423  if (new_item)
424  sprintf (new_item, "%s=%s", name.c_str (), value.c_str ());
425 
426  // As far as I can see there's no way to distinguish between the
427  // various errors; putenv doesn't have errno values.
428 
429 #if defined (OCTAVE_USE_WINDOWS_API)
430  wchar_t *wnew_item = u8_to_wchar (new_item);
431  unwind_protect frame;
432  frame.add_fcn (std::free, static_cast<void *> (new_item));
433  if (_wputenv (wnew_item) < 0)
434  (*current_liboctave_error_handler) ("putenv (%s) failed", new_item);
435 #else
436  if (octave_putenv_wrapper (new_item) < 0)
437  (*current_liboctave_error_handler) ("putenv (%s) failed", new_item);
438 #endif
439  }
440 
441  std::string
442  getenv_wrapper (const std::string& name)
443  {
444 #if defined (OCTAVE_USE_WINDOWS_API)
445  std::wstring wname = u8_to_wstring (name);
446  wchar_t *env = _wgetenv (wname.c_str ());
447  return env ? u8_from_wstring (env) : "";
448 #else
449  char *env = ::getenv (name.c_str ());
450  return env ? env : "";
451 #endif
452  }
453 
454  int
455  unsetenv_wrapper (const std::string& name)
456  {
457 #if defined (OCTAVE_USE_WINDOWS_API)
458  putenv_wrapper (name, "");
459 
460  std::wstring wname = u8_to_wstring (name);
461  return (SetEnvironmentVariableW (wname.c_str (), nullptr) ? 0 : -1);
462 #else
463  return octave_unsetenv_wrapper (name.c_str ());
464 #endif
465  }
466 
467  std::wstring
468  u8_to_wstring (const std::string& utf8_string)
469  {
470  size_t srclen = utf8_string.length ();
471  const uint8_t *src = reinterpret_cast<const uint8_t *>
472  (utf8_string.c_str ());
473 
474  size_t length = 0;
475  wchar_t *wchar = reinterpret_cast<wchar_t *>
476  (octave_u8_conv_to_encoding ("wchar_t", src, srclen,
477  &length));
478 
479  std::wstring retval = L"";
480  if (wchar != nullptr)
481  {
482  retval = std::wstring (wchar, length / sizeof (wchar_t));
483  free (static_cast<void *> (wchar));
484  }
485 
486  return retval;
487  }
488 
489  std::string
490  u8_from_wstring (const std::wstring& wchar_string)
491  {
492  size_t srclen = wchar_string.length () * sizeof (wchar_t);
493  const char *src = reinterpret_cast<const char *> (wchar_string.c_str ());
494 
495  size_t length = 0;
496  char *mbchar = reinterpret_cast<char *>
497  (octave_u8_conv_from_encoding ("wchar_t", src, srclen,
498  &length));
499 
500  std::string retval = "";
501  if (mbchar != nullptr)
502  {
503  retval = std::string (mbchar, length);
504  free (static_cast<void *> (mbchar));
505  }
506 
507  return retval;
508  }
509 
510  // At quite a few places in the code we are passing file names as
511  // char arrays to external library functions.
512 
513  // When these functions try to locate the corresponding file on the
514  // disc, they need to use the wide character API on Windows to
515  // correctly open files with non-ASCII characters.
516 
517  // But they have no way of knowing which encoding we are using for
518  // the passed string. So they have no way of reliably converting to
519  // a wchar_t array. (I.e. there is no possible fix for these
520  // functions with current C or C++.)
521 
522  // To solve the dilemma, the function "get_ASCII_filename" first
523  // checks whether there are any non-ASCII characters in the passed
524  // file name. If there are not, it returns the original name.
525 
526  // Otherwise, it tries to obtain the short file name (8.3 naming
527  // scheme) which only consists of ASCII characters and are safe to
528  // pass. However, short file names can be disabled for performance
529  // reasons on the file system level with NTFS. So there is no
530  // guarantee that these exist.
531 
532  // If short file names are not stored, a hard link to the file is
533  // created. For this the path to the file is split at the deepest
534  // possible level that doesn't contain non-ASCII characters. At
535  // that level a hidden folder is created that holds the hard links.
536  // That means we need to have write access on that location. A path
537  // to that hard link is returned.
538 
539  // If the file system is FAT32, there are no hard links. But FAT32
540  // always stores short file names. So we are safe.
541 
542  // ExFAT that is occasionally used on USB sticks and SD cards stores
543  // neither short file names nor does it support hard links. So for
544  // exFAT with this function, there is (currently) no way to generate
545  // a file name that is stripped from non-ASCII characters but still
546  // is valid.
547 
548  // For Unixy systems, this function does nothing.
549 
550  std::string
551  get_ASCII_filename (const std::string& orig_file_name)
552  {
553 #if defined (OCTAVE_USE_WINDOWS_API)
554 
555  // Return file name that only contains ASCII characters that can
556  // be used to access the file orig_file_name. The original file
557  // must exist in the file system before calling this function.
558  // This is useful for passing file names to functions that are not
559  // aware of the character encoding we are using.
560 
561  // 1. Check whether filename contains non-ASCII (UTF-8) characters.
562 
563  std::string::const_iterator first_non_ASCII
564  = std::find_if (orig_file_name.begin (), orig_file_name.end (),
565  [](char c) { return (c < 0 || c >= 128); });
566 
567  if (first_non_ASCII == orig_file_name.end ())
568  return orig_file_name;
569 
570  // 2. Check if file system stores short filenames (always
571  // ASCII-only).
572 
573  std::wstring w_orig_file_name_str = u8_to_wstring (orig_file_name);
574  const wchar_t *w_orig_file_name = w_orig_file_name_str.c_str ();
575 
576  // Get full path to file
577  wchar_t w_full_file_name[_MAX_PATH];
578  if (_wfullpath (w_full_file_name, w_orig_file_name, _MAX_PATH) == nullptr)
579  return orig_file_name;
580 
581  std::wstring w_full_file_name_str = w_full_file_name;
582 
583  // Get short filename (8.3) from UTF-16 filename.
584 
585  long length = GetShortPathNameW (w_full_file_name, nullptr, 0);
586 
587  if (length > 0)
588  {
589  // Dynamically allocate the correct size (terminating null char
590  // was included in length).
591 
592  wchar_t *w_short_file_name = new wchar_t[length];
593  GetShortPathNameW (w_full_file_name, w_short_file_name, length);
594 
595  std::wstring w_short_file_name_str
596  = std::wstring (w_short_file_name, length);
597  std::string short_file_name = u8_from_wstring (w_short_file_name_str);
598 
599  if (w_short_file_name_str.compare (0, length-1, w_full_file_name_str) != 0)
600  return short_file_name;
601  }
602 
603  // 3. Create hard link with only-ASCII characters.
604  // Get longest possible part of path that only contains ASCII chars.
605 
606  std::wstring::iterator w_first_non_ASCII
607  = std::find_if (w_full_file_name_str.begin (), w_full_file_name_str.end (),
608  [](wchar_t c) { return (c < 0 || c >= 128); });
609  std::wstring tmp_substr
610  = std::wstring (w_full_file_name_str.begin (), w_first_non_ASCII);
611 
612  size_t pos
613  = tmp_substr.find_last_of (u8_to_wstring (file_ops::dir_sep_chars ()));
614 
615  std::string par_dir
616  = u8_from_wstring (w_full_file_name_str.substr (0, pos+1));
617 
618  // Create .oct_ascii directory.
619  // FIXME: We need to have write permission in this location.
620 
621  std::string oct_ascii_dir = par_dir + ".oct_ascii";
622  std::string test_dir = canonicalize_file_name (oct_ascii_dir);
623 
624  if (test_dir.empty ())
625  {
626  std::string msg;
627  int status = sys::mkdir (oct_ascii_dir, 0777, msg);
628 
629  if (status < 0)
630  return orig_file_name;
631 
632  // Set hidden property.
633  SetFileAttributesA (oct_ascii_dir.c_str (), FILE_ATTRIBUTE_HIDDEN);
634  }
635 
636  // Create file from hash of full filename.
637  std::string filename_hash
638  = (oct_ascii_dir + file_ops::dir_sep_str ()
639  + crypto::hash ("SHA1", orig_file_name));
640 
641  std::string abs_filename_hash = canonicalize_file_name (filename_hash);
642 
643  if (! abs_filename_hash.empty ())
644  sys::unlink (filename_hash);
645 
646  wchar_t w_filename_hash[filename_hash.length ()+1] = {0};
647 
648  for (size_t i=0; i < filename_hash.length (); i++)
649  w_filename_hash[i] = filename_hash.at (i);
650 
651  if (CreateHardLinkW (w_filename_hash, w_orig_file_name, nullptr))
652  return filename_hash;
653 
654 #endif
655 
656  return orig_file_name;
657  }
658  }
659 }
#define SEEK_SET
void add_fcn(void(*fcn)(Params...), Args &&... args)
Definition: dir-ops.h:42
bool close(void)
Definition: dir-ops.cc:93
string_vector read(void)
Definition: dir-ops.cc:73
std::string error(void) const
Definition: dir-ops.h:86
int octave_fseeko_wrapper(FILE *fp, off_t offset, int whence)
off_t octave_ftello_wrapper(FILE *fp)
QString path
QString name
OCTAVE_NORETURN liboctave_error_handler current_liboctave_error_handler
Definition: lo-error.c:41
std::string hash(hash_fptr hash_fcn, const std::string &str, int result_buf_len)
Definition: lo-hash.cc:45
FloatComplex(* fptr)(const FloatComplex &, float, int, octave_idx_type &)
Definition: lo-specfun.cc:1128
bool strcmp(const T &str_a, const T &str_b)
True if strings are the same.
Definition: oct-string.cc:122
std::string dirname(const std::string &path)
Definition: file-ops.cc:363
std::string dir_sep_str(void)
Definition: file-ops.cc:243
std::string tilde_expand(const std::string &name)
Definition: file-ops.cc:286
std::string dir_sep_chars(void)
Definition: file-ops.cc:252
std::string get_ASCII_filename(const std::string &orig_file_name)
Definition: lo-sysdep.cc:551
std::string tempnam(const std::string &dir, const std::string &pfx)
Definition: file-ops.cc:646
std::string canonicalize_file_name(const std::string &name)
Definition: file-ops.cc:693
std::string getcwd(void)
Definition: lo-sysdep.cc:53
std::FILE * fopen(const std::string &filename, const std::string &mode)
Definition: lo-sysdep.cc:295
int mkdir(const std::string &nm, mode_t md)
Definition: file-ops.cc:404
std::string u8_from_wstring(const std::wstring &wchar_string)
Definition: lo-sysdep.cc:490
int unsetenv_wrapper(const std::string &name)
Definition: lo-sysdep.cc:455
std::ifstream ifstream(const std::string &filename, const std::ios::openmode mode)
Definition: lo-sysdep.cc:381
int chdir(const std::string &path_arg)
Definition: lo-sysdep.cc:88
void putenv_wrapper(const std::string &name, const std::string &value)
Definition: lo-sysdep.cc:409
std::fstream fstream(const std::string &filename, const std::ios::openmode mode)
Definition: lo-sysdep.cc:367
int unlink(const std::string &name)
Definition: file-ops.cc:626
std::string getenv_wrapper(const std::string &name)
Definition: lo-sysdep.cc:442
std::ofstream ofstream(const std::string &filename, const std::ios::openmode mode)
Definition: lo-sysdep.cc:395
bool get_dirlist(const std::string &dirname, string_vector &dirlist, std::string &msg)
Definition: lo-sysdep.cc:101
std::wstring u8_to_wstring(const std::string &utf8_string)
Definition: lo-sysdep.cc:468
void * malloc(unsigned)
void free(void *)
octave_value::octave_value(const Array< char > &chm, char type) return retval
Definition: ov.cc:811
int octave_putenv_wrapper(char *str)
uint8_t * octave_u8_conv_from_encoding(const char *fromcode, const char *src, size_t srclen, size_t *lengthp)
char * octave_u8_conv_to_encoding(const char *tocode, const uint8_t *src, size_t srclen, size_t *lengthp)
wchar_t * u8_to_wchar(const char *u8)
int octave_chdir_wrapper(const char *nm)
char * octave_getcwd_wrapper(char *nm, size_t len)
int octave_unsetenv_wrapper(const char *name)