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 <cerrno>
31 #include <cstdio>
32 #include <cstdlib>
33 #include <cstring>
34 
35 #include <vector>
36 
37 #if defined (OCTAVE_USE_WINDOWS_API)
38 #  include <windows.h>
39 #  include <shlwapi.h>
40 #endif
41 
42 #if (defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) && ! defined (OCTAVE_HAVE_POSIX_FILESYSTEM))
43 #  include <algorithm>
44 #  include "localcharset-wrapper.h"
45 #  include "uniconv-wrappers.h"
46 #endif
47 
48 #include "areadlink-wrapper.h"
49 #include "canonicalize-file-name-wrapper.h"
50 #include "dir-ops.h"
51 #include "file-ops.h"
52 #include "file-stat.h"
53 #include "gen-tempname-wrapper.h"
54 #include "lo-sysdep.h"
55 #include "oct-env.h"
56 #include "oct-locbuf.h"
57 #include "oct-passwd.h"
58 #include "quit.h"
59 #include "stat-wrappers.h"
60 #include "str-vec.h"
61 #include "unistd-wrappers.h"
62 
63 namespace octave
64 {
65   // The following tilde-expansion code was stolen and adapted from
66   // readline.
67 
68   // The default value of tilde_additional_prefixes.  This is set to
69   // whitespace preceding a tilde so that simple programs which do not
70   // perform any word separation get desired behavior.
71   static const char *default_prefixes[] = { " ~", "\t~", ":~", nullptr };
72 
73   // The default value of tilde_additional_suffixes.  This is set to
74   // whitespace or newline so that simple programs which do not perform
75   // any word separation get desired behavior.
76   static const char *default_suffixes[] = { " ", "\n", ":", nullptr };
77 
78   static std::size_t
tilde_find_prefix(const std::string & s,std::size_t & len)79   tilde_find_prefix (const std::string& s, std::size_t& len)
80   {
81     len = 0;
82 
83     std::size_t s_len = s.length ();
84 
85     if (s_len == 0 || s[0] == '~')
86       return 0;
87 
88     string_vector prefixes = sys::file_ops::tilde_additional_prefixes;
89 
90     if (! prefixes.empty ())
91       {
92         for (std::size_t i = 0; i < s_len; i++)
93           {
94             for (int j = 0; j < prefixes.numel (); j++)
95               {
96                 std::size_t pfx_len = prefixes[j].length ();
97 
98                 if (prefixes[j] == s.substr (i, pfx_len))
99                   {
100                     len = pfx_len - 1;
101                     return i + len;
102                   }
103               }
104           }
105       }
106 
107     return s_len;
108   }
109 
110   // Find the end of a tilde expansion in S, and return the index
111   // of the character which ends the tilde definition.
112 
113   static std::size_t
tilde_find_suffix(const std::string & s)114   tilde_find_suffix (const std::string& s)
115   {
116     std::size_t s_len = s.length ();
117 
118     string_vector suffixes = sys::file_ops::tilde_additional_suffixes;
119 
120     std::size_t i = 0;
121 
122     for ( ; i < s_len; i++)
123       {
124         if (sys::file_ops::is_dir_sep (s[i]))
125           break;
126 
127         if (! suffixes.empty ())
128           {
129             for (int j = 0; j < suffixes.numel (); j++)
130               {
131                 std::size_t sfx_len = suffixes[j].length ();
132 
133                 if (suffixes[j] == s.substr (i, sfx_len))
134                   return i;
135               }
136           }
137       }
138 
139     return i;
140   }
141 
142   // Take FNAME and return the tilde prefix we want expanded.
143 
144   static std::string
isolate_tilde_prefix(const std::string & fname)145   isolate_tilde_prefix (const std::string& fname)
146   {
147     std::size_t f_len = fname.length ();
148 
149     std::size_t len = 1;
150 
151     while (len < f_len && ! sys::file_ops::is_dir_sep (fname[len]))
152       len++;
153 
154     return fname.substr (1, len);
155   }
156 
157   // Do the work of tilde expansion on FILENAME.  FILENAME starts with a
158   // tilde.
159 
160   static std::string
tilde_expand_word(const std::string & filename)161   tilde_expand_word (const std::string& filename)
162   {
163     std::size_t f_len = filename.length ();
164 
165     if (f_len == 0 || filename[0] != '~')
166       return std::string (filename);
167 
168     // A leading '~/' or a bare '~' is *always* translated to the value
169     // of $HOME or the home directory of the current user, regardless of
170     // any preexpansion hook.
171 
172     if (f_len == 1 || sys::file_ops::is_dir_sep (filename[1]))
173       return sys::env::get_home_directory () + filename.substr (1);
174 
175     std::string username = isolate_tilde_prefix (filename);
176 
177     std::size_t user_len = username.length ();
178 
179     std::string dirname;
180 
181     if (sys::file_ops::tilde_expansion_preexpansion_hook)
182       {
183         std::string expansion
184           = sys::file_ops::tilde_expansion_preexpansion_hook (username);
185 
186         if (! expansion.empty ())
187           return expansion + filename.substr (user_len+1);
188       }
189 
190     // No preexpansion hook, or the preexpansion hook failed.  Look in the
191     // password database.
192 
193     sys::password pw = sys::password::getpwnam (username);
194 
195     if (! pw)
196       {
197         // If the calling program has a special syntax for expanding tildes,
198         // and we couldn't find a standard expansion, then let them try.
199 
200         if (sys::file_ops::tilde_expansion_failure_hook)
201           {
202             std::string expansion
203               = sys::file_ops::tilde_expansion_failure_hook (username);
204 
205             if (! expansion.empty ())
206               dirname = expansion + filename.substr (user_len+1);
207           }
208 
209         // If we don't have a failure hook, or if the failure hook did not
210         // expand the tilde, return a copy of what we were passed.
211 
212         if (dirname.empty ())
213           dirname = filename;
214       }
215     else
216       dirname = pw.dir () + filename.substr (user_len+1);
217 
218     return dirname;
219   }
220 
221   namespace sys
222   {
223     namespace file_ops
224     {
dev_sep_char(void)225       char dev_sep_char (void)
226       {
227 #if (defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) && ! defined (OCTAVE_HAVE_POSIX_FILESYSTEM))
228         return ':';
229 #else
230         return 0;
231 #endif
232       }
233 
dir_sep_char(void)234       char dir_sep_char (void)
235       {
236 #if (defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) && ! defined (OCTAVE_HAVE_POSIX_FILESYSTEM))
237         return '\\';
238 #else
239         return '/';
240 #endif
241       }
242 
dir_sep_str(void)243       std::string dir_sep_str (void)
244       {
245 #if (defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) && ! defined (OCTAVE_HAVE_POSIX_FILESYSTEM))
246         return R"(\)";
247 #else
248         return "/";
249 #endif
250       }
251 
dir_sep_chars(void)252       std::string dir_sep_chars (void)
253       {
254 #if defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM)
255         return R"(/\)";
256 #else
257         return dir_sep_str ();
258 #endif
259       }
260 
261       tilde_expansion_hook tilde_expansion_preexpansion_hook = nullptr;
262 
263       tilde_expansion_hook tilde_expansion_failure_hook = nullptr;
264 
265       string_vector tilde_additional_prefixes = default_prefixes;
266 
267       string_vector tilde_additional_suffixes = default_suffixes;
268 
is_dev_sep(char c)269       bool is_dev_sep (char c)
270       {
271 #if (defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) && ! defined (OCTAVE_HAVE_POSIX_FILESYSTEM))
272         return c == dev_sep_char ();
273 #else
274         octave_unused_parameter (c);
275 
276         return false;
277 #endif
278       }
279 
is_dir_sep(char c)280       bool is_dir_sep (char c)
281       {
282         std::string tmp = dir_sep_chars ();
283         return tmp.find (c) != std::string::npos;
284       }
285 
tilde_expand(const std::string & name)286       std::string tilde_expand (const std::string& name)
287       {
288         if (name.find ('~') == std::string::npos)
289           return std::string (name);
290         else
291           {
292             std::string result;
293 
294             std::size_t name_len = name.length ();
295 
296             // Scan through S expanding tildes as we come to them.
297 
298             std::size_t pos = 0;
299 
300             while (1)
301               {
302                 if (pos > name_len)
303                   break;
304 
305                 std::size_t len;
306 
307                 // Make START point to the tilde which starts the expansion.
308 
309                 std::size_t start = tilde_find_prefix (name.substr (pos), len);
310 
311                 result.append (name.substr (pos, start));
312 
313                 // Advance STRING to the starting tilde.
314 
315                 pos += start;
316 
317                 // Make FINI be the index of one after the last character of the
318                 // username.
319 
320                 std::size_t fini = tilde_find_suffix (name.substr (pos));
321 
322                 // If both START and FINI are zero, we are all done.
323 
324                 if (! (start || fini))
325                   break;
326 
327                 // Expand the entire tilde word, and copy it into RESULT.
328 
329                 std::string tilde_word = name.substr (pos, fini);
330 
331                 pos += fini;
332 
333                 std::string expansion = tilde_expand_word (tilde_word);
334 
335                 result.append (expansion);
336               }
337 
338             return result;
339           }
340       }
341 
tilde_expand(const string_vector & names)342       string_vector tilde_expand (const string_vector& names)
343       {
344         int n = names.numel ();
345 
346         string_vector retval (n);
347 
348         for (int i = 0; i < n; i++)
349           retval[i] = tilde_expand (names[i]);
350 
351         return retval;
352       }
353 
concat(const std::string & dir,const std::string & file)354       std::string concat (const std::string& dir, const std::string& file)
355       {
356         return dir.empty ()
357           ? file
358           : (is_dir_sep (dir.back ())
359              ? dir + file
360              : dir + dir_sep_char () + file);
361       }
362 
dirname(const std::string & path)363       std::string dirname (const std::string& path)
364       {
365         std::size_t ipos = path.find_last_of (dir_sep_chars ());
366 
367         return (ipos != std::string::npos) ? path.substr (0, ipos) : "";
368       }
369 
tail(const std::string & path)370       std::string tail (const std::string& path)
371       {
372         std::size_t ipos = path.find_last_of (dir_sep_chars ());
373 
374         if (ipos != std::string::npos)
375           ipos++;
376         else
377           ipos = 0;
378 
379         return path.substr (ipos);
380       }
381 
native_separator_path(const std::string & path)382       std::string native_separator_path (const std::string& path)
383       {
384         std::string retval;
385 
386         if (dir_sep_char () == '/')
387           retval = path;
388         else
389           {
390             std::size_t n = path.length ();
391             for (std::size_t i = 0; i < n; i++)
392               {
393                 if (path[i] == '/')
394                   retval += dir_sep_char();
395                 else
396                   retval += path[i];
397               }
398           }
399 
400         return retval;
401       }
402     }
403 
mkdir(const std::string & nm,mode_t md)404     int mkdir (const std::string& nm, mode_t md)
405     {
406       std::string msg;
407       return mkdir (nm, md, msg);
408     }
409 
mkdir(const std::string & name,mode_t mode,std::string & msg)410     int mkdir (const std::string& name, mode_t mode, std::string& msg)
411     {
412       msg = "";
413 
414       int status = octave_mkdir_wrapper (name.c_str (), mode);
415 
416       if (status < 0)
417         msg = std::strerror (errno);
418 
419       return status;
420     }
421 
mkfifo(const std::string & nm,mode_t md)422     int mkfifo (const std::string& nm, mode_t md)
423     {
424       std::string msg;
425       return mkfifo (nm, md, msg);
426     }
427 
mkfifo(const std::string & name,mode_t mode,std::string & msg)428     int mkfifo (const std::string& name, mode_t mode, std::string& msg)
429     {
430       msg = "";
431 
432       int status = octave_mkfifo_wrapper (name.c_str (), mode);
433 
434       if (status < 0)
435         msg = std::strerror (errno);
436 
437       return status;
438     }
439 
link(const std::string & old_name,const std::string & new_name)440     int link (const std::string& old_name, const std::string& new_name)
441     {
442       std::string msg;
443       return link (old_name, new_name, msg);
444     }
445 
link(const std::string & old_name,const std::string & new_name,std::string & msg)446     int link (const std::string& old_name, const std::string& new_name,
447               std::string& msg)
448     {
449       msg = "";
450 
451       int status = -1;
452 
453       status = octave_link_wrapper (old_name.c_str (), new_name.c_str ());
454 
455       if (status < 0)
456         msg = std::strerror (errno);
457 
458       return status;
459     }
460 
symlink(const std::string & old_name,const std::string & new_name)461     int symlink (const std::string& old_name, const std::string& new_name)
462     {
463       std::string msg;
464       return symlink (old_name, new_name, msg);
465     }
466 
symlink(const std::string & old_name,const std::string & new_name,std::string & msg)467     int symlink (const std::string& old_name, const std::string& new_name,
468                  std::string& msg)
469     {
470       msg = "";
471 
472       int status = -1;
473 
474       status = octave_symlink_wrapper (old_name.c_str (), new_name.c_str ());
475 
476       if (status < 0)
477         msg = std::strerror (errno);
478 
479       return status;
480     }
481 
readlink(const std::string & path,std::string & result)482     int readlink (const std::string& path, std::string& result)
483     {
484       std::string msg;
485       return readlink (path, result, msg);
486     }
487 
readlink(const std::string & path,std::string & result,std::string & msg)488     int readlink (const std::string& path, std::string& result, std::string& msg)
489     {
490       int status = -1;
491 
492       msg = "";
493 
494       char *buf = octave_areadlink_wrapper (path.c_str ());
495 
496       if (! buf)
497         msg = std::strerror (errno);
498       else
499         {
500           result = buf;
501           ::free (buf);
502           status = 0;
503         }
504 
505       return status;
506     }
507 
rename(const std::string & from,const std::string & to)508     int rename (const std::string& from, const std::string& to)
509     {
510       std::string msg;
511       return rename (from, to, msg);
512     }
513 
rename(const std::string & from,const std::string & to,std::string & msg)514     int rename (const std::string& from, const std::string& to,
515                 std::string& msg)
516     {
517       int status = -1;
518 
519       msg = "";
520 
521 #if defined (OCTAVE_USE_WINDOWS_API)
522       std::wstring wfrom = u8_to_wstring (from);
523       std::wstring wto = u8_to_wstring (to);
524       status = _wrename (wfrom.c_str (), wto.c_str ());
525 #else
526       status = std::rename (from.c_str (), to.c_str ());
527 #endif
528 
529       if (status < 0)
530         msg = std::strerror (errno);
531 
532       return status;
533     }
534 
rmdir(const std::string & name)535     int rmdir (const std::string& name)
536     {
537       std::string msg;
538       return rmdir (name, msg);
539     }
540 
rmdir(const std::string & name,std::string & msg)541     int rmdir (const std::string& name, std::string& msg)
542     {
543       msg = "";
544 
545       int status = -1;
546 
547       status = octave_rmdir_wrapper (name.c_str ());
548 
549       if (status < 0)
550         msg = std::strerror (errno);
551 
552       return status;
553     }
554 
555     // And a version that works recursively.
556 
recursive_rmdir(const std::string & name)557     int recursive_rmdir (const std::string& name)
558     {
559       std::string msg;
560       return recursive_rmdir (name, msg);
561     }
562 
recursive_rmdir(const std::string & name,std::string & msg)563     int recursive_rmdir (const std::string& name, std::string& msg)
564     {
565       msg = "";
566 
567       int status = 0;
568 
569       string_vector dirlist;
570 
571       if (get_dirlist (name, dirlist, msg))
572         {
573           for (octave_idx_type i = 0; i < dirlist.numel (); i++)
574             {
575               octave_quit ();
576 
577               std::string nm = dirlist[i];
578 
579               // Skip current directory and parent.
580               if (nm == "." || nm == "..")
581                 continue;
582 
583               std::string fullnm = name + file_ops::dir_sep_str () + nm;
584 
585               // Get info about the file.  Don't follow links.
586               file_stat fs (fullnm, false);
587 
588               if (fs)
589                 {
590                   if (fs.is_dir ())
591                     {
592                       status = recursive_rmdir (fullnm, msg);
593 
594                       if (status < 0)
595                         break;
596                     }
597                   else
598                     {
599                       status = unlink (fullnm, msg);
600 
601                       if (status < 0)
602                         break;
603                     }
604                 }
605               else
606                 {
607                   msg = fs.error ();
608                   break;
609                 }
610             }
611 
612           if (status >= 0)
613             status = rmdir (name, msg);
614         }
615       else
616         status = -1;
617 
618       return status;
619     }
620 
umask(mode_t mode)621     int umask (mode_t mode)
622     {
623       return octave_umask_wrapper (mode);
624     }
625 
unlink(const std::string & name)626     int unlink (const std::string& name)
627     {
628       std::string msg;
629       return unlink (name, msg);
630     }
631 
unlink(const std::string & name,std::string & msg)632     int unlink (const std::string& name, std::string& msg)
633     {
634       msg = "";
635 
636       int status = -1;
637 
638       status = octave_unlink_wrapper (name.c_str ());
639 
640       if (status < 0)
641         msg = std::strerror (errno);
642 
643       return status;
644     }
645 
tempnam(const std::string & dir,const std::string & pfx)646     std::string tempnam (const std::string& dir, const std::string& pfx)
647     {
648       std::string msg;
649       return tempnam (dir, pfx, msg);
650     }
651 
tempnam(const std::string & dir,const std::string & pfx,std::string & msg)652     std::string tempnam (const std::string& dir, const std::string& pfx,
653                          std::string& msg)
654     {
655       msg = "";
656 
657       std::string retval;
658 
659       // get dir path to use for template
660       std::string templatename;
661       if (dir.empty ())
662         templatename = env::get_temp_directory ();
663       else if (! file_stat (dir, false).is_dir ())
664         templatename = env::get_temp_directory ();
665       else
666         templatename = dir;
667 
668       // add dir sep char if it is not there
669       if (*templatename.rbegin () != file_ops::dir_sep_char ())
670         templatename += file_ops::dir_sep_char ();
671 
672       if (pfx.empty ())
673         templatename += "file";
674       else
675         templatename += pfx;
676 
677       // add the required XXXXXX for the template
678       templatename += "XXXXXX";
679 
680       // create and copy template to char array for call to gen_tempname
681       char tname [templatename.length () + 1];
682 
683       strcpy (tname, templatename.c_str ());
684 
685       if (octave_gen_tempname_wrapper (tname) == -1)
686         msg = std::strerror (errno);
687       else
688         retval = tname;
689 
690       return retval;
691     }
692 
canonicalize_file_name(const std::string & name)693     std::string canonicalize_file_name (const std::string& name)
694     {
695       std::string msg;
696       return canonicalize_file_name (name, msg);
697     }
698 
canonicalize_file_name(const std::string & name,std::string & msg)699     std::string canonicalize_file_name (const std::string& name, std::string& msg)
700     {
701       msg = "";
702 
703       std::string retval;
704 
705 #if (defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) && ! defined (OCTAVE_HAVE_POSIX_FILESYSTEM))
706       // On Windows, convert to locale charset before passing to
707       // canonicalize_file_name, and convert back to UTF-8 after that.
708 
709       // FIXME: This only allows non-ASCII characters in the file or path that
710       // can be encoded in the locale charset.
711       // Consider replacing this with std::filesystem::canonical once we allow
712       // using C++17.
713 
714       const char *locale = octave_locale_charset_wrapper ();
715       const uint8_t *name_u8 = reinterpret_cast<const uint8_t *>
716                                  (name.c_str ());
717       std::size_t length = 0;
718       char *name_locale = octave_u8_conv_to_encoding (locale, name_u8,
719                                                       name.length () + 1,
720                                                       &length);
721 
722       if (name_locale)
723         {
724           char *tmp_locale =
725             octave_canonicalize_file_name_wrapper (name_locale);
726           free (name_locale);
727 
728           if (tmp_locale)
729             {
730               char *tmp = reinterpret_cast<char *>
731                             (octave_u8_conv_from_encoding (locale, tmp_locale,
732                                                            strlen (tmp_locale),
733                                                            &length));
734               free (tmp_locale);
735 
736               if (tmp)
737                 {
738                   retval = std::string (tmp, length);
739                   free (tmp);
740                 }
741             }
742         }
743 #else
744       char *tmp = octave_canonicalize_file_name_wrapper (name.c_str ());
745 
746       if (tmp)
747         {
748           retval = tmp;
749           free (tmp);
750         }
751 #endif
752 
753 #if (defined (OCTAVE_HAVE_WINDOWS_FILESYSTEM) && ! defined (OCTAVE_HAVE_POSIX_FILESYSTEM))
754       // Canonical Windows file separator is backslash.
755       std::replace (retval.begin (), retval.end (), '/', '\\');
756 #endif
757 
758 #if defined (OCTAVE_USE_WINDOWS_API)
759       std::wstring w_tmp;
760       bool strip_marker = true;
761       if (retval.empty ())
762         {
763           // For UNC paths, take the input as is.
764           // Also translate forward slashes to backslashes.
765           std::string name_backsl = name;
766           std::replace (name_backsl.begin (), name_backsl.end (), '/', '\\');
767           if (name_backsl.compare (0, 2, "\\\\") == 0)
768             {
769               w_tmp = u8_to_wstring (name_backsl);
770               strip_marker = false;
771               wchar_t canon_path[MAX_PATH];
772               if (PathCanonicalizeW (canon_path, w_tmp.c_str ()))
773                 w_tmp = std::wstring (canon_path);
774             }
775         }
776       else
777         w_tmp = L"\\\\?\\" + u8_to_wstring (retval);
778 
779       if (! w_tmp.empty ())
780         {
781           // Get a more canonical name wrt case and full names
782           // FIXME: To make this work on partitions that don't store short file
783           // names, use FindFirstFileW on each component of the path.
784           // Insufficient access permissions on parent folders might make this
785           // tricky.
786 
787           // Parts of the path that wouldn't fit into a short 8.3 file name are
788           // copied as is by GetLongPathNameW.  To also get the correct case
789           // for these parts, first convert to short file names and than back
790           // to long.
791           wchar_t buffer[32767] = L"";
792           int w_len = GetShortPathNameW (w_tmp.c_str (), buffer, 32767);
793           w_len = GetLongPathNameW (buffer, buffer, 32767);
794 
795           if (! strip_marker)
796             retval = u8_from_wstring (std::wstring (buffer, w_len));
797           else if (w_len > 4)
798             retval = u8_from_wstring (std::wstring (buffer+4, w_len-4));
799 
800           // If root is a drive, use an upper case letter for the drive letter.
801           if (retval.length () > 1 && retval[1] == ':')
802             retval[0] = toupper (retval[0]);
803         }
804 #endif
805 
806       if (retval.empty ())
807         msg = std::strerror (errno);
808 
809       return retval;
810     }
811   }
812 }
813