1 /**************************************************************************
2 Copyright:
3 (C) 2008 - 2013 Alexander Shaduri <ashaduri 'at' gmail.com>
4 License: See LICENSE_zlib.txt file
5 ***************************************************************************/
6 /// \file
7 /// \author Alexander Shaduri
8 /// \ingroup hz
9 /// \weakgroup hz
10 /// @{
11
12 #ifndef HZ_FS_PATH_H
13 #define HZ_FS_PATH_H
14
15 #include "hz_config.h" // feature macros
16
17 #include <string>
18 #include <cerrno> // errno (not std::errno, it may be a macro)
19 #include <cstdio> // for stdio.h; std::FILE, std::fclose, std::remove.
20 #include <stdio.h> // _wfopen*
21 #include <ctime> // std::time_t
22 #include <sys/types.h> // *stat() needs this; utime.h needs this; mode_t
23 #include <sys/stat.h> // *stat() needs this; mkdir()
24
25 #ifdef _WIN32
26 #include <io.h> // _waccess*(), _wstat(), _wunlink(), _wrmdir(), _wmkdir()
27 #include <sys/utime.h> // _wutime()
28 #else
29 #include <cstddef> // std::size_t
30 #include <unistd.h> // access(), stat(), unlink(), readlink()
31 #include <utime.h> // utime()
32 #endif
33
34 #include "fs_common.h" // separator
35 #include "fs_path_utils.h" // path_* functions
36 #include "fs_error_holder.h" // FsErrorHolder
37 #include "fs_dir_platform.h" // directory_* functions
38 #include "win32_tools.h" // hz::win32_* charset conversion
39
40
41 /**
42 \file
43 Filesystem path and file manipulation.
44
45 This API accepts/gives utf-8 filenames/paths on win32,
46 current locale filenames/paths on others (just like glib).
47 */
48
49
50 namespace hz {
51
52
53
54
55 /// The sole purpose of this class is to enforce privacy of its members.
56 /// You can not construct an object of this class.
57 class FsPathHolder {
58 protected: // construct only from children
59
60 /// Constructor
FsPathHolder()61 FsPathHolder()
62 #ifdef _WIN32
63 : utf16_path_(0)
64 #endif
65 { }
66
67 /// Constructor.
FsPathHolder(const std::string & path)68 FsPathHolder(const std::string& path) : path_(path)
69 #ifdef _WIN32
70 , utf16_path_(0)
71 #endif
72 { }
73
74
75 public:
76
77 /// Virtual destructor
~FsPathHolder()78 virtual ~FsPathHolder()
79 {
80 #ifdef _WIN32
81 delete[] utf16_path_;
82 #endif
83 }
84
85
86 // --- these will _not_ set bad() status
87
88 /// Set current path.
set_path(const std::string & path)89 void set_path(const std::string& path)
90 {
91 path_ = path;
92 #ifdef _WIN32
93 delete[] utf16_path_;
94 utf16_path_ = 0; // reset
95 #endif
96 }
97
98 /// Get current path
get_path()99 std::string get_path() const
100 {
101 return path_;
102 }
103
104 /// Same as get_path()
str()105 std::string str() const
106 {
107 return path_;
108 }
109
110 /// Same as get_path().c_str()
c_str()111 const char* c_str() const
112 {
113 return path_.c_str();
114 }
115
116 /// Check if current path string is empty
empty()117 bool empty() const
118 {
119 return path_.empty();
120 }
121
122
123 // These are sort-of internal
124 #ifdef _WIN32
125 /// Get path in UTF-16 format and store it (convert it from UTF-8)
126 /// Note: This may actually return 0 if it's unsupported or the encoded string is invalid.
get_utf16()127 const wchar_t* get_utf16() const
128 {
129 if (!utf16_path_)
130 utf16_path_ = hz::win32_utf8_to_utf16(path_.c_str());
131 return utf16_path_;
132 }
133
134 /// Set path in UTF-16 format. Also convert it to UTF-8 and store it.
set_utf16(const wchar_t * path)135 bool set_utf16(const wchar_t* path)
136 {
137 char* utf8 = win32_utf16_to_utf8(path);
138 if (utf8) {
139 set_path(utf8); // this correctly resets utf16_path_
140 delete[] utf8;
141 return true;
142 }
143 return false;
144 }
145 #endif
146
147
148 private: // don't let children modify path_ directly, it will desync utf16_path_.
149
150 std::string path_; ///< Current path. Always UTF-8 in windows.
151
152 #ifdef _WIN32
153 mutable wchar_t* utf16_path_; ///< Same as path_, but in UTF-16.
154 #endif
155
156 };
157
158
159
160
161 /// A class representing a filesystem path.
162 class FsPath : public FsPathHolder, public FsErrorHolder {
163 public:
164
165 /// \typedef mode_type
166 /// mkdir() mode type.
167
168 #ifdef _WIN32
169 typedef int mode_type;
170 #else
171 typedef mode_t mode_type;
172 #endif
173
174 /// Constructor
FsPath()175 FsPath()
176 { }
177
178 /// Constructor, sets current path
FsPath(const std::string & path)179 FsPath(const std::string& path) : FsPathHolder(path)
180 { }
181
182 /// Destructor
~FsPath()183 virtual ~FsPath()
184 { }
185
186
187
188 // --- these will _not_ set bad() status
189
190
191 /// Convert path from unknown format to native (e.g. unix paths to win32).
192 /// The current object is also modified.
193 inline FsPath& to_native();
194
195 /// Remove trailing separators in path (unless they are parts of the root component).
196 /// The current object is also modified.
197 inline FsPath& trim_trailing();
198
199 /// Go up \c steps steps. The current object is also modified.
200 inline FsPath& go_up(unsigned int steps = 1);
201
202 /// Append a partial (e.g. relative) path. It doesn't matter if it starts with a
203 /// separator. The current object is also modified.
204 inline FsPath& append(const std::string& partial_path);
205
206 /// Compress a path - remove double separators, trailing
207 /// separator, "/./" components, and deal with "/../" if possible.
208 /// Note: This function performs its operations on strings, not real paths.
209 /// The current object is also modified.
210 inline FsPath& compress();
211
212 /// Make the path absolute (if it's not already) by prepending \c base_path.
213 inline FsPath& make_absolute(const std::string& base_path);
214
215
216 /// Get the path truncated by 1 level, e.g. /usr/local/ -> /usr.
217 inline std::string get_dirname() const;
218
219 /// Get the basename of path, e.g. /usr/local/ -> local; /a/b/c -> c.
220 inline std::string get_basename() const;
221
222 /// Get root path of current path. e.g. '/' or 'D:\'.
223 /// May not work with relative paths under win32.
224 inline std::string get_root() const;
225
226 /// Check if the path corresponds to root (drive / share in win32).
227 inline bool is_root() const;
228
229 /// Get an extension of the last component, e.g. /local/archive.tar.gz -> gz
230 inline std::string get_extension() const;
231
232 /// Get a full extension of the last component, e.g. /local/archive.tar.gz -> tar.gz
233 inline std::string get_full_extension() const;
234
235 /// Get the last component without extension, e.g. /local/archive.tar.gz -> archive.tar
236 inline std::string get_noext_basename() const;
237
238 /// Get the last component without extension, e.g. /local/archive.tar.gz -> archive
239 inline std::string get_noext_min_basename() const;
240
241 /// Check if the path is absolute (only for native paths). returns 0 if it's not.
242 /// the returned value is a position past the root component (e.g. 3 for C:\\temp).
243 inline std::string::size_type is_absolute() const;
244
245 /// Check if the current path is a subpath of supplied argument.
246 /// e.g. /usr/local/bin is a subpath of /usr. Note: This doesn't check real paths, only strings.
247 inline bool is_subpath_of(const std::string& superpath) const;
248
249
250
251 // --- these may set bad() status
252
253
254 /// Check if the existing file can be fopen'ed with "rb", or the directory has read perms.
255 /// Note: This function should be use only as an utility function (e.g. for GUI notification);
256 /// other uses are not logically concurrent-safe (and therefore, insecure).
257 /// bad() status is set on failure.
258 inline bool is_readable();
259
260 /// Check if the existing or soon to be created file is writable, or if files can be created in this dir.
261 /// Note: The same security considerations apply to this function as to is_readable().
262 /// bad() status is set on failure.
263 inline bool is_writable();
264
265
266 /// Check if anything exists at this path.
267 /// Note: The same security considerations apply to this function as to is_readable().
268 /// bad() status is set on failure.
269 inline bool exists();
270
271 /// Check if it's a file (any type, including block, etc..,). Will also match for symlinks to files.
272 /// Note: The same security considerations apply to this function as to is_readable().
273 /// bad() status is set on error.
274 inline bool is_file();
275
276 /// Check if it's a regular file. Will also match for symlinks to files.
277 /// Note: The same security considerations apply to this function as to is_readable().
278 /// bad() status is set on failure.
279 inline bool is_regular();
280
281 /// Check if it's a directory.
282 /// Note: The same security considerations apply to this function as to is_readable().
283 /// bad() status is set on error.
284 inline bool is_dir();
285
286 /// Check if it's a symlink.
287 /// Note: The same security considerations apply to this function as to is_readable().
288 /// bad() status is set on failure.
289 inline bool is_symlink();
290
291 /// If current path is a symbolic link, put its destination into \c dest.
292 /// Note: You should avoid calling this in loop, you may end up with an
293 /// infinite loop if the links point to each other.
294 /// Note: The returned path may be relative to the link's directory.
295 /// If error occurs, false is returned and bad() status is set.
296 /// If current path is not a symbolic link, false is returned, bad() is _not_ set.
297 /// If false is returned, dest is untouched.
298 inline bool get_link_destination(std::string& dest);
299
300 /// Get "last modified time" property.
301 /// Do NOT assign the result to int - std::time_t is implementation-defined.
302 /// Usually it's seconds since epoch, see time(2) for details.
303 /// bad() status is set on failure and false is returned.
304 inline bool get_last_modified(std::time_t& put_here);
305
306 /// Set the last modified time. It will also change the last access
307 /// time as a side effect.
308 /// bad() status is set on failure and false is returned.
309 inline bool set_last_modified(std::time_t t);
310
311
312 /// Create a directory (assuming that the parent directory already exists).
313 /// octal_mode parameter is ignored on Windows.
314 /// Note: If creating with parents, when failing to create one of the directories,
315 /// it won't remove the previously created ones.
316 /// Note: If creating with parents, the supplied path _must_ be absolute.
317 /// bad() status is set on failure and false is returned.
318 inline bool make_dir(mode_type octal_mode, bool with_parents);
319
320
321 /// Remove a file or directory.
322 /// bad() status is set on failure and false is returned.
323 inline bool remove(bool recursive = false);
324
325
326 };
327
328
329
330
331
332 // ------------------------------------------- Implementation
333
334
335
to_native()336 inline FsPath& FsPath::to_native()
337 {
338 this->set_path(path_to_native(this->get_path()));
339 return *this;
340 }
341
342
343
trim_trailing()344 inline FsPath& FsPath::trim_trailing()
345 {
346 this->set_path(path_trim_trailing_separators(this->get_path()));
347 return *this;
348 }
349
350
351
go_up(unsigned int steps)352 inline FsPath& FsPath::go_up(unsigned int steps)
353 {
354 std::string p = this->get_path();
355 while (steps--)
356 p = get_dirname();
357 this->set_path(p);
358 return *this;
359 }
360
361
362
append(const std::string & partial_path)363 inline FsPath& FsPath::append(const std::string& partial_path)
364 {
365 std::string p = this->get_path();
366 // trim leading separators of partial_path
367 std::string::size_type index = partial_path.find_first_not_of(DIR_SEPARATOR);
368 if (index != std::string::npos) { // if no non-separator characters found, do nothing.
369 trim_trailing();
370 if (!is_root())
371 p += DIR_SEPARATOR_S;
372 p += partial_path.substr(index);
373 }
374 this->set_path(p);
375 return *this;
376 }
377
378
379
compress()380 inline FsPath& FsPath::compress()
381 {
382 this->set_path(path_compress(this->get_path()));
383 return *this;
384 }
385
386
387
make_absolute(const std::string & base_path)388 FsPath& FsPath::make_absolute(const std::string& base_path)
389 {
390 if (!is_absolute()) {
391 set_path(FsPath(base_path).append(get_path()).get_path());
392 }
393 return compress();
394 }
395
396
397
get_dirname()398 inline std::string FsPath::get_dirname() const
399 {
400 return path_get_dirname(this->get_path());
401 }
402
403
404
get_basename()405 inline std::string FsPath::get_basename() const
406 {
407 return path_get_basename(this->get_path());
408 }
409
410
411
get_root()412 inline std::string FsPath::get_root() const
413 {
414 return path_get_root(this->get_path());
415 }
416
417
418
is_root()419 inline bool FsPath::is_root() const
420 {
421 std::string p = path_trim_trailing_separators(this->get_path());
422 return (path_is_absolute(p) == p.size());
423 }
424
425
426
get_extension()427 inline std::string FsPath::get_extension() const
428 {
429 std::string base = get_basename();
430 std::string::size_type pos = base.rfind('.');
431 if (pos != std::string::npos)
432 return base.substr(pos + 1);
433 return std::string();
434 }
435
436
437
get_full_extension()438 inline std::string FsPath::get_full_extension() const
439 {
440 std::string base = get_basename();
441 std::string::size_type pos = base.find('.');
442 if (pos != std::string::npos)
443 return base.substr(pos + 1);
444 return std::string();
445 }
446
447
448
get_noext_basename()449 std::string FsPath::get_noext_basename() const
450 {
451 std::string base = get_basename();
452 std::string::size_type pos = base.rfind('.');
453 return base.substr(0, pos);
454 }
455
456
457
get_noext_min_basename()458 std::string FsPath::get_noext_min_basename() const
459 {
460 std::string base = get_basename();
461 std::string::size_type pos = base.find('.');
462 return base.substr(0, pos);
463 }
464
465
466
is_absolute()467 inline std::string::size_type FsPath::is_absolute() const
468 {
469 return path_is_absolute(this->get_path());
470 }
471
472
473
is_subpath_of(const std::string & superpath)474 inline bool FsPath::is_subpath_of(const std::string& superpath) const
475 {
476 return (this->get_path().compare(0, superpath.length(), superpath) == 0);
477 }
478
479
480
is_readable()481 inline bool FsPath::is_readable()
482 {
483 clear_error();
484
485 if (this->empty()) {
486 set_error(std::string(HZ__("Unable to check if a file or directory is readable: "))
487 + HZ__("Supplied path is empty."));
488 return false;
489 }
490
491 #if defined HAVE_WIN_SE_FUNCS && HAVE_WIN_SE_FUNCS
492 if (_waccess_s(this->get_utf16(), 04)) // msvc uses integers instead (R_OK == 04 anyway).
493 #elif defined _WIN32
494 if (_waccess(this->get_utf16(), 04) == -1) // *access*() may not work with < win2k with directories.
495 #else
496 if (access(this->c_str(), R_OK) == -1) // from unistd.h
497 #endif
498 {
499 set_error(HZ__("File or directory \"/path1/\" is not readable: /errno/."), errno, this->get_path());
500 }
501
502 return ok();
503 }
504
505
506
is_writable()507 inline bool FsPath::is_writable()
508 {
509 clear_error();
510 if (this->empty()) {
511 set_error(std::string(HZ__("Unable to check if a file or directory is writable: "))
512 + HZ__("Supplied path is empty."));
513 return false;
514 }
515
516 bool is_directory = is_dir();
517 bool path_exists = exists();
518 std::string dirname = (is_directory ? path_trim_trailing_separators(this->get_path()) : get_dirname());
519
520 clear_error();
521
522 #ifdef _WIN32 // win32 doesn't get access() (it just doesn't work with writing)
523
524 // If it doesn't exist, try to create it.
525 // If it exists and is a file, try to open it for writing.
526 // If it exists and is a directory, try to create a test file in it.
527 // Note: This method is possibly non-suitable for symlink-capable filesystems.
528
529 FsPath path_to_check(this->get_path());
530 path_to_check.trim_trailing();
531 if (path_exists && is_directory) {
532 path_to_check.set_path(path_to_check.get_path() += std::string(DIR_SEPARATOR_S) + "__test.txt");
533 path_exists = path_to_check.exists();
534 }
535
536 // pcheck either doesn't exist, or it's a file. try to open it.
537 std::FILE* f = 0;
538 #if defined HAVE_WIN_SE_FUNCS && HAVE_WIN_SE_FUNCS
539 errno = _wfopen_s(&f, path_to_check.get_utf16(), L"ab");
540 #else
541 f = _wfopen(path_to_check.get_utf16(), L"ab"); // this creates a 0 size file if it doesn't exist!
542 #endif
543 if (!f) {
544 set_error(HZ__("File or directory \"/path1/\" is not writable: /errno/."), errno, this->get_path());
545 return false;
546 }
547
548 if (std::fclose(f) != 0) {
549 set_error(std::string(HZ__("Unable to check if a file or directory \"/path1/\" is writable: "))
550 + HZ__("Error while closing file: /errno/."), errno, this->get_path());
551 return false;
552 }
553
554 // remove the created file
555 if (path_exists && _wunlink(path_to_check.get_utf16()) == -1) {
556 set_error(std::string(HZ__("Unable to check if a file or directory \"/path1/\" is writable: "))
557 + HZ__("Error while removing file: /errno/."), errno, this->get_path());
558 return false;
559 }
560
561 // All OK
562
563 #else
564
565 if (path_exists && is_directory) {
566 if (access(dirname.c_str(), W_OK) == -1) {
567 set_error(HZ__("File or directory \"/path1/\" is not writable: /errno/."), errno, this->get_path());
568 }
569
570 } else { // no such path or it's a file
571 if (path_exists) { // checking an existing file
572 if (access(this->c_str(), W_OK) == -1)
573 set_error(HZ__("File or directory \"/path1/\" is not writable: /errno/."), errno, this->get_path());
574
575 } else { // no such path, check parent dir's access mode
576 if (access(dirname.c_str(), W_OK) == -1)
577 set_error(HZ__("File or directory \"/path1/\" is not writable: /errno/."), errno, this->get_path());
578 }
579 }
580
581 #endif
582
583 return ok();
584 }
585
586
587
exists()588 inline bool FsPath::exists()
589 {
590 clear_error();
591 if (this->empty()) {
592 set_error(std::string(HZ__("Unable to check if a file or directory exists: "))
593 + HZ__("Supplied path is empty."));
594 return false;
595 }
596
597 #if defined HAVE_WIN_SE_FUNCS && HAVE_WIN_SE_FUNCS
598 if (_waccess_s(this->get_utf16(), 00) != 0) // msvc uses integers instead (F_OK == 00 anyway).
599 #elif defined _WIN32
600 if (_waccess(this->get_utf16(), 00) != 0) // msvc uses integers instead (F_OK == 00 anyway).
601 #else
602 if (access(this->c_str(), F_OK) == -1)
603 #endif
604 {
605 if (errno != ENOENT) // ENOENT (No such file or directory) shouldn't be reported as error.
606 set_error(HZ__("File or directory \"/path1/\" doesn't exist: /errno/."), errno, this->get_path());
607 return false;
608 }
609 return ok();
610 }
611
612
613
is_file()614 inline bool FsPath::is_file()
615 {
616 clear_error();
617 if (this->empty()) {
618 set_error(std::string(HZ__("Unable to check if a path points to a file: "))
619 + HZ__("Supplied path is empty."));
620 return false;
621 }
622
623 #ifdef _WIN32
624 struct _stat s;
625 const int stat_result = _wstat(this->get_utf16(), &s);
626 #else
627 struct stat s;
628 const int stat_result = stat(this->c_str(), &s);
629 #endif
630 if (stat_result == -1) {
631 set_error(HZ__("Unable to check if a path \"/path1/\" points to a file: /errno/."), errno, this->get_path());
632 return false;
633 }
634
635 #ifdef _WIN32
636 if (s.st_mode & _S_IFDIR)
637 #else
638 if (S_ISDIR(s.st_mode)) // we check for dir. anything else is a file.
639 #endif
640 {
641 // set_error("The path \"" + path_ + "\" points to directory.");
642 return false;
643 }
644
645 return true;
646 }
647
648
649
is_regular()650 inline bool FsPath::is_regular()
651 {
652 clear_error();
653 if (this->empty()) {
654 set_error(std::string(HZ__("Unable to check if a path points to a regular file: "))
655 + HZ__("Supplied path is empty."));
656 return false;
657 }
658
659 #ifdef _WIN32
660 struct _stat s;
661 const int stat_result = _wstat(this->get_utf16(), &s);
662 #else
663 struct stat s;
664 const int stat_result = stat(this->c_str(), &s);
665 #endif
666 if (stat_result == -1) {
667 set_error(HZ__("Unable to check if a path \"/path1/\" points to a regular file: /errno/."), errno, this->get_path());
668 return false;
669 }
670
671 #ifdef _WIN32
672 // MS documentation contradicts itself about when _S_IFREG is set
673 // (see _fstat() and _stat()). _stat() says it's a regular file or a char device,
674 // _fstat() says it's a regular file and char device is represented by _S_IFCHR.
675 // We check both, just in case.
676 if (!(s.st_mode & _S_IFREG) || (s.st_mode & _S_IFCHR)) // if it's not a regular file or it's a char device.
677 #else
678 if (!S_ISREG(s.st_mode)) // we check for dir. anything else is a file.
679 #endif
680 {
681 // set_error("The path \"" + path_ + "\" points to a non-regular file.");
682 return false;
683 }
684
685 return true;
686 }
687
688
689
is_dir()690 inline bool FsPath::is_dir()
691 {
692 clear_error();
693 if (this->empty()) {
694 set_error(std::string(HZ__("Unable to check if a path points to directory: "))
695 + HZ__("Supplied path is empty."));
696 return false;
697 }
698
699 #ifdef _WIN32
700 struct _stat s;
701 const int stat_result = _wstat(this->get_utf16(), &s);
702 #else
703 struct stat s;
704 const int stat_result = stat(this->c_str(), &s);
705 #endif
706 if (stat_result == -1) {
707 set_error(HZ__("Unable to check if a path \"/path1/\" points to directory: /errno/."), errno, this->get_path());
708 return false;
709 }
710
711 #ifdef _WIN32
712 if (!(s.st_mode & _S_IFDIR))
713 #else
714 if (!S_ISDIR(s.st_mode))
715 #endif
716 {
717 // set_error("The path \"" + path_ + "\" points to a file.");
718 return false;
719 }
720
721 return true;
722 }
723
724
725
is_symlink()726 inline bool FsPath::is_symlink()
727 {
728 clear_error();
729 if (this->empty()) {
730 set_error(std::string(HZ__("Unable to check if a path points to a symbolic link: "))
731 + HZ__("Supplied path is empty."));
732 return false;
733 }
734
735 #ifdef _WIN32
736 // well, win32 and all...
737 // Although there are symlinks there (theoretically), we don't want to get
738 // our hands dirty (glib doesn't).
739 return false;
740
741 #else
742 struct stat s;
743 if (lstat(this->c_str(), &s) == -1) {
744 set_error(HZ__("Unable to check if a path \"/path1/\" points to a symbolic link: /errno/."), errno, this->get_path());
745 return false;
746 }
747
748 if (!S_ISLNK(s.st_mode)) {
749 return false;
750 }
751
752 return true;
753 #endif
754 }
755
756
757
get_link_destination(std::string & dest)758 inline bool FsPath::get_link_destination(std::string& dest)
759 {
760 clear_error();
761 if (this->empty()) {
762 set_error(std::string(HZ__("Unable to get link destination: ")) + HZ__("Supplied path is empty."));
763 return false;
764 }
765
766 #ifdef _WIN32
767 return false; // not a link (see is_symlink())
768
769 #else
770
771 std::size_t buf_size = 256; // size_t, as accepted by readlink().
772
773 do {
774 char* buf = new char[buf_size];
775
776 // readlink() returns the number of written bytes, not including terminating 0.
777 ssize_t written = readlink(this->c_str(), buf, buf_size);
778
779 if (written == -1) { // error
780 if (errno != EINVAL) { // EINVAL: The named file is not a symbolic link.
781 set_error(HZ__("Unable to get link destination of path \"/path1/\": /errno/."), errno, this->get_path());
782 }
783 delete[] buf;
784 return false;
785 }
786
787 if (written != static_cast<ssize_t>(buf_size)) { // means we have enough room
788 buf[written] = '\0'; // there's a place for this.
789 dest = buf;
790 delete[] buf;
791 break;
792
793 } else { // doesn't fit, increase size.
794 buf_size *= 4;
795 }
796
797 delete[] buf;
798
799 } while (true);
800
801 return true;
802 #endif
803 }
804
805
806
get_last_modified(std::time_t & put_here)807 inline bool FsPath::get_last_modified(std::time_t& put_here)
808 {
809 clear_error();
810 if (this->empty()) {
811 set_error(std::string(HZ__("Unable to get the last modification time of a path: "))
812 + HZ__("Supplied path is empty."));
813 return false;
814 }
815
816 #ifdef _WIN32
817 struct _stat s;
818 const int stat_result = _wstat(this->get_utf16(), &s);
819 #else
820 struct stat s;
821 const int stat_result = stat(this->c_str(), &s);
822 #endif
823 if (stat_result == -1) {
824 set_error(HZ__("Unable to get the last modification time of path \"/path1/\": /errno/."), errno, this->get_path());
825 return false;
826 }
827
828 put_here = s.st_mtime;
829
830 return ok();
831 }
832
833
834
set_last_modified(std::time_t t)835 inline bool FsPath::set_last_modified(std::time_t t)
836 {
837 clear_error();
838 if (this->empty()) {
839 set_error(std::string(HZ__("Unable to set the last modification time of a filesystem entry: "))
840 + HZ__("Supplied path is empty."));
841 return false;
842 }
843
844 #ifdef _WIN32
845 struct _utimbuf tb;
846 #else
847 struct utimbuf tb;
848 #endif
849 tb.actime = t; // this is a side effect - change access time too
850 tb.modtime = t;
851
852 #ifdef _WIN32
853 if (_wutime(this->get_utf16(), &tb) == -1)
854 #else
855 if (utime(this->c_str(), &tb) == -1)
856 #endif
857 {
858 set_error(HZ__("Unable to set the last modification time of path \"/path1/\": /errno/."), errno, this->get_path());
859 return false;
860 }
861
862 return ok();
863 }
864
865
866
make_dir(mode_type octal_mode,bool with_parents)867 inline bool FsPath::make_dir(mode_type octal_mode, bool with_parents)
868 {
869 // debug_out_dump("hz", DBG_FUNC_MSG << "Path: \"" << path_ << "\"\n");
870 clear_error();
871 if (this->empty()) {
872 set_error(std::string(HZ__("Unable to create directory: ")) + HZ__("Supplied path is empty."));
873 return false;
874 }
875
876 if (is_root())
877 return true;
878
879 if (with_parents) {
880 if (!is_absolute()) {
881 set_error(std::string(HZ__("Unable to create directory with parents at path \"/path1/\": "))
882 + HZ__("Supplied path must be absolute."), 0, this->get_path());
883 return false;
884 }
885
886 FsPath p(get_dirname());
887 if (!p.make_dir(octal_mode, with_parents)) {
888 import_error(p);
889 return false;
890 }
891 }
892
893 #ifdef _WIN32
894 int status = _wmkdir(this->get_utf16());
895 #else
896 int status = mkdir(this->c_str(), octal_mode);
897 #endif
898 if (status == -1) {
899 if (errno == EEXIST && is_dir()) {
900 return true; // already exists
901 }
902 set_error(HZ__("Unable to create directory at path \"/path1/\": /errno/."), errno, this->get_path());
903 return false;
904 }
905
906 return ok();
907 }
908
909
910
911
912 namespace internal {
913
914
915 /// Helper function, internal.
916 /// Remove Remove directory recursively.
917 /// \return the number of not removed files. 0 on success. pass directory only.
path_remove_dir_recursive(const std::string & path)918 inline int path_remove_dir_recursive(const std::string& path)
919 {
920 hz::FsPath p(path);
921 if (!p.exists())
922 return 1; // couldn't remove 1 file. maybe doesn't exist - it's still an error.
923
924 // if (! p.is_file().bad()) { // file
925 // #ifdef _WIN32
926 // return static_cast<int>(_wunlink(p.get_utf16()) == -1);
927 // #else
928 // return static_cast<int>(unlink(p.c_str()) == -1);
929 // #endif
930 // }
931
932 int error_count = 0;
933
934 // It's a directory, iterate it, then remove it
935 directory_handle_type dir = directory_open(path.c_str());
936
937 if (dir) {
938 while (true) {
939 errno = 0;
940 directory_entry_handle_type entry = directory_read(dir);
941
942 if (errno) { // error while reading entry. try next one.
943 error_count++;
944 continue;
945 }
946 if (!entry) { // no error and entry is null - means we reached the end.
947 break;
948 }
949
950 std::string entry_name = directory_entry_name(entry);
951 if (entry_name == "." || entry_name == "..") // won't delete those
952 continue;
953
954 std::string entry_path = path + DIR_SEPARATOR_S + directory_entry_name(entry);
955 FsPath ep(entry_path);
956
957 if (ep.is_dir() && !ep.is_symlink()) { // if it's a directory and not a symlink, go recursive
958 error_count += path_remove_dir_recursive(entry_path);
959
960 } else { // just remove it
961 #ifdef _WIN32
962 if (_wunlink(ep.get_utf16()) == -1)
963 #else
964 if (unlink(ep.c_str()) == -1)
965 #endif
966 {
967 error_count++;
968 }
969 }
970 }
971
972 directory_close(dir);
973 }
974
975 // try to remove dir even if it's non-readable.
976 #ifdef _WIN32
977 const int rmdir_result = _wrmdir(p.get_utf16());
978 #else
979 const int rmdir_result = rmdir(p.c_str());
980 #endif
981 if (rmdir_result == -1)
982 return ++error_count;
983
984 return error_count;
985 }
986
987
988 } // ns
989
990
991
992
remove(bool recursive)993 inline bool FsPath::remove(bool recursive)
994 {
995 clear_error();
996 if (this->empty()) {
997 set_error(std::string(HZ__("Unable to remove file or directory: ")) + HZ__("Supplied path is empty."));
998 return false;
999 }
1000
1001 if (path_trim_trailing_separators(this->get_path()) == get_root()) {
1002 set_error(std::string(HZ__("Unable to remove file or directory \"/path1/\": "))
1003 + HZ__("Cannot remove root directory."), 0, this->get_path());
1004 return false;
1005 }
1006
1007 if (recursive && !is_file()) {
1008 clear_error(); // clear previous function call
1009 if (internal::path_remove_dir_recursive(this->get_path()) > 0) { // supply dirs here only.
1010 set_error(HZ__("Unable to remove directory \"/path1/\" completely: Some files couldn't be deleted."), 0, this->get_path());
1011 }
1012 return false;
1013 }
1014
1015 #ifdef _WIN32 // win2k (maybe later wins too) remove() says "permission denied" (!) on directories.
1016 int status = 0;
1017 if (is_dir()) {
1018 status = _wrmdir(this->get_utf16()); // empty dir only
1019
1020 } else {
1021 status = _wunlink(this->get_utf16()); // files only
1022 }
1023
1024 if (status == -1) {
1025 set_error(HZ__("Unable to remove file or directory \"/path1/\": /errno/."), errno, this->get_path());
1026 }
1027
1028 #else
1029 if (std::remove(this->c_str()) == -1) {
1030
1031 // In case of not empty directory, POSIX says the error will be EEXIST
1032 // or ENOTEMPTY. Linux uses ENOTEMPTY, Solaris uses EEXIST.
1033 // ENOTEMPTY makes more sense for error messages, so convert.
1034 if (errno == EEXIST)
1035 errno = ENOTEMPTY;
1036
1037 set_error(HZ__("Unable to remove file or directory \"/path1/\": /errno/."), errno, this->get_path());
1038 }
1039 #endif
1040
1041 return ok();
1042 }
1043
1044
1045
1046
1047
1048 } // ns hz
1049
1050
1051
1052 #endif
1053
1054 /// @}
1055