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