1 ////////////////////////////////////////////////////////////////////////////////
3 // Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez.
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 // http://www.opensource.org/licenses/mit-license.php
25 ////////////////////////////////////////////////////////////////////////////////
31 #include <sys/types.h>
44 #if defined SOLARIS || defined NETBSD || defined FREEBSD || !defined(__GLIBC__)
49 #include <sys/syslimits.h>
52 // Fixes build with musl libc.
61 ////////////////////////////////////////////////////////////////////////////////
66 ////////////////////////////////////////////////////////////////////////////////
67 Path::Path (const Path& other)
71 _original = other._original;
76 ////////////////////////////////////////////////////////////////////////////////
77 Path::Path (const std::string& in)
83 ////////////////////////////////////////////////////////////////////////////////
84 Path& Path::operator= (const Path& other)
88 this->_original = other._original;
89 this->_data = other._data;
95 ////////////////////////////////////////////////////////////////////////////////
96 bool Path::operator== (const Path& other)
98 return _data == other._data;
101 ////////////////////////////////////////////////////////////////////////////////
102 Path& Path::operator+= (const std::string& dir)
108 ////////////////////////////////////////////////////////////////////////////////
109 Path::operator std::string () const
114 ////////////////////////////////////////////////////////////////////////////////
115 std::string Path::name () const
119 auto slash = _data.rfind ('/');
120 if (slash != std::string::npos)
121 return _data.substr (slash + 1, std::string::npos);
127 ////////////////////////////////////////////////////////////////////////////////
128 std::string Path::parent () const
132 auto slash = _data.rfind ('/');
133 if (slash != std::string::npos)
134 return _data.substr (0, slash);
140 ////////////////////////////////////////////////////////////////////////////////
141 std::string Path::extension () const
145 auto dot = _data.rfind ('.');
146 if (dot != std::string::npos)
147 return _data.substr (dot + 1, std::string::npos);
153 ////////////////////////////////////////////////////////////////////////////////
154 bool Path::exists () const
156 return access (_data.c_str (), F_OK) ? false : true;
159 ////////////////////////////////////////////////////////////////////////////////
160 bool Path::is_directory () const
165 if (stat (_data.c_str (), &s))
166 throw format ("stat error {1}: {2}", errno, strerror (errno));
168 return S_ISDIR (s.st_mode);
174 ////////////////////////////////////////////////////////////////////////////////
175 bool Path::is_absolute () const
177 if (_data.length () && _data[0] == '/')
183 ////////////////////////////////////////////////////////////////////////////////
184 bool Path::is_link () const
187 if (lstat (_data.c_str (), &s))
188 throw format ("lstat error {1}: {2}", errno, strerror (errno));
190 return S_ISLNK (s.st_mode);
193 ////////////////////////////////////////////////////////////////////////////////
194 // EACCES is a permissions problem which is exactly what this method is trying
196 bool Path::readable () const
198 auto status = access (_data.c_str (), R_OK);
199 if (status == -1 && errno != EACCES)
200 throw format ("access error {1}: {2}", errno, strerror (errno));
202 return status ? false : true;
205 ////////////////////////////////////////////////////////////////////////////////
206 // EACCES is a permissions problem which is exactly what this method is trying
208 bool Path::writable () const
210 auto status = access (_data.c_str (), W_OK);
211 if (status == -1 && errno != EACCES)
212 throw format ("access error {1}: {2}", errno, strerror (errno));
214 return status ? false : true;
217 ////////////////////////////////////////////////////////////////////////////////
218 // EACCES is a permissions problem which is exactly what this method is trying
220 bool Path::executable () const
222 auto status = access (_data.c_str (), X_OK);
223 if (status == -1 && errno != EACCES)
224 throw format ("access error {1}: {2}", errno, strerror (errno));
226 return status ? false : true;
229 ////////////////////////////////////////////////////////////////////////////////
230 bool Path::rename (const std::string& new_name)
232 auto expanded = expand (new_name);
233 if (_data != expanded)
235 if (std::rename (_data.c_str (), expanded.c_str ()) == 0)
245 ////////////////////////////////////////////////////////////////////////////////
247 // ~foo/x --> /home/foo/s
248 // ~/x --> /home/foo/x
251 std::string Path::expand (const std::string& in)
253 std::string copy = in;
255 auto tilde = copy.find ('~');
256 std::string::size_type slash;
258 if (tilde != std::string::npos)
260 const char *home = getenv("HOME");
263 struct passwd* pw = getpwuid (getuid ());
267 // Convert: ~ --> /home/user
268 if (copy.length () == 1)
271 // Convert: ~/x --> /home/user/x
272 else if (copy.length () > tilde + 1 &&
273 copy[tilde + 1] == '/')
275 copy.replace (tilde, 1, home);
278 // Convert: ~foo/x --> /home/foo/x
279 else if ((slash = copy.find ('/', tilde)) != std::string::npos)
281 std::string name = copy.substr (tilde + 1, slash - tilde - 1);
282 struct passwd* pw = getpwnam (name.c_str ());
284 copy.replace (tilde, slash - tilde, pw->pw_dir);
289 else if (in.length () > 2 &&
290 in.substr (0, 2) == "./")
292 copy = Directory::cwd () + in.substr (1);
294 else if (in.length () > 1 &&
298 copy = Directory::cwd () + '/' + in;
304 ////////////////////////////////////////////////////////////////////////////////
305 std::vector <std::string> Path::glob (const std::string& pattern)
307 std::vector <std::string> results;
311 if (!::glob (pattern.c_str (), GLOB_ERR, nullptr, &g))
313 if (!::glob (pattern.c_str (), GLOB_ERR | GLOB_BRACE | GLOB_TILDE, nullptr, &g))
315 for (int i = 0; i < (int) g.gl_pathc; ++i)
316 results.push_back (g.gl_pathv[i]);
322 ////////////////////////////////////////////////////////////////////////////////
331 ////////////////////////////////////////////////////////////////////////////////
332 File::File (const Path& other)
340 ////////////////////////////////////////////////////////////////////////////////
341 File::File (const File& other)
349 ////////////////////////////////////////////////////////////////////////////////
350 File::File (const std::string& in)
358 ////////////////////////////////////////////////////////////////////////////////
365 ////////////////////////////////////////////////////////////////////////////////
366 File& File::operator= (const File& other)
369 Path::operator= (other);
375 ////////////////////////////////////////////////////////////////////////////////
376 bool File::create (int mode /* = 0640 */)
388 ////////////////////////////////////////////////////////////////////////////////
389 bool File::remove () const
391 return unlink (_data.c_str ()) == 0 ? true : false;
394 ////////////////////////////////////////////////////////////////////////////////
395 std::string File::removeBOM (const std::string& input)
397 if (input[0] && input[0] == '\xEF' &&
398 input[1] && input[1] == '\xBB' &&
399 input[2] && input[2] == '\xBF')
400 return input.substr (3);
405 ////////////////////////////////////////////////////////////////////////////////
412 bool already_exists = exists ();
414 if (!readable () || !writable ())
415 throw std::string (format ("Insufficient permissions for '{1}'.", _data));
417 _fh = fopen (_data.c_str (), (already_exists ? "r+" : "w+"));
425 throw format ("fopen error {1}: {2}", errno, strerror (errno));
434 ////////////////////////////////////////////////////////////////////////////////
443 throw format ("fclose error {1}: {2}", errno, strerror (errno));
451 ////////////////////////////////////////////////////////////////////////////////
458 // l_type l_whence l_start l_len l_pid l_sysid
459 struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0, 0 };
461 // l_type l_whence l_start l_len l_pid
462 struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };
464 fl.l_pid = getpid ();
465 if (fcntl (_h, F_SETLKW, &fl) == 0)
472 ////////////////////////////////////////////////////////////////////////////////
478 // l_type l_whence l_start l_len l_pid l_sysid
479 struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0, 0 };
481 // l_type l_whence l_start l_len l_pid
482 struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };
484 fl.l_pid = getpid ();
486 fcntl (_h, F_SETLK, &fl);
491 ////////////////////////////////////////////////////////////////////////////////
492 // Opens if necessary.
493 void File::read (std::string& contents)
496 contents.reserve (size ());
498 std::ifstream in (_data.c_str ());
503 line.reserve (512 * 1024);
504 while (getline (in, line))
506 // Detect forbidden BOM on first line.
509 line = File::removeBOM (line);
513 contents += line + '\n';
520 ////////////////////////////////////////////////////////////////////////////////
521 // Opens if necessary.
522 void File::read (std::vector <std::string>& contents)
526 std::ifstream in (_data.c_str ());
531 line.reserve (512 * 1024);
532 while (getline (in, line))
534 // Detect forbidden BOM on first line.
537 line = File::removeBOM (line);
541 contents.push_back (line);
548 ////////////////////////////////////////////////////////////////////////////////
549 // Opens if necessary.
550 void File::append (const std::string& line)
557 fseek (_fh, 0, SEEK_END);
559 if (fputs (line.c_str (), _fh) == EOF)
560 throw format ("fputs error {1}: {2}", errno, strerror (errno));
564 ////////////////////////////////////////////////////////////////////////////////
565 // Opens if necessary.
566 void File::append (const std::vector <std::string>& lines)
573 fseek (_fh, 0, SEEK_END);
575 for (auto& line : lines)
576 if (fputs (line.c_str (), _fh) == EOF)
577 throw format ("fputs error {1}: {2}", errno, strerror (errno));
581 ////////////////////////////////////////////////////////////////////////////////
582 void File::write_raw (const std::string& line)
588 if (fputs (line.c_str (), _fh) == EOF)
589 throw format ("fputs error {1}: {2}", errno, strerror (errno));
592 ////////////////////////////////////////////////////////////////////////////////
593 void File::truncate ()
599 if (ftruncate (_h, 0))
600 throw format ("ftruncate error {1}: {2}", errno, strerror (errno));
603 ////////////////////////////////////////////////////////////////////////////////
604 // S_IFMT 0170000 type of file
605 // S_IFIFO 0010000 named pipe (fifo)
606 // S_IFCHR 0020000 character special
607 // S_IFDIR 0040000 directory
608 // S_IFBLK 0060000 block special
609 // S_IFREG 0100000 regular
610 // S_IFLNK 0120000 symbolic link
611 // S_IFSOCK 0140000 socket
612 // S_IFWHT 0160000 whiteout
613 // S_ISUID 0004000 set user id on execution
614 // S_ISGID 0002000 set group id on execution
615 // S_ISVTX 0001000 save swapped text even after use
616 // S_IRUSR 0000400 read permission, owner
617 // S_IWUSR 0000200 write permission, owner
618 // S_IXUSR 0000100 execute/search permission, owner
622 if (stat (_data.c_str (), &s))
623 throw format ("stat error {1}: {2}", errno, strerror (errno));
628 ////////////////////////////////////////////////////////////////////////////////
629 size_t File::size () const
632 if (stat (_data.c_str (), &s))
633 throw format ("stat error {1}: {2}", errno, strerror (errno));
638 ////////////////////////////////////////////////////////////////////////////////
639 time_t File::mtime () const
642 if (stat (_data.c_str (), &s))
643 throw format ("stat error {1}: {2}", errno, strerror (errno));
648 ////////////////////////////////////////////////////////////////////////////////
649 time_t File::ctime () const
652 if (stat (_data.c_str (), &s))
653 throw format ("stat error {1}: {2}", errno, strerror (errno));
658 ////////////////////////////////////////////////////////////////////////////////
659 time_t File::btime () const
662 if (stat (_data.c_str (), &s))
663 throw format ("stat error {1}: {2}", errno, strerror (errno));
665 #ifdef HAVE_ST_BIRTHTIME
666 return s.st_birthtime;
672 ////////////////////////////////////////////////////////////////////////////////
673 bool File::create (const std::string& name, int mode /* = 0640 */)
675 std::string full_name = expand (name);
676 std::ofstream out (full_name.c_str ());
680 if (chmod (full_name.c_str (), mode))
681 throw format ("chmod error {1}: {2}", errno, strerror (errno));
689 ////////////////////////////////////////////////////////////////////////////////
690 bool File::read (const std::string& name, std::string& contents)
694 std::ifstream in (name.c_str ());
700 while (getline (in, line))
702 // Detect forbidden BOM on first line.
705 line = File::removeBOM (line);
709 contents += line + '\n';
719 ////////////////////////////////////////////////////////////////////////////////
720 bool File::read (const std::string& name, std::vector <std::string>& contents)
724 std::ifstream in (name.c_str ());
730 while (getline (in, line))
732 // Detect forbidden BOM on first line.
735 line = File::removeBOM (line);
739 contents.push_back (line);
749 ////////////////////////////////////////////////////////////////////////////////
750 bool File::write (const std::string& name, const std::string& contents)
752 std::ofstream out (expand (name).c_str (),
753 std::ios_base::out | std::ios_base::trunc);
764 ////////////////////////////////////////////////////////////////////////////////
766 const std::string& name,
767 const std::vector <std::string>& lines,
768 bool addNewlines /* = true */)
770 std::ofstream out (expand (name).c_str (),
771 std::ios_base::out | std::ios_base::trunc);
774 for (auto& line : lines)
789 ////////////////////////////////////////////////////////////////////////////////
790 bool File::remove (const std::string& name)
792 return unlink (expand (name).c_str ()) == 0 ? true : false;
795 ////////////////////////////////////////////////////////////////////////////////
796 bool File::copy (const std::string& from, const std::string& to)
798 // 'from' must exist.
799 if (! access (from.c_str (), F_OK))
801 std::ifstream src (from, std::ios::binary);
802 std::ofstream dst (to, std::ios::binary);
811 ////////////////////////////////////////////////////////////////////////////////
812 bool File::move (const std::string& from, const std::string& to)
814 auto expanded = expand (to);
815 if (from != expanded)
816 if (std::rename (from.c_str (), to.c_str ()) == 0)
822 ////////////////////////////////////////////////////////////////////////////////
823 Directory::Directory ()
827 ////////////////////////////////////////////////////////////////////////////////
828 Directory::Directory (const Directory& other)
833 ////////////////////////////////////////////////////////////////////////////////
834 Directory::Directory (const File& other)
839 ////////////////////////////////////////////////////////////////////////////////
840 Directory::Directory (const Path& other)
845 ////////////////////////////////////////////////////////////////////////////////
846 Directory::Directory (const std::string& in)
851 ////////////////////////////////////////////////////////////////////////////////
852 Directory& Directory::operator= (const Directory& other)
855 File::operator= (other);
860 ////////////////////////////////////////////////////////////////////////////////
861 bool Directory::create (int mode /* = 0755 */)
863 // No error handling because we want failure to be silent, somewhat emulating
865 return mkdir (_data.c_str (), mode) == 0 ? true : false;
868 ////////////////////////////////////////////////////////////////////////////////
869 bool Directory::remove () const
871 return remove_directory (_data);
874 ////////////////////////////////////////////////////////////////////////////////
875 bool Directory::remove_directory (const std::string& dir) const
877 DIR* dp = opendir (dir.c_str ());
881 while ((de = readdir (dp)) != nullptr)
883 if (! strcmp (de->d_name, ".") ||
884 ! strcmp (de->d_name, ".."))
887 #if defined (SOLARIS) || defined (HAIKU)
889 if (lstat ((dir + '/' + de->d_name).c_str (), &s))
890 throw format ("lstat error {1}: {2}", errno, strerror (errno));
892 if (S_ISDIR (s.st_mode))
893 remove_directory (dir + '/' + de->d_name);
895 unlink ((dir + '/' + de->d_name).c_str ());
897 if (de->d_type == DT_UNKNOWN)
900 if (lstat ((dir + '/' + de->d_name).c_str (), &s))
901 throw format ("lstat error {1}: {2}", errno, strerror (errno));
903 if (S_ISDIR (s.st_mode))
906 if (de->d_type == DT_DIR)
907 remove_directory (dir + '/' + de->d_name);
909 unlink ((dir + '/' + de->d_name).c_str ());
916 return rmdir (dir.c_str ()) ? false : true;
919 ////////////////////////////////////////////////////////////////////////////////
920 std::vector <std::string> Directory::list ()
922 std::vector <std::string> files;
923 list (_data, files, false);
927 ////////////////////////////////////////////////////////////////////////////////
928 std::vector <std::string> Directory::listRecursive ()
930 std::vector <std::string> files;
931 list (_data, files, true);
935 ////////////////////////////////////////////////////////////////////////////////
936 std::string Directory::cwd ()
938 #ifdef HAVE_GET_CURRENT_DIR_NAME
939 char *buf = get_current_dir_name ();
941 throw std::bad_alloc ();
942 std::string result (buf);
947 getcwd (buf, PATH_MAX - 1);
948 return std::string (buf);
952 ////////////////////////////////////////////////////////////////////////////////
953 bool Directory::up ()
958 auto slash = _data.rfind ('/');
961 _data = "/"; // Root dir should retain the slash.
964 else if (slash != std::string::npos)
966 _data = _data.substr (0, slash);
973 ////////////////////////////////////////////////////////////////////////////////
974 bool Directory::cd () const
976 return chdir (_data.c_str ()) == 0 ? true : false;
979 ////////////////////////////////////////////////////////////////////////////////
980 void Directory::list (
981 const std::string& base,
982 std::vector <std::string>& results,
985 DIR* dp = opendir (base.c_str ());
989 while ((de = readdir (dp)) != nullptr)
991 if (!strcmp (de->d_name, ".") ||
992 !strcmp (de->d_name, ".."))
995 #if defined (SOLARIS) || defined (HAIKU)
997 if (stat ((base + '/' + de->d_name).c_str (), &s))
998 throw format ("stat error {1}: {2}", errno, strerror (errno));
1000 if (recursive && S_ISDIR (s.st_mode))
1001 list (base + '/' + de->d_name, results, recursive);
1003 results.push_back (base + '/' + de->d_name);
1005 if (recursive && de->d_type == DT_UNKNOWN)
1008 if (lstat ((base + '/' + de->d_name).c_str (), &s))
1009 throw format ("lstat error {1}: {2}", errno, strerror (errno));
1011 if (S_ISDIR (s.st_mode))
1012 de->d_type = DT_DIR;
1014 if (recursive && de->d_type == DT_DIR)
1015 list (base + '/' + de->d_name, results, recursive);
1017 results.push_back (base + '/' + de->d_name);
1025 ////////////////////////////////////////////////////////////////////////////////