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