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