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