]> git.armaanb.net Git - gen-shell.git/blob - src/libshared/src/FS.cpp
added install instructions
[gen-shell.git] / src / libshared / src / FS.cpp
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 ////////////////////////////////////////////////////////////////////////////////
62 Path::Path ()
63 {
64 }
65
66 ////////////////////////////////////////////////////////////////////////////////
67 Path::Path (const Path& other)
68 {
69   if (this != &other)
70   {
71     _original = other._original;
72     _data     = other._data;
73   }
74 }
75
76 ////////////////////////////////////////////////////////////////////////////////
77 Path::Path (const std::string& in)
78 {
79   _original = in;
80   _data     = expand (in);
81 }
82
83 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
96 bool Path::operator== (const Path& other)
97 {
98   return _data == other._data;
99 }
100
101 ////////////////////////////////////////////////////////////////////////////////
102 Path& Path::operator+= (const std::string& dir)
103 {
104   _data += '/' + dir;
105   return *this;
106 }
107
108 ////////////////////////////////////////////////////////////////////////////////
109 Path::operator std::string () const
110 {
111   return _data;
112 }
113
114 ////////////////////////////////////////////////////////////////////////////////
115 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 ////////////////////////////////////////////////////////////////////////////////
128 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 ////////////////////////////////////////////////////////////////////////////////
141 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 ////////////////////////////////////////////////////////////////////////////////
154 bool Path::exists () const
155 {
156   return access (_data.c_str (), F_OK) ? false : true;
157 }
158
159 ////////////////////////////////////////////////////////////////////////////////
160 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 ////////////////////////////////////////////////////////////////////////////////
175 bool Path::is_absolute () const
176 {
177   if (_data.length () && _data[0] == '/')
178     return true;
179
180   return false;
181 }
182
183 ////////////////////////////////////////////////////////////////////////////////
184 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.
196 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.
208 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.
220 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 ////////////////////////////////////////////////////////////////////////////////
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
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 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
323 File::File ()
324 : Path::Path ()
325 , _fh (nullptr)
326 , _h (-1)
327 , _locked (false)
328 {
329 }
330
331 ////////////////////////////////////////////////////////////////////////////////
332 File::File (const Path& other)
333 : Path::Path (other)
334 , _fh (nullptr)
335 , _h (-1)
336 , _locked (false)
337 {
338 }
339
340 ////////////////////////////////////////////////////////////////////////////////
341 File::File (const File& other)
342 : Path::Path (other)
343 , _fh (nullptr)
344 , _h (-1)
345 , _locked (false)
346 {
347 }
348
349 ////////////////////////////////////////////////////////////////////////////////
350 File::File (const std::string& in)
351 : Path::Path (in)
352 , _fh (nullptr)
353 , _h (-1)
354 , _locked (false)
355 {
356 }
357
358 ////////////////////////////////////////////////////////////////////////////////
359 File::~File ()
360 {
361   if (_fh)
362     close ();
363 }
364
365 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
389 bool File::remove () const
390 {
391   return unlink (_data.c_str ()) == 0 ? true : false;
392 }
393
394 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
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 #else
461                     // l_type   l_whence  l_start  l_len  l_pid
462     struct flock fl = {F_WRLCK, SEEK_SET, 0,       0,     0 };
463 #endif
464     fl.l_pid = getpid ();
465     if (fcntl (_h, F_SETLKW, &fl) == 0)
466       _locked = true;
467   }
468
469   return _locked;
470 }
471
472 ////////////////////////////////////////////////////////////////////////////////
473 void File::unlock ()
474 {
475   if (_locked)
476   {
477 #ifdef FREEBSD
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 };
480 #else
481                     // l_type   l_whence  l_start  l_len  l_pid
482     struct flock fl = {F_WRLCK, SEEK_SET, 0,       0,     0 };
483 #endif
484     fl.l_pid = getpid ();
485
486     fcntl (_h, F_SETLK, &fl);
487     _locked = false;
488   }
489 }
490
491 ////////////////////////////////////////////////////////////////////////////////
492 // Opens if necessary.
493 void File::read (std::string& contents)
494 {
495   contents = "";
496   contents.reserve (size ());
497
498   std::ifstream in (_data.c_str ());
499   if (in.good ())
500   {
501     bool first = true;
502     std::string line;
503     line.reserve (512 * 1024);
504     while (getline (in, line))
505     {
506       // Detect forbidden BOM on first line.
507       if (first)
508       {
509         line = File::removeBOM (line);
510         first = false;
511       }
512
513       contents += line + '\n';
514     }
515
516     in.close ();
517   }
518 }
519
520 ////////////////////////////////////////////////////////////////////////////////
521 // Opens if necessary.
522 void File::read (std::vector <std::string>& contents)
523 {
524   contents.clear ();
525
526   std::ifstream in (_data.c_str ());
527   if (in.good ())
528   {
529     bool first = true;
530     std::string line;
531     line.reserve (512 * 1024);
532     while (getline (in, line))
533     {
534       // Detect forbidden BOM on first line.
535       if (first)
536       {
537         line = File::removeBOM (line);
538         first = false;
539       }
540
541       contents.push_back (line);
542     }
543
544     in.close ();
545   }
546 }
547
548 ////////////////////////////////////////////////////////////////////////////////
549 // Opens if necessary.
550 void File::append (const std::string& line)
551 {
552   if (!_fh)
553     open ();
554
555   if (_fh)
556   {
557     fseek (_fh, 0, SEEK_END);
558
559     if (fputs (line.c_str (), _fh) == EOF)
560       throw format ("fputs error {1}: {2}", errno, strerror (errno));
561   }
562 }
563
564 ////////////////////////////////////////////////////////////////////////////////
565 // Opens if necessary.
566 void File::append (const std::vector <std::string>& lines)
567 {
568   if (!_fh)
569     open ();
570
571   if (_fh)
572   {
573     fseek (_fh, 0, SEEK_END);
574
575     for (auto& line : lines)
576       if (fputs (line.c_str (), _fh) == EOF)
577         throw format ("fputs error {1}: {2}", errno, strerror (errno));
578   }
579 }
580
581 ////////////////////////////////////////////////////////////////////////////////
582 void File::write_raw (const std::string& line)
583 {
584   if (!_fh)
585     open ();
586
587   if (_fh)
588     if (fputs (line.c_str (), _fh) == EOF)
589       throw format ("fputs error {1}: {2}", errno, strerror (errno));
590 }
591
592 ////////////////////////////////////////////////////////////////////////////////
593 void File::truncate ()
594 {
595   if (!_fh)
596     open ();
597
598   if (_fh)
599     if (ftruncate (_h, 0))
600       throw format ("ftruncate error {1}: {2}", errno, strerror (errno));
601 }
602
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
619 mode_t File::mode ()
620 {
621   struct stat s;
622   if (stat (_data.c_str (), &s))
623     throw format ("stat error {1}: {2}", errno, strerror (errno));
624
625   return s.st_mode;
626 }
627
628 ////////////////////////////////////////////////////////////////////////////////
629 size_t File::size () const
630 {
631   struct stat s;
632   if (stat (_data.c_str (), &s))
633     throw format ("stat error {1}: {2}", errno, strerror (errno));
634
635   return s.st_size;
636 }
637
638 ////////////////////////////////////////////////////////////////////////////////
639 time_t File::mtime () const
640 {
641   struct stat s;
642   if (stat (_data.c_str (), &s))
643     throw format ("stat error {1}: {2}", errno, strerror (errno));
644
645   return s.st_mtime;
646 }
647
648 ////////////////////////////////////////////////////////////////////////////////
649 time_t File::ctime () const
650 {
651   struct stat s;
652   if (stat (_data.c_str (), &s))
653     throw format ("stat error {1}: {2}", errno, strerror (errno));
654
655   return s.st_ctime;
656 }
657
658 ////////////////////////////////////////////////////////////////////////////////
659 time_t File::btime () const
660 {
661   struct stat s;
662   if (stat (_data.c_str (), &s))
663     throw format ("stat error {1}: {2}", errno, strerror (errno));
664
665 #ifdef HAVE_ST_BIRTHTIME
666   return s.st_birthtime;
667 #else
668   return s.st_ctime;
669 #endif
670 }
671
672 ////////////////////////////////////////////////////////////////////////////////
673 bool File::create (const std::string& name, int mode /* = 0640 */)
674 {
675   std::string full_name = expand (name);
676   std::ofstream out (full_name.c_str ());
677   if (out.good ())
678   {
679     out.close ();
680     if (chmod (full_name.c_str (), mode))
681       throw format ("chmod error {1}: {2}", errno, strerror (errno));
682
683     return true;
684   }
685
686   return false;
687 }
688
689 ////////////////////////////////////////////////////////////////////////////////
690 bool File::read (const std::string& name, std::string& contents)
691 {
692   contents = "";
693
694   std::ifstream in (name.c_str ());
695   if (in.good ())
696   {
697     bool first = true;
698     std::string line;
699     line.reserve (1024);
700     while (getline (in, line))
701     {
702       // Detect forbidden BOM on first line.
703       if (first)
704       {
705         line = File::removeBOM (line);
706         first = false;
707       }
708
709       contents += line + '\n';
710     }
711
712     in.close ();
713     return true;
714   }
715
716   return false;
717 }
718
719 ////////////////////////////////////////////////////////////////////////////////
720 bool File::read (const std::string& name, std::vector <std::string>& contents)
721 {
722   contents.clear ();
723
724   std::ifstream in (name.c_str ());
725   if (in.good ())
726   {
727     bool first = true;
728     std::string line;
729     line.reserve (1024);
730     while (getline (in, line))
731     {
732       // Detect forbidden BOM on first line.
733       if (first)
734       {
735         line = File::removeBOM (line);
736         first = false;
737       }
738
739       contents.push_back (line);
740     }
741
742     in.close ();
743     return true;
744   }
745
746   return false;
747 }
748
749 ////////////////////////////////////////////////////////////////////////////////
750 bool File::write (const std::string& name, const std::string& contents)
751 {
752   std::ofstream out (expand (name).c_str (),
753                      std::ios_base::out | std::ios_base::trunc);
754   if (out.good ())
755   {
756     out << contents;
757     out.close ();
758     return true;
759   }
760
761   return false;
762 }
763
764 ////////////////////////////////////////////////////////////////////////////////
765 bool File::write (
766   const std::string& name,
767   const std::vector <std::string>& lines,
768   bool addNewlines /* = true */)
769 {
770   std::ofstream out (expand (name).c_str (),
771                      std::ios_base::out | std::ios_base::trunc);
772   if (out.good ())
773   {
774     for (auto& line : lines)
775     {
776       out << line;
777
778       if (addNewlines)
779         out << '\n';
780     }
781
782     out.close ();
783     return true;
784   }
785
786   return false;
787 }
788
789 ////////////////////////////////////////////////////////////////////////////////
790 bool File::remove (const std::string& name)
791 {
792   return unlink (expand (name).c_str ()) == 0 ? true : false;
793 }
794
795 ////////////////////////////////////////////////////////////////////////////////
796 bool File::copy (const std::string& from, const std::string& to)
797 {
798   // 'from' must exist.
799   if (! access (from.c_str (), F_OK))
800   {
801     std::ifstream src (from, std::ios::binary);
802     std::ofstream dst (to,   std::ios::binary);
803
804     dst << src.rdbuf ();
805     return true;
806   }
807
808   return false;
809 }
810
811 ////////////////////////////////////////////////////////////////////////////////
812 bool File::move (const std::string& from, const std::string& to)
813 {
814   auto expanded = expand (to);
815   if (from != expanded)
816     if (std::rename (from.c_str (), to.c_str ()) == 0)
817       return true;
818
819   return false;
820 }
821
822 ////////////////////////////////////////////////////////////////////////////////
823 Directory::Directory ()
824 {
825 }
826
827 ////////////////////////////////////////////////////////////////////////////////
828 Directory::Directory (const Directory& other)
829 : File::File (other)
830 {
831 }
832
833 ////////////////////////////////////////////////////////////////////////////////
834 Directory::Directory (const File& other)
835 : File::File (other)
836 {
837 }
838
839 ////////////////////////////////////////////////////////////////////////////////
840 Directory::Directory (const Path& other)
841 : File::File (other)
842 {
843 }
844
845 ////////////////////////////////////////////////////////////////////////////////
846 Directory::Directory (const std::string& in)
847 : File::File (in)
848 {
849 }
850
851 ////////////////////////////////////////////////////////////////////////////////
852 Directory& Directory::operator= (const Directory& other)
853 {
854   if (this != &other)
855     File::operator= (other);
856
857   return *this;
858 }
859
860 ////////////////////////////////////////////////////////////////////////////////
861 bool Directory::create (int mode /* = 0755 */)
862 {
863   // No error handling because we want failure to be silent, somewhat emulating
864   // "mkdir -p".
865   return mkdir (_data.c_str (), mode) == 0 ? true : false;
866 }
867
868 ////////////////////////////////////////////////////////////////////////////////
869 bool Directory::remove () const
870 {
871   return remove_directory (_data);
872 }
873
874 ////////////////////////////////////////////////////////////////////////////////
875 bool Directory::remove_directory (const std::string& dir) const
876 {
877   DIR* dp = opendir (dir.c_str ());
878   if (dp != nullptr)
879   {
880     struct dirent* de;
881     while ((de = readdir (dp)) != nullptr)
882     {
883       if (! strcmp (de->d_name, ".") ||
884           ! strcmp (de->d_name, ".."))
885         continue;
886
887 #if defined (SOLARIS) || defined (HAIKU)
888       struct stat s;
889       if (lstat ((dir + '/' + de->d_name).c_str (), &s))
890         throw format ("lstat error {1}: {2}", errno, strerror (errno));
891
892       if (S_ISDIR (s.st_mode))
893         remove_directory (dir + '/' + de->d_name);
894       else
895         unlink ((dir + '/' + de->d_name).c_str ());
896 #else
897       if (de->d_type == DT_UNKNOWN)
898       {
899         struct stat s;
900         if (lstat ((dir + '/' + de->d_name).c_str (), &s))
901           throw format ("lstat error {1}: {2}", errno, strerror (errno));
902
903         if (S_ISDIR (s.st_mode))
904           de->d_type = DT_DIR;
905       }
906       if (de->d_type == DT_DIR)
907         remove_directory (dir + '/' + de->d_name);
908       else
909         unlink ((dir + '/' + de->d_name).c_str ());
910 #endif
911     }
912
913     closedir (dp);
914   }
915
916   return rmdir (dir.c_str ()) ? false : true;
917 }
918
919 ////////////////////////////////////////////////////////////////////////////////
920 std::vector <std::string> Directory::list ()
921 {
922   std::vector <std::string> files;
923   list (_data, files, false);
924   return files;
925 }
926
927 ////////////////////////////////////////////////////////////////////////////////
928 std::vector <std::string> Directory::listRecursive ()
929 {
930   std::vector <std::string> files;
931   list (_data, files, true);
932   return files;
933 }
934
935 ////////////////////////////////////////////////////////////////////////////////
936 std::string Directory::cwd ()
937 {
938 #ifdef HAVE_GET_CURRENT_DIR_NAME
939   char *buf = get_current_dir_name ();
940   if (buf == nullptr)
941     throw std::bad_alloc ();
942   std::string result (buf);
943   free (buf);
944   return result;
945 #else
946   char buf[PATH_MAX];
947   getcwd (buf, PATH_MAX - 1);
948   return std::string (buf);
949 #endif
950 }
951
952 ////////////////////////////////////////////////////////////////////////////////
953 bool Directory::up ()
954 {
955   if (_data == "/")
956     return false;
957
958   auto slash = _data.rfind ('/');
959   if (slash == 0)
960   {
961     _data = "/";  // Root dir should retain the slash.
962     return true;
963   }
964   else if (slash != std::string::npos)
965   {
966     _data = _data.substr (0, slash);
967     return true;
968   }
969
970   return false;
971 }
972
973 ////////////////////////////////////////////////////////////////////////////////
974 bool Directory::cd () const
975 {
976   return chdir (_data.c_str ()) == 0 ? true : false;
977 }
978
979 ////////////////////////////////////////////////////////////////////////////////
980 void Directory::list (
981   const std::string& base,
982   std::vector <std::string>& results,
983   bool recursive)
984 {
985   DIR* dp = opendir (base.c_str ());
986   if (dp != nullptr)
987   {
988     struct dirent* de;
989     while ((de = readdir (dp)) != nullptr)
990     {
991       if (!strcmp (de->d_name, ".") ||
992           !strcmp (de->d_name, ".."))
993         continue;
994
995 #if defined (SOLARIS) || defined (HAIKU)
996       struct stat s;
997       if (stat ((base + '/' + de->d_name).c_str (), &s))
998         throw format ("stat error {1}: {2}", errno, strerror (errno));
999
1000       if (recursive && S_ISDIR (s.st_mode))
1001         list (base + '/' + de->d_name, results, recursive);
1002       else
1003         results.push_back (base + '/' + de->d_name);
1004 #else
1005       if (recursive && de->d_type == DT_UNKNOWN)
1006       {
1007         struct stat s;
1008         if (lstat ((base + '/' + de->d_name).c_str (), &s))
1009           throw format ("lstat error {1}: {2}", errno, strerror (errno));
1010
1011         if (S_ISDIR (s.st_mode))
1012           de->d_type = DT_DIR;
1013       }
1014       if (recursive && de->d_type == DT_DIR)
1015         list (base + '/' + de->d_name, results, recursive);
1016       else
1017         results.push_back (base + '/' + de->d_name);
1018 #endif
1019     }
1020
1021     closedir (dp);
1022   }
1023 }
1024
1025 ////////////////////////////////////////////////////////////////////////////////