1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2021, Paul Beckingham, Federico Hernandez.
4 //
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:
11 //
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
14 //
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
21 // SOFTWARE.
22 //
23 // http://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26 
27 #include <cmake.h>
28 // Needed for Cygwin compatibility (see #2632)
29 #if defined CYGWIN
30 #define _GNU_SOURCE
31 #endif
32 #include <FS.h>
33 #include <fstream>
34 #include <glob.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <stdlib.h>
38 #include <pwd.h>
39 #include <cstdio>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <dirent.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <shared.h>
46 #include <format.h>
47 
48 #if defined SOLARIS || defined NETBSD || defined FREEBSD || defined DRAGONFLY || !defined(__GLIBC__)
49 #include <limits.h>
50 #endif
51 
52 #if defined __APPLE__
53 #include <sys/syslimits.h>
54 #endif
55 
56 // Fixes build with musl libc.
57 #ifndef GLOB_TILDE
58 #define GLOB_TILDE 0
59 #endif
60 
61 #ifndef GLOB_BRACE
62 #define GLOB_BRACE 0
63 #endif
64 
65 ////////////////////////////////////////////////////////////////////////////////
66 Path::Path () = default;
67 
68 ////////////////////////////////////////////////////////////////////////////////
Path(const Path & other)69 Path::Path (const Path& other)
70 {
71   if (this != &other)
72   {
73     _original = other._original;
74     _data     = other._data;
75   }
76 }
77 
78 ////////////////////////////////////////////////////////////////////////////////
Path(const std::string & in)79 Path::Path (const std::string& in)
80 {
81   _original = in;
82   _data     = expand (in);
83 }
84 
85 ////////////////////////////////////////////////////////////////////////////////
operator =(const Path & other)86 Path& Path::operator= (const Path& other)
87 {
88   if (this != &other)
89   {
90     this->_original = other._original;
91     this->_data     = other._data;
92   }
93 
94   return *this;
95 }
96 
97 ////////////////////////////////////////////////////////////////////////////////
operator ==(const Path & other)98 bool Path::operator== (const Path& other)
99 {
100   return _data == other._data;
101 }
102 
103 ////////////////////////////////////////////////////////////////////////////////
operator +=(const std::string & dir)104 Path& Path::operator+= (const std::string& dir)
105 {
106   _data += '/' + dir;
107   return *this;
108 }
109 
110 ////////////////////////////////////////////////////////////////////////////////
operator std::string() const111 Path::operator std::string () const
112 {
113   return _data;
114 }
115 
116 ////////////////////////////////////////////////////////////////////////////////
name() const117 std::string Path::name () const
118 {
119   if (_data.length ())
120   {
121     auto slash = _data.rfind ('/');
122     if (slash != std::string::npos)
123       return _data.substr (slash + 1, std::string::npos);
124   }
125 
126  return _data;
127 }
128 
129 ////////////////////////////////////////////////////////////////////////////////
parent() const130 std::string Path::parent () const
131 {
132   if (_data.length ())
133   {
134     auto slash = _data.rfind ('/');
135     if (slash != std::string::npos)
136       return _data.substr (0, slash);
137     return ".";
138   }
139 
140   return "";
141 }
142 
143 ////////////////////////////////////////////////////////////////////////////////
extension() const144 std::string Path::extension () const
145 {
146   if (_data.length ())
147   {
148     auto dot = _data.rfind ('.');
149     if (dot != std::string::npos)
150       return _data.substr (dot + 1, std::string::npos);
151   }
152 
153   return "";
154 }
155 
156 ////////////////////////////////////////////////////////////////////////////////
realpath() const157 std::string Path::realpath () const
158 {
159   if (_data.empty ())
160     return "";
161 
162   char *result_c = ::realpath (_data.c_str(), NULL);
163   if (result_c == nullptr)
164     return "";
165 
166   std::string result (result_c);
167   free(result_c);
168   return result;
169 }
170 
171 ////////////////////////////////////////////////////////////////////////////////
exists() const172 bool Path::exists () const
173 {
174   return access (_data.c_str (), F_OK) ? false : true;
175 }
176 
177 ////////////////////////////////////////////////////////////////////////////////
is_directory() const178 bool Path::is_directory () const
179 {
180   if (exists ())
181   {
182     struct stat s {};
183     if (stat (_data.c_str (), &s))
184       throw format ("stat error {1}: {2}", errno, strerror (errno));
185 
186     return S_ISDIR (s.st_mode);
187   }
188 
189   return false;
190 }
191 
192 ////////////////////////////////////////////////////////////////////////////////
is_absolute() const193 bool Path::is_absolute () const
194 {
195   if (_data.length () && _data[0] == '/')
196     return true;
197 
198   return false;
199 }
200 
201 ////////////////////////////////////////////////////////////////////////////////
is_link() const202 bool Path::is_link () const
203 {
204   if (! exists ())
205     return false;
206 
207   struct stat s {};
208   if (lstat (_data.c_str (), &s))
209     throw format ("lstat error {1}: {2}", errno, strerror (errno));
210 
211   return S_ISLNK (s.st_mode);
212 }
213 
214 ////////////////////////////////////////////////////////////////////////////////
215 // EACCES is a permissions problem which is exactly what this method is trying
216 // to determine.
readable() const217 bool Path::readable () const
218 {
219   if (! exists ())
220     return false;
221 
222   auto status = access (_data.c_str (), R_OK);
223   if (status == -1 && errno != EACCES)
224     throw format ("access error {1}: {2}", errno, strerror (errno));
225 
226   return status ? false : true;
227 }
228 
229 ////////////////////////////////////////////////////////////////////////////////
230 // EACCES is a permissions problem which is exactly what this method is trying
231 // to determine.
writable() const232 bool Path::writable () const
233 {
234   if (! exists ())
235     return false;
236 
237   auto status = access (_data.c_str (), W_OK);
238   if (status == -1 && errno != EACCES)
239     throw format ("access error {1}: {2}", errno, strerror (errno));
240 
241   return status ? false : true;
242 }
243 
244 ////////////////////////////////////////////////////////////////////////////////
245 // EACCES is a permissions problem which is exactly what this method is trying
246 // to determine.
executable() const247 bool Path::executable () const
248 {
249   if (! exists ())
250     return false;
251 
252   auto status = access (_data.c_str (), X_OK);
253   if (status == -1 && errno != EACCES)
254     throw format ("access error {1}: {2}", errno, strerror (errno));
255 
256   return status ? false : true;
257 }
258 
259 ////////////////////////////////////////////////////////////////////////////////
rename(const std::string & new_name)260 bool Path::rename (const std::string& new_name)
261 {
262   auto expanded = expand (new_name);
263   if (_data != expanded)
264   {
265     if (std::rename (_data.c_str (), expanded.c_str ()) == 0)
266     {
267       _data = expanded;
268       return true;
269     }
270   }
271 
272   return false;
273 }
274 
275 ////////////////////////////////////////////////////////////////////////////////
276 // Supported expansion:
277 // - tilde       `~`    -> home directory of current user
278 // - tilde       `~foo` -> home directory of user `foo`
279 // - environment `$FOO` -> value of environment variable `FOO`
280 //
281 // Relative path is kept relative here.
282 //
283 // ~        --> /home/user
284 // ~foo/x   --> /home/foo/s
285 // ~/x      --> /home/foo/x
286 // $HOME/x  --> /home/foo/x
287 // ./$FOO/x --> ./<value of FOO>/x
288 // ./x      --> ./x
expand(const std::string & in)289 std::string Path::expand (const std::string& in)
290 {
291   std::string result;
292   for (size_t i = 0; i < in.length (); ++i)
293   {
294     // Expand `~` only at start.
295     if (i == 0 && in.at (0) == '~')
296     {
297       auto slash = in.find ('/', 1);
298       if (slash == std::string::npos)
299         slash = in.length ();
300 
301       // Convert: ~ --> /home/user
302       if (slash == 1)
303       {
304         const char* home = getenv ("HOME");
305         if (home == nullptr)
306         {
307           struct passwd* pw = getpwuid (getuid ());
308           home = pw->pw_dir;
309         }
310         result += home;
311       }
312       // Convert: ~name --> /home/name
313       else
314       {
315         std::string name = in.substr (1, slash - 1);
316         struct passwd* pw = getpwnam (name.c_str ());
317         if (pw)
318           result += pw->pw_dir;
319         else
320           result += "/home/" + name;
321       }
322 
323       // Process `/` in the next loop.
324       i = slash - 1;
325     }
326 
327     // Expand environment variable.
328     else if (in.at (i) == '$')
329     {
330       size_t end = i + 1;
331       while (end < in.length () && (isalnum(in.at (end)) || in.at (end) == '_'))
332         ++end;
333       if (i + 1 == end)
334         result += "$";
335       else
336       {
337         std::string name = in.substr (i + 1, end - (i + 1));
338         const char *value = getenv (name.c_str ());
339         if (value != nullptr)
340           result += value;
341       }
342       i = end - 1;
343     }
344 
345     // Otherwise, keep the char.
346     else
347       result += in.at (i);
348   }
349 
350   return result;
351 }
352 
353 ////////////////////////////////////////////////////////////////////////////////
glob(const std::string & pattern)354 std::vector <std::string> Path::glob (const std::string& pattern)
355 {
356   std::vector <std::string> results;
357 
358   glob_t g;
359 #ifdef SOLARIS
360   if (!::glob (pattern.c_str (), GLOB_ERR, nullptr, &g))
361 #else
362   if (!::glob (pattern.c_str (), GLOB_ERR | GLOB_BRACE | GLOB_TILDE, nullptr, &g))
363 #endif
364     for (int i = 0; i < (int) g.gl_pathc; ++i)
365       results.push_back (g.gl_pathv[i]);
366 
367   globfree (&g);
368   return results;
369 }
370 
371 ////////////////////////////////////////////////////////////////////////////////
File()372 File::File ()
373 : Path::Path ()
374 , _fh (nullptr)
375 , _h (-1)
376 , _locked (false)
377 {
378 }
379 
380 ////////////////////////////////////////////////////////////////////////////////
File(const Path & other)381 File::File (const Path& other)
382 : Path::Path (other)
383 , _fh (nullptr)
384 , _h (-1)
385 , _locked (false)
386 {
387 }
388 
389 ////////////////////////////////////////////////////////////////////////////////
File(const File & other)390 File::File (const File& other)
391 : Path::Path (other)
392 , _fh (nullptr)
393 , _h (-1)
394 , _locked (false)
395 {
396 }
397 
398 ////////////////////////////////////////////////////////////////////////////////
File(const std::string & in)399 File::File (const std::string& in)
400 : Path::Path (in)
401 , _fh (nullptr)
402 , _h (-1)
403 , _locked (false)
404 {
405 }
406 
407 ////////////////////////////////////////////////////////////////////////////////
~File()408 File::~File ()
409 {
410   try
411   {
412     if (_fh)
413       close ();
414   }
415   catch (...)
416   {
417   }
418 }
419 
420 ////////////////////////////////////////////////////////////////////////////////
operator =(const File & other)421 File& File::operator= (const File& other)
422 {
423   if (this != &other)
424     Path::operator= (other);
425 
426   _locked = false;
427   return *this;
428 }
429 
430 ////////////////////////////////////////////////////////////////////////////////
create(int mode)431 bool File::create (int mode /* = 0640 */)
432 {
433   if (open ())
434   {
435     fchmod (_h, mode);
436     close ();
437     return true;
438   }
439 
440   return false;
441 }
442 
443 ////////////////////////////////////////////////////////////////////////////////
remove() const444 bool File::remove () const
445 {
446   return unlink (_data.c_str ()) == 0 ? true : false;
447 }
448 
449 ////////////////////////////////////////////////////////////////////////////////
removeBOM(const std::string & input)450 std::string File::removeBOM (const std::string& input)
451 {
452   if (input[0] && input[0] == '\xEF' &&
453       input[1] && input[1] == '\xBB' &&
454       input[2] && input[2] == '\xBF')
455     return input.substr (3);
456 
457   return input;
458 }
459 
460 ////////////////////////////////////////////////////////////////////////////////
open()461 bool File::open ()
462 {
463   if (_data != "")
464   {
465     if (! _fh)
466     {
467       bool already_exists = exists ();
468       if (already_exists)
469         if (!readable () || !writable ())
470           throw std::string (format ("Insufficient permissions for '{1}'.", _data));
471 
472       _fh = fopen (_data.c_str (), (already_exists ? "r+" : "w+"));
473       if (_fh)
474       {
475         _h = fileno (_fh);
476         _locked = false;
477         return true;
478       }
479       else
480         throw format ("fopen error {1}: {2}", errno, strerror (errno));
481     }
482     else
483       return true;
484   }
485 
486   return false;
487 }
488 
489 ////////////////////////////////////////////////////////////////////////////////
close()490 void File::close ()
491 {
492   if (_fh)
493   {
494     if (_locked)
495       unlock ();
496 
497     // fdatasync() is faster we can't trust it anywhere but Linux.
498     // https://news.ycombinator.com/item?id=25171572
499     #if defined (LINUX)
500       if (fdatasync (fileno (_fh)))
501         throw format ("fdatasync error {1}: {2}", errno, strerror (errno));
502     #elif defined (DARWIN)
503       // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
504       // fsync() on macOS flush data to the drvice but does not force drive
505       // flush.
506       if (fcntl (fileno (_fh), F_FULLFSYNC, 0))
507         throw format ("fcntl F_FULLFSYNC error {1}: {2}", errno, strerror (errno));
508     #else
509       if (fsync (fileno (_fh)))
510         throw format ("fsync error {1}: {2}", errno, strerror (errno));
511     # endif
512     if (fclose (_fh))
513       throw format ("fclose error {1}: {2}", errno, strerror (errno));
514 
515     _fh = nullptr;
516     _h = -1;
517     _locked = false;
518   }
519 }
520 
521 ////////////////////////////////////////////////////////////////////////////////
lock()522 bool File::lock ()
523 {
524   _locked = false;
525   if (_fh && _h != -1)
526   {
527 #if defined(DARWIN)
528                     // l_start l_len l_pid l_type   l_whence
529     struct flock fl = {0,      0,    0,    F_WRLCK, SEEK_SET};
530 #elif defined(DRAGONFLY)
531                     // l_start l_len l_pid l_type   l_whence
532     struct flock fl = {0,      0,    0,    F_WRLCK, SEEK_SET};
533 #elif defined(FREEBSD)
534                     // l_type   l_whence  l_start  l_len  l_pid  l_sysid
535     struct flock fl = {F_WRLCK, SEEK_SET, 0,       0,     0,     0 };
536 #else
537                     // l_type   l_whence  l_start  l_len  l_pid
538     struct flock fl = {F_WRLCK, SEEK_SET, 0,       0,     0 };
539 #endif
540     fl.l_pid = getpid ();
541     if (fcntl (_h, F_SETLKW, &fl) == 0)
542       _locked = true;
543   }
544 
545   return _locked;
546 }
547 
548 ////////////////////////////////////////////////////////////////////////////////
unlock()549 void File::unlock ()
550 {
551   if (_locked)
552   {
553 #if defined(DARWIN)
554                     // l_start l_len l_pid l_type   l_whence
555     struct flock fl = {0,      0,    0,    F_WRLCK, SEEK_SET};
556 #elif defined(DRAGONFLY)
557                     // l_start l_len l_pid l_type   l_whence
558     struct flock fl = {0,      0,    0,    F_WRLCK, SEEK_SET};
559 #elif defined(FREEBSD)
560                     // l_type   l_whence  l_start  l_len  l_pid  l_sysid
561     struct flock fl = {F_WRLCK, SEEK_SET, 0,       0,     0,     0 };
562 #else
563                     // l_type   l_whence  l_start  l_len  l_pid
564     struct flock fl = {F_WRLCK, SEEK_SET, 0,       0,     0 };
565 #endif
566     fl.l_pid = getpid ();
567 
568     fcntl (_h, F_SETLK, &fl);
569     _locked = false;
570   }
571 }
572 
573 ////////////////////////////////////////////////////////////////////////////////
574 // Opens if necessary.
read(std::string & contents)575 void File::read (std::string& contents)
576 {
577   contents = "";
578   contents.reserve (size ());
579 
580   std::ifstream in (_data.c_str ());
581   if (in.good ())
582   {
583     bool first = true;
584     std::string line;
585     line.reserve (512 * 1024);
586     while (getline (in, line))
587     {
588       // Detect forbidden BOM on first line.
589       if (first)
590       {
591         line = File::removeBOM (line);
592         first = false;
593       }
594 
595       contents += line + '\n';
596     }
597 
598     in.close ();
599   }
600 }
601 
602 ////////////////////////////////////////////////////////////////////////////////
603 // Opens if necessary.
read(std::vector<std::string> & contents)604 void File::read (std::vector <std::string>& contents)
605 {
606   contents.clear ();
607 
608   std::ifstream in (_data.c_str ());
609   if (in.good ())
610   {
611     bool first = true;
612     std::string line;
613     line.reserve (512 * 1024);
614     while (getline (in, line))
615     {
616       // Detect forbidden BOM on first line.
617       if (first)
618       {
619         line = File::removeBOM (line);
620         first = false;
621       }
622 
623       contents.push_back (line);
624     }
625 
626     in.close ();
627   }
628 }
629 
630 ////////////////////////////////////////////////////////////////////////////////
631 // Opens if necessary.
append(const std::string & line)632 void File::append (const std::string& line)
633 {
634   if (!_fh)
635     open ();
636 
637   if (_fh)
638   {
639     fseek (_fh, 0, SEEK_END);
640 
641     if (fputs (line.c_str (), _fh) == EOF)
642       throw format ("fputs error {1}: {2}", errno, strerror (errno));
643   }
644 }
645 
646 ////////////////////////////////////////////////////////////////////////////////
647 // Opens if necessary.
append(const std::vector<std::string> & lines)648 void File::append (const std::vector <std::string>& lines)
649 {
650   if (!_fh)
651     open ();
652 
653   if (_fh)
654   {
655     fseek (_fh, 0, SEEK_END);
656 
657     for (auto& line : lines)
658       if (fputs (line.c_str (), _fh) == EOF)
659         throw format ("fputs error {1}: {2}", errno, strerror (errno));
660   }
661 }
662 
663 ////////////////////////////////////////////////////////////////////////////////
write_raw(const std::string & line)664 void File::write_raw (const std::string& line)
665 {
666   if (!_fh)
667     open ();
668 
669   if (_fh)
670     if (fputs (line.c_str (), _fh) == EOF)
671       throw format ("fputs error {1}: {2}", errno, strerror (errno));
672 }
673 
674 ////////////////////////////////////////////////////////////////////////////////
truncate()675 void File::truncate ()
676 {
677   if (!_fh)
678     open ();
679 
680   if (_fh)
681     if (ftruncate (_h, 0))
682       throw format ("ftruncate error {1}: {2}", errno, strerror (errno));
683 }
684 
685 ////////////////////////////////////////////////////////////////////////////////
686 //  S_IFMT          0170000  type of file
687 //         S_IFIFO  0010000  named pipe (fifo)
688 //         S_IFCHR  0020000  character special
689 //         S_IFDIR  0040000  directory
690 //         S_IFBLK  0060000  block special
691 //         S_IFREG  0100000  regular
692 //         S_IFLNK  0120000  symbolic link
693 //         S_IFSOCK 0140000  socket
694 //         S_IFWHT  0160000  whiteout
695 //  S_ISUID         0004000  set user id on execution
696 //  S_ISGID         0002000  set group id on execution
697 //  S_ISVTX         0001000  save swapped text even after use
698 //  S_IRUSR         0000400  read permission, owner
699 //  S_IWUSR         0000200  write permission, owner
700 //  S_IXUSR         0000100  execute/search permission, owner
mode()701 mode_t File::mode ()
702 {
703   struct stat s;
704   if (stat (_data.c_str (), &s))
705     throw format ("stat error {1}: {2}", errno, strerror (errno));
706 
707   return s.st_mode;
708 }
709 
710 ////////////////////////////////////////////////////////////////////////////////
size() const711 size_t File::size () const
712 {
713   struct stat s;
714   if (stat (_data.c_str (), &s))
715     throw format ("stat error {1}: {2}", errno, strerror (errno));
716 
717   return s.st_size;
718 }
719 
720 ////////////////////////////////////////////////////////////////////////////////
mtime() const721 time_t File::mtime () const
722 {
723   struct stat s;
724   if (stat (_data.c_str (), &s))
725     throw format ("stat error {1}: {2}", errno, strerror (errno));
726 
727   return s.st_mtime;
728 }
729 
730 ////////////////////////////////////////////////////////////////////////////////
ctime() const731 time_t File::ctime () const
732 {
733   struct stat s;
734   if (stat (_data.c_str (), &s))
735     throw format ("stat error {1}: {2}", errno, strerror (errno));
736 
737   return s.st_ctime;
738 }
739 
740 ////////////////////////////////////////////////////////////////////////////////
btime() const741 time_t File::btime () const
742 {
743   struct stat s;
744   if (stat (_data.c_str (), &s))
745     throw format ("stat error {1}: {2}", errno, strerror (errno));
746 
747 #ifdef HAVE_ST_BIRTHTIME
748   return s.st_birthtime;
749 #else
750   return s.st_ctime;
751 #endif
752 }
753 
754 ////////////////////////////////////////////////////////////////////////////////
create(const std::string & name,int mode)755 bool File::create (const std::string& name, int mode /* = 0640 */)
756 {
757   std::string full_name = expand (name);
758   std::ofstream out (full_name.c_str ());
759   if (out.good ())
760   {
761     out.close ();
762     if (chmod (full_name.c_str (), mode))
763       throw format ("chmod error {1}: {2}", errno, strerror (errno));
764 
765     return true;
766   }
767 
768   return false;
769 }
770 
771 ////////////////////////////////////////////////////////////////////////////////
read(const std::string & name,std::string & contents)772 bool File::read (const std::string& name, std::string& contents)
773 {
774   contents = "";
775 
776   std::ifstream in (name.c_str ());
777   if (in.good ())
778   {
779     bool first = true;
780     std::string line;
781     line.reserve (1024);
782     while (getline (in, line))
783     {
784       // Detect forbidden BOM on first line.
785       if (first)
786       {
787         line = File::removeBOM (line);
788         first = false;
789       }
790 
791       contents += line + '\n';
792     }
793 
794     in.close ();
795     return true;
796   }
797 
798   return false;
799 }
800 
801 ////////////////////////////////////////////////////////////////////////////////
read(const std::string & name,std::vector<std::string> & contents)802 bool File::read (const std::string& name, std::vector <std::string>& contents)
803 {
804   contents.clear ();
805 
806   std::ifstream in (name.c_str ());
807   if (in.good ())
808   {
809     bool first = true;
810     std::string line;
811     line.reserve (1024);
812     while (getline (in, line))
813     {
814       // Detect forbidden BOM on first line.
815       if (first)
816       {
817         line = File::removeBOM (line);
818         first = false;
819       }
820 
821       contents.push_back (line);
822     }
823 
824     in.close ();
825     return true;
826   }
827 
828   return false;
829 }
830 
831 ////////////////////////////////////////////////////////////////////////////////
write(const std::string & name,const std::string & contents)832 bool File::write (const std::string& name, const std::string& contents)
833 {
834   std::ofstream out (expand (name).c_str (),
835                      std::ios_base::out | std::ios_base::trunc);
836   if (out.good ())
837   {
838     out << contents;
839     out.close ();
840     return out.good ();
841   }
842 
843   return false;
844 }
845 
846 ////////////////////////////////////////////////////////////////////////////////
write(const std::string & name,const std::vector<std::string> & lines,bool addNewlines)847 bool File::write (
848   const std::string& name,
849   const std::vector <std::string>& lines,
850   bool addNewlines /* = true */)
851 {
852   std::ofstream out (expand (name).c_str (),
853                      std::ios_base::out | std::ios_base::trunc);
854   if (out.good ())
855   {
856     for (auto& line : lines)
857     {
858       out << line;
859 
860       if (addNewlines)
861         out << '\n';
862     }
863 
864     out.close ();
865     return out.good ();
866   }
867 
868   return false;
869 }
870 
871 ////////////////////////////////////////////////////////////////////////////////
remove(const std::string & name)872 bool File::remove (const std::string& name)
873 {
874   return unlink (expand (name).c_str ()) == 0 ? true : false;
875 }
876 
877 ////////////////////////////////////////////////////////////////////////////////
copy(const std::string & from,const std::string & to)878 bool File::copy (const std::string& from, const std::string& to)
879 {
880   std::ifstream src (from, std::ios::binary);
881 
882   if (! src.fail ())
883   {
884     std::ofstream dst (to,   std::ios::binary);
885     dst << src.rdbuf ();
886     dst.close ();
887     return dst.good () && ! src.bad ();
888   }
889 
890   return false;
891 }
892 
893 ////////////////////////////////////////////////////////////////////////////////
move(const std::string & from,const std::string & to)894 bool File::move (const std::string& from, const std::string& to)
895 {
896   auto expanded = expand (to);
897   if (from != expanded)
898     if (std::rename (from.c_str (), to.c_str ()) == 0)
899       return true;
900 
901   return false;
902 }
903 
904 ////////////////////////////////////////////////////////////////////////////////
905 Directory::Directory () = default;
906 
907 ////////////////////////////////////////////////////////////////////////////////
908 Directory::Directory (const Directory& other) = default;
909 
910 ////////////////////////////////////////////////////////////////////////////////
Directory(const File & other)911 Directory::Directory (const File& other)
912 : File::File (other)
913 {
914 }
915 
916 ////////////////////////////////////////////////////////////////////////////////
Directory(const Path & other)917 Directory::Directory (const Path& other)
918 : File::File (other)
919 {
920 }
921 
922 ////////////////////////////////////////////////////////////////////////////////
Directory(const std::string & in)923 Directory::Directory (const std::string& in)
924 : File::File (in)
925 {
926 }
927 
928 ////////////////////////////////////////////////////////////////////////////////
operator =(const Directory & other)929 Directory& Directory::operator= (const Directory& other)
930 {
931   if (this != &other)
932     File::operator= (other);
933 
934   return *this;
935 }
936 
937 ////////////////////////////////////////////////////////////////////////////////
create(int mode)938 bool Directory::create (int mode /* = 0755 */)
939 {
940   // No error handling because we want failure to be silent, somewhat emulating
941   // "mkdir -p".
942   Directory parent_dir = parent ();
943   if (! parent_dir.exists ())
944   {
945       if (! parent_dir.create (mode))
946       {
947           return false;
948       }
949   }
950   return mkdir (_data.c_str (), mode) == 0;
951 }
952 
953 ////////////////////////////////////////////////////////////////////////////////
remove() const954 bool Directory::remove () const
955 {
956   return remove_directory (_data);
957 }
958 
959 ////////////////////////////////////////////////////////////////////////////////
remove_directory(const std::string & dir) const960 bool Directory::remove_directory (const std::string& dir) const
961 {
962   DIR* dp = opendir (dir.c_str ());
963   if (dp != nullptr)
964   {
965     struct dirent* de;
966     while ((de = readdir (dp)) != nullptr)
967     {
968       if (! strcmp (de->d_name, ".") ||
969           ! strcmp (de->d_name, ".."))
970         continue;
971 
972 #if defined (SOLARIS) || defined (HAIKU)
973       struct stat s;
974       if (lstat ((dir + '/' + de->d_name).c_str (), &s))
975         throw format ("lstat error {1}: {2}", errno, strerror (errno));
976 
977       if (S_ISDIR (s.st_mode))
978         remove_directory (dir + '/' + de->d_name);
979       else
980         unlink ((dir + '/' + de->d_name).c_str ());
981 #else
982       if (de->d_type == DT_UNKNOWN)
983       {
984         struct stat s;
985         if (lstat ((dir + '/' + de->d_name).c_str (), &s))
986           throw format ("lstat error {1}: {2}", errno, strerror (errno));
987 
988         if (S_ISDIR (s.st_mode))
989           de->d_type = DT_DIR;
990       }
991       if (de->d_type == DT_DIR)
992         remove_directory (dir + '/' + de->d_name);
993       else
994         unlink ((dir + '/' + de->d_name).c_str ());
995 #endif
996     }
997 
998     closedir (dp);
999   }
1000 
1001   return rmdir (dir.c_str ()) ? false : true;
1002 }
1003 
1004 ////////////////////////////////////////////////////////////////////////////////
list()1005 std::vector <std::string> Directory::list ()
1006 {
1007   std::vector <std::string> files;
1008   list (_data, files, false);
1009   return files;
1010 }
1011 
1012 ////////////////////////////////////////////////////////////////////////////////
listRecursive()1013 std::vector <std::string> Directory::listRecursive ()
1014 {
1015   std::vector <std::string> files;
1016   list (_data, files, true);
1017   return files;
1018 }
1019 
1020 ////////////////////////////////////////////////////////////////////////////////
cwd()1021 std::string Directory::cwd ()
1022 {
1023 #ifdef HAVE_GET_CURRENT_DIR_NAME
1024   char *buf = get_current_dir_name ();
1025   if (buf == nullptr)
1026     throw std::bad_alloc ();
1027   std::string result (buf);
1028   free (buf);
1029   return result;
1030 #else
1031   char buf[PATH_MAX];
1032   getcwd (buf, PATH_MAX - 1);
1033   return std::string (buf);
1034 #endif
1035 }
1036 
1037 ////////////////////////////////////////////////////////////////////////////////
up()1038 bool Directory::up ()
1039 {
1040   if (_data == "/")
1041     return false;
1042 
1043   auto slash = _data.rfind ('/');
1044   if (slash == 0)
1045   {
1046     _data = "/";  // Root dir should retain the slash.
1047     return true;
1048   }
1049   else if (slash != std::string::npos)
1050   {
1051     _data = _data.substr (0, slash);
1052     return true;
1053   }
1054 
1055   return false;
1056 }
1057 
1058 ////////////////////////////////////////////////////////////////////////////////
cd() const1059 bool Directory::cd () const
1060 {
1061   return chdir (_data.c_str ()) == 0 ? true : false;
1062 }
1063 
1064 ////////////////////////////////////////////////////////////////////////////////
list(const std::string & base,std::vector<std::string> & results,bool recursive)1065 void Directory::list (
1066   const std::string& base,
1067   std::vector <std::string>& results,
1068   bool recursive)
1069 {
1070   DIR* dp = opendir (base.c_str ());
1071   if (dp != nullptr)
1072   {
1073     struct dirent* de;
1074     while ((de = readdir (dp)) != nullptr)
1075     {
1076       if (!strcmp (de->d_name, ".") ||
1077           !strcmp (de->d_name, ".."))
1078         continue;
1079 
1080 #if defined (SOLARIS) || defined (HAIKU)
1081       struct stat s;
1082       if (stat ((base + '/' + de->d_name).c_str (), &s))
1083         throw format ("stat error {1}: {2}", errno, strerror (errno));
1084 
1085       if (recursive && S_ISDIR (s.st_mode))
1086         list (base + '/' + de->d_name, results, recursive);
1087       else
1088         results.push_back (base + '/' + de->d_name);
1089 #else
1090       if (recursive && de->d_type == DT_UNKNOWN)
1091       {
1092         struct stat s;
1093         if (lstat ((base + '/' + de->d_name).c_str (), &s))
1094           throw format ("lstat error {1}: {2}", errno, strerror (errno));
1095 
1096         if (S_ISDIR (s.st_mode))
1097           de->d_type = DT_DIR;
1098       }
1099       if (recursive && de->d_type == DT_DIR)
1100         list (base + '/' + de->d_name, results, recursive);
1101       else
1102         results.push_back (base + '/' + de->d_name);
1103 #endif
1104     }
1105 
1106     closedir (dp);
1107   }
1108 }
1109 
1110 ////////////////////////////////////////////////////////////////////////////////
1111