Mercurial > irccd
changeset 82:5779d53ea67b
Irccd: merge directory/filesystem with libfs, #451
- Irccd.Directory constructor now fails if the given path is not a directory,
no breaking changes as the constructor could already throw before,
- Irccd.Directory.find now returns undefined if not found and not recursive,
- More checks to avoid using functions on non Directory objects.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 31 Mar 2016 13:27:05 +0200 |
parents | 969f30acfb0e |
children | f1a3026027f1 |
files | doc/html/api/module/Irccd.Directory/function/find.md doc/html/api/module/Irccd.Directory/method/find.md lib/irccd/CMakeSources.cmake lib/irccd/directory.cpp lib/irccd/directory.h lib/irccd/filesystem.cpp lib/irccd/filesystem.h lib/irccd/fs.cpp lib/irccd/fs.h lib/irccd/irccd.cpp lib/irccd/irccdctl.cpp lib/irccd/js-directory.cpp lib/irccd/js-file.cpp lib/irccd/path.cpp lib/irccd/plugin.cpp lib/irccd/system.cpp |
diffstat | 16 files changed, 768 insertions(+), 622 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/html/api/module/Irccd.Directory/function/find.md Thu Mar 31 12:39:08 2016 +0200 +++ b/doc/html/api/module/Irccd.Directory/function/find.md Thu Mar 31 13:27:05 2016 +0200 @@ -6,5 +6,7 @@ - "**path**: the base path," - "**pattern**: the regular expression or file name," - "**recursive**: set to true to search recursively (Optional, default: false)." -returns: "The path to the file or undefined on errors or not found." +returns: "The path to the file or undefined if not found." +throws: + - "Any exception on error." ---
--- a/doc/html/api/module/Irccd.Directory/method/find.md Thu Mar 31 12:39:08 2016 +0200 +++ b/doc/html/api/module/Irccd.Directory/method/find.md Thu Mar 31 13:27:05 2016 +0200 @@ -5,5 +5,7 @@ arguments: - "**pattern**: the regular expression or file name," - "**recursive**: set to true to search recursively (Optional, default: false)." -returns: "The path to the file or undefined on errors or not found" +returns: "The path to the file or undefined if not found." +throws: + - "Any exception on error." ---
--- a/lib/irccd/CMakeSources.cmake Thu Mar 31 12:39:08 2016 +0200 +++ b/lib/irccd/CMakeSources.cmake Thu Mar 31 13:27:05 2016 +0200 @@ -32,9 +32,8 @@ ${CMAKE_CURRENT_LIST_DIR}/cmd-watch.h ${CMAKE_CURRENT_LIST_DIR}/command.h ${CMAKE_CURRENT_LIST_DIR}/config.h - ${CMAKE_CURRENT_LIST_DIR}/directory.h ${CMAKE_CURRENT_LIST_DIR}/elapsed-timer.h - ${CMAKE_CURRENT_LIST_DIR}/filesystem.h + ${CMAKE_CURRENT_LIST_DIR}/fs.h ${CMAKE_CURRENT_LIST_DIR}/ini.h ${CMAKE_CURRENT_LIST_DIR}/irccd.h ${CMAKE_CURRENT_LIST_DIR}/irccdctl.h @@ -99,9 +98,8 @@ ${CMAKE_CURRENT_LIST_DIR}/cmd-server-topic.cpp ${CMAKE_CURRENT_LIST_DIR}/cmd-watch.cpp ${CMAKE_CURRENT_LIST_DIR}/command.cpp - ${CMAKE_CURRENT_LIST_DIR}/directory.cpp ${CMAKE_CURRENT_LIST_DIR}/elapsed-timer.cpp - ${CMAKE_CURRENT_LIST_DIR}/filesystem.cpp + ${CMAKE_CURRENT_LIST_DIR}/fs.cpp ${CMAKE_CURRENT_LIST_DIR}/ini.cpp ${CMAKE_CURRENT_LIST_DIR}/irccd.cpp ${CMAKE_CURRENT_LIST_DIR}/irccdctl.cpp
--- a/lib/irccd/directory.cpp Thu Mar 31 12:39:08 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,188 +0,0 @@ -/* - * directory.cpp -- open and read directories - * - * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <sstream> -#include <stdexcept> - -#include "directory.h" - -#if defined(_WIN32) -# include <Windows.h> -#else -# include <cstring> -# include <cerrno> - -# include <sys/types.h> -# include <dirent.h> -#endif - -namespace irccd { - -#if defined(_WIN32) - -namespace { - -std::string systemError() -{ - LPSTR error = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&error, 0, NULL); - - if (error) { - errmsg = std::string(error); - LocalFree(error); - } - - return errmsg; -} - -} // !namespace - -void Directory::systemLoad(const std::string &path, int flags) -{ - std::ostringstream oss; - HANDLE handle; - WIN32_FIND_DATA fdata; - - oss << path << "\\*"; - handle = FindFirstFile(oss.str().c_str(), &fdata); - - if (handle == nullptr) - throw std::runtime_error(systemError()); - - do { - DirectoryEntry entry; - - entry.name = fdata.cFileName; - if (entry.name == "." && !(flags & Directory::Dot)) - continue; - if (entry.name == ".." && !(flags & Directory::DotDot)) - continue; - - switch (fdata.dwFileAttributes) { - case FILE_ATTRIBUTE_DIRECTORY: - entry.type = DirectoryEntry::Dir; - break; - case FILE_ATTRIBUTE_NORMAL: - entry.type = DirectoryEntry::File; - break; - case FILE_ATTRIBUTE_REPARSE_POINT: - entry.type = DirectoryEntry::Link; - break; - default: - break; - } - - m_list.push_back(entry); - } while (FindNextFile(handle, &fdata) != 0); - - FindClose(handle); -} - -#else - -void Directory::systemLoad(const std::string &path, int flags) -{ - DIR *dp; - struct dirent *ent; - - if ((dp = opendir(path.c_str())) == nullptr) - throw std::runtime_error(std::strerror(errno)); - - while ((ent = readdir(dp)) != nullptr) { - DirectoryEntry entry; - - entry.name = ent->d_name; - if (entry.name == "." && !(flags & Directory::Dot)) - continue; - if (entry.name == ".." && !(flags & Directory::DotDot)) - continue; - - switch (ent->d_type) { - case DT_DIR: - entry.type = DirectoryEntry::Dir; - break; - case DT_REG: - entry.type = DirectoryEntry::File; - break; - case DT_LNK: - entry.type = DirectoryEntry::Link; - break; - default: - break; - } - - m_list.push_back(entry); - } - - closedir(dp); -} - -#endif - -bool operator==(const DirectoryEntry &e1, const DirectoryEntry &e2) -{ - return e1.name == e2.name && e1.type == e2.type; -} - -Directory::Directory() -{ -} - -Directory::Directory(const std::string &path, int flags) -{ - systemLoad(path, flags); -} - -Directory::List::iterator Directory::begin() -{ - return m_list.begin(); -} - -Directory::List::const_iterator Directory::cbegin() const -{ - return m_list.cbegin(); -} - -Directory::List::iterator Directory::end() -{ - return m_list.end(); -} - -Directory::List::const_iterator Directory::cend() const -{ - return m_list.cend(); -} - -int Directory::count() const -{ - return m_list.size(); -} - -bool operator==(const Directory &d1, const Directory &d2) -{ - return d1.m_list == d2.m_list; -} - -} // !irccd
--- a/lib/irccd/directory.h Thu Mar 31 12:39:08 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -/* - * directory.h -- open and read directories - * - * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _DIRECTORY_H_ -#define _DIRECTORY_H_ - -#include <cstddef> -#include <string> -#include <vector> - -namespace irccd { - -/** - * @class Entry - * @brief entry in the directory list - */ -class DirectoryEntry { -public: - /** - * @enum Type - * @brief Describe the type of an entry - */ - enum Type { - Unknown = 0, - File, - Dir, - Link - }; - - std::string name; //! name of entry (base name) - Type type{Unknown}; //! type of file - - friend bool operator==(const DirectoryEntry &e1, const DirectoryEntry &e2); -}; - -/** - * @class Directory - * @brief class to manipulate directories - * - * This class allow the user to iterate directories in a for range based - * loop using iterators. - */ -class Directory { -public: - /** - * @enum Flags - * @brief optional flags to read directories - */ - enum Flags { - Dot = (1 << 0), //!< If set, lists "." too - DotDot = (1 << 1) //!< If set, lists ".." too - }; - - using List = std::vector<DirectoryEntry>; - - // C++ Container compatibility - using value_type = List::value_type; - using iterator = List::iterator; - using const_iterator = List::const_iterator; - -private: - List m_list; - - void systemLoad(const std::string &path, int flags); - -public: - /** - * Default constructor, does nothing. - */ - Directory(); - - /** - * Open a directory and read all its content. - * @param path the path - * @param flags the optional flags - */ - Directory(const std::string &path, int flags = 0); - - /** - * Virtual destructor defaulted. - */ - virtual ~Directory() = default; - - /** - * Return an iterator the beginning. - * - * @return the iterator - */ - List::iterator begin(); - - /** - * Return a const iterator the beginning. - * - * @return the iterator - */ - List::const_iterator cbegin() const; - - /** - * Return an iterator to past the end. - * - * @return the iterator - */ - List::iterator end(); - - /** - * Return a const iterator to past the end. - * - * @return the iterator - */ - List::const_iterator cend() const; - - /** - * Get the number of entries in the directory. - * - * @return the number - */ - int count() const; - - friend bool operator==(const Directory &d1, const Directory &d2); -}; - -} // !irccd - -#endif // !_DIRECTORY_H_
--- a/lib/irccd/filesystem.cpp Thu Mar 31 12:39:08 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -/* - * filesystem.cpp -- some file system operation - * - * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cerrno> -#include <cstdio> -#include <cstdlib> -#include <cstring> -#include <stdexcept> -#include <sstream> - -#include <irccd-config.h> - -#if defined(IRCCD_SYSTEM_WINDOWS) -# include <direct.h> -# include <shlwapi.h> -#else -# include <sys/stat.h> -# include <climits> -# include <unistd.h> -# include <libgen.h> -#endif - -#include "filesystem.h" - -namespace irccd { - -namespace fs { - -#if defined(IRCCD_SYSTEM_WINDOWS) -const char Separator('\\'); -#else -const char Separator('/'); -#endif - -std::string baseName(std::string path) -{ -#if defined(IRCCD_SYSTEM_WINDOWS) - std::size_t pos; - - pos = path.find_last_of('\\'); - if (pos == std::string::npos) - pos = path.find_last_of('/'); - if (pos == std::string::npos) - return path; - - return path.substr(pos + 1); -#else - return basename(&path[0]); -#endif -} - -std::string dirName(std::string path) -{ -#if defined(IRCCD_SYSTEM_WINDOWS) - std::size_t pos; - - pos = path.find_last_of('\\'); - if (pos == std::string::npos) - pos = path.find_last_of('/'); - if (pos == std::string::npos) - return path; - - return path.substr(0, pos); -#else - return dirname(&path[0]); -#endif -} - -bool isAbsolute(const std::string &path) noexcept -{ -#if defined(IRCCD_SYSTEM_WINDOWS) - return !isRelative(path); -#else - return path.size() > 0 && path[0] == '/'; -#endif -} - -bool isRelative(const std::string &path) noexcept -{ -#if defined(IRCCD_SYSTEM_WINDOWS) - return PathIsRelativeA(path.c_str()); -#else - return !isAbsolute(path); -#endif -} - -bool exists(const std::string &path) -{ -#if defined(HAVE_ACCESS) - return access(path.c_str(), F_OK) == 0; -#elif defined(HAVE_STAT) - struct stat st; - - return (stat(path.c_str(), &st) == 0); -#else - // worse fallback - std::FILE *file = std::fopen(path.c_str(), "r"); - bool result; - - if (file != nullptr) { - result = true; - std::fclose(file); - } else { - result = false; - } - - return result; -#endif -} - -void mkdir(const std::string &dir, int mode) -{ - std::ostringstream oss; - - oss << "mkdir: "; - - for (std::size_t i = 0; i < dir.length(); ++i) { - if (dir[i] != '/' && dir[i] != '\\') - continue; - - std::string part = dir.substr(0, i); - if (part.length() <= 0 || exists(part)) - continue; - -#if defined(IRCCD_SYSTEM_WINDOWS) - if (::_mkdir(part.c_str()) < 0) { -#else - if (::mkdir(part.c_str(), mode) < 0) { -#endif - oss << part << ": " << std::strerror(errno); - throw std::runtime_error(oss.str()); - } - } - - // Last part -#if defined(IRCCD_SYSTEM_WINDOWS) - if (::_mkdir(dir.c_str()) < 0) { -#else - if (::mkdir(dir.c_str(), mode) < 0) { -#endif - oss << dir << ": " << std::strerror(errno); - throw std::runtime_error(oss.str()); - } - -#if defined(IRCCD_SYSTEM_WINDOWS) - // Windows's mkdir does not use mode. - (void)mode; -#endif -} - -std::string cwd() -{ -#if defined(IRCCD_SYSTEM_WINDOWS) - char path[MAX_PATH]; - - if (!GetCurrentDirectoryA(sizeof (path), path)) - throw std::runtime_error("failed to get current working directory"); - - return path; -#else - char path[PATH_MAX]; - - if (getcwd(path, sizeof (path)) == nullptr) - throw std::runtime_error(std::strerror(errno)); - - return path; -#endif -} - -} // !fs - -} // !irccd
--- a/lib/irccd/filesystem.h Thu Mar 31 12:39:08 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -/* - * filesystem.h -- some file system operation - * - * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _IRCCD_FILESYSTEM_H_ -#define _IRCCD_FILESYSTEM_H_ - -#include <string> - -namespace irccd { - -namespace fs { - -extern const char Separator; - -std::string baseName(std::string path); -std::string dirName(std::string path); -bool isAbsolute(const std::string &path) noexcept; -bool isRelative(const std::string &path) noexcept; -bool exists(const std::string &path); -void mkdir(const std::string &dir, int mode = 0700); -std::string cwd(); - -} // !fs - -} // !irccd - -#endif // !_IRCCD_FILESYSTEM_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/fs.cpp Thu Mar 31 13:27:05 2016 +0200 @@ -0,0 +1,383 @@ +/* + * fs.cpp -- filesystem operations + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <algorithm> +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <stdexcept> + +#if defined(_WIN32) +# include <direct.h> +# include <Windows.h> +# include <Shlwapi.h> +#else +# include <sys/types.h> +# include <dirent.h> +#endif + +#if defined(HAVE_ACCESS) +# include <unistd.h> +#endif + +#include "fs.h" + +namespace irccd { + +namespace fs { + +namespace { + +#if defined(_WIN32) + +std::string error() +{ + LPSTR error = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&error, 0, nullptr); + + if (error) { + errmsg = std::string(error); + LocalFree(error); + } + + return errmsg; +} + +#endif + +bool can(const std::string &path, const std::string &mode) +{ + auto fp = std::fopen(path.c_str(), mode.c_str()); + auto result = fp != nullptr; + + std::fclose(fp); + + return result; +} + +#if defined(_WIN32) + +bool is(const std::string &path, DWORD flags) +{ + DWORD result = GetFileAttributes(path.c_str()); + + if (result == INVALID_FILE_ATTRIBUTES) + return false; + + return result & flags; +} + +#else + +template <typename Predicate> +bool is(const std::string &path, Predicate &&predicate) noexcept +{ + struct stat st; + + if (::stat(path.c_str(), &st) < 0) + return false; + + return predicate(st); +} + +#endif + +} // !namespace + +std::string clean(std::string input) +{ + if (input.empty()) + return input; + + /* First, remove any duplicates */ + input.erase(std::unique(input.begin(), input.end(), [&] (char c1, char c2) { + return c1 == c2 && (c1 == '/' || c1 == '\\'); + }), input.end()); + + /* Add a trailing / or \\ */ + char c = input[input.length() - 1]; + if (c != '/' && c != '\\') + input += separator(); + + /* Now converts all / to \\ for Windows and the opposite for Unix */ +#if defined(_WIN32) + std::replace(input.begin(), input.end(), '/', '\\'); +#else + std::replace(input.begin(), input.end(), '\\', '/'); +#endif + + return input; +} + +std::string baseName(std::string path) +{ + auto pos = path.find_last_of("\\/"); + + if (pos != std::string::npos) + path = path.substr(0, pos + 1); + + return path.substr(pos + 1); +} + +std::string dirName(std::string path) +{ + auto pos = path.find_last_of("\\/"); + + if (pos == std::string::npos) + path = "."; + else + path = path.substr(0, pos); + + return path; +} + +bool isAbsolute(const std::string &path) noexcept +{ +#if defined(_WIN32) + return !isRelative(path); +#else + return path.size() > 0 && path[0] == '/'; +#endif +} + +bool isRelative(const std::string &path) noexcept +{ +#if defined(_WIN32) + return PathIsRelativeA(path.c_str()); +#else + return !isAbsolute(path); +#endif +} + +bool isReadable(const std::string &path) noexcept +{ + return can(path, "r"); +} + +bool isWritable(const std::string &path) noexcept +{ + return can(path, "w"); +} + +bool isFile(const std::string &path) noexcept +{ +#if defined(_WIN32) + return is(path, FILE_ATTRIBUTE_ARCHIVE); +#else + return is(path, [] (const struct stat &st) { return S_ISREG(st.st_mode); }); +#endif +} + +bool isDirectory(const std::string &path) noexcept +{ +#if defined(_WIN32) + return is(path, FILE_ATTRIBUTE_DIRECTORY); +#else + return is(path, [] (const struct stat &st) { return S_ISDIR(st.st_mode); }); +#endif +} + +bool isSymlink(const std::string &path) noexcept +{ +#if defined(_WIN32) + return is(path, FILE_ATTRIBUTE_REPARSE_POINT); +#else + return is(path, [] (const struct stat &st) { return S_ISLNK(st.st_mode); }); +#endif +} + +struct stat stat(const std::string &path) +{ + struct stat st; + + if (stat(path.c_str(), &st) < 0) + throw std::runtime_error(std::strerror(errno)); + + return st; +} + +bool exists(const std::string &path) noexcept +{ +#if defined(HAVE_ACCESS) + return ::access(path.c_str(), F_OK) == 0; +#else + struct stat st; + + return ::stat(path.c_str(), &st) == 0; +#endif +} + +std::vector<Entry> readdir(const std::string &path, int flags) +{ + std::vector<Entry> entries; + +#if defined(_WIN32) + std::ostringstream oss; + HANDLE handle; + WIN32_FIND_DATA fdata; + + oss << path << "\\*"; + handle = FindFirstFile(oss.str().c_str(), &fdata); + + if (handle == nullptr) + throw std::runtime_error{error()}; + + do { + Entry entry; + + entry.name = fdata.cFileName; + + if (entry.name == "." && !(flags & Dot)) + continue; + if (entry.name == ".." && !(flags & DotDot)) + continue; + + switch (fdata.dwFileAttributes) { + case FILE_ATTRIBUTE_DIRECTORY: + entry.type = Entry::Dir; + break; + case FILE_ATTRIBUTE_NORMAL: + entry.type = Entry::File; + break; + case FILE_ATTRIBUTE_REPARSE_POINT: + entry.type = Entry::Link; + break; + default: + break; + } + + entries.push_back(std::move(entry)); + } while (FindNextFile(handle, &fdata) != 0); + + FindClose(handle); +#else + DIR *dp; + struct dirent *ent; + + if ((dp = opendir(path.c_str())) == nullptr) + throw std::runtime_error(std::strerror(errno)); + + while ((ent = readdir(dp)) != nullptr) { + Entry entry; + + entry.name = ent->d_name; + if (entry.name == "." && !(flags & Dot)) + continue; + if (entry.name == ".." && !(flags & DotDot)) + continue; + + switch (ent->d_type) { + case DT_DIR: + entry.type = Entry::Dir; + break; + case DT_REG: + entry.type = Entry::File; + break; + case DT_LNK: + entry.type = Entry::Link; + break; + default: + break; + } + + entries.push_back(std::move(entry)); + } + + closedir(dp); +#endif + + return entries; +} + +void mkdir(const std::string &path, int mode) +{ + std::string::size_type next = 0; + std::string part; + + for (;;) { + next = path.find_first_of("\\/", next); + part = path.substr(0, next); + + if (!part.empty()) { +#if defined(_WIN32) + (void)mode; + + if (_mkdir(part.c_str()) < 0 && errno != EEXIST) + throw std::runtime_error(std::strerror(errno)); +#else + if (mkdir(part.c_str(), mode) < 0 && errno != EEXIST) + throw std::runtime_error(std::strerror(errno)); +#endif + } + + if (next++ == std::string::npos) + break; + } +} + +void rmdir(const std::string &base) noexcept +{ + try { + for (const auto &entry : readdir(base)) { + std::string path = base + separator() + entry.name; + + if (entry.type == Entry::Dir) + rmdir(path); + else + (void)::remove(path.c_str()); + } + } catch (...) { + /* Silently discard to remove as much as possible */ + } + +#if defined(_WIN32) + RemoveDirectory(base.c_str()); +#else + (void)::remove(base.c_str()); +#endif +} + +std::string cwd() +{ +#if defined(_WIN32) + char path[MAX_PATH]; + + if (!GetCurrentDirectoryA(sizeof (path), path)) + throw std::runtime_error{"failed to get current working directory"}; + + return path; +#else + char path[PATH_MAX]; + + if (getcwd(path, sizeof (path)) == nullptr) + throw std::runtime_error{std::strerror(errno)}; + + return path; +#endif +} + +} // !fs + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/fs.h Thu Mar 31 13:27:05 2016 +0200 @@ -0,0 +1,320 @@ +/* + * fs.h -- filesystem operations + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_FS_H +#define IRCCD_FS_H + +/** + * @file fs.h + * @brief Filesystem operations made easy. + * + * The following options can be set by the user: + * + * - **HAVE_ACCESS**: (bool) Set to true if unistd.h file and access(2) function are available. + * + * On Windows, you must link to shlwapi library. + */ + +#include <sys/stat.h> + +#include <regex> +#include <string> +#include <vector> + +namespace irccd { + +namespace fs { + +/** + * @enum Flags + * @brief Flags for readdir. + */ +enum Flags { + Dot = (1 << 0), //!< if set, also lists "." + DotDot = (1 << 1) //!< if set, also lists ".." +}; + +/** + * @class Entry + * @brief Entry in the directory list. + */ +class Entry { +public: + /** + * @brief Describe the type of an entry + */ + enum Type : char { + Unknown, //!< File type is unknown, + File, //!< File is regular type, + Dir, //!< File is directory, + Link //!< File is link + }; + + std::string name; //! name of entry (base name) + Type type{Unknown}; //! type of file +}; + +/** + * Check if two entries are identical. + * + * @param e1 the first entry + * @param e2 the second entry + * @return true if they are identical + */ +bool operator==(const Entry &e1, const Entry &e2) noexcept; + +/** + * Check if two entries are different. + * + * @param e1 the first entry + * @param e2 the second entry + * @return true if they are different + */ +bool operator!=(const Entry &e1, const Entry &e2) noexcept; + +/** + * Get the separator for that system. + * + * @return \ on Windows and / otherwise + */ +inline char separator() noexcept +{ +#if defined(_WIN32) + return '\\'; +#else + return '/'; +#endif +} + +/** + * Clean a path by removing any extra / or \ and add a trailing one. + * + * @param path the path + * @return the updated path + */ +std::string clean(std::string path); + +/** + * Get the base name from a path. + * + * Example, baseName("/etc/foo.conf") // foo.conf + * + * @param path the path + * @return the base name + */ +std::string baseName(std::string path); + +/** + * Get the parent directory from a path. + * + * Example, dirName("/etc/foo.conf") // /etc + * + * @param path the path + * @return the parent directory + */ +std::string dirName(std::string path); + +/** + * Get stat information. + * + * @param path the path + * @return the stat information + * @throw std::runtime_error on failure + */ +struct stat stat(const std::string &path); + +/** + * Check if a file exists. + * + * If HAVE_ACCESS is defined, the function access is used, otherwise stat is used. + * + * @param path the path to check + * @return true if the path exists + */ +bool exists(const std::string &path) noexcept; + +/** + * Check if the path is absolute. + * + * @param path the path + * @return true if the path is absolute + */ +bool isAbsolute(const std::string &path) noexcept; + +/** + * Check if the path is relative. + * + * @param path the path + * @return true if the path is absolute + */ +bool isRelative(const std::string &path) noexcept; + +/** + * Check if the file is readable. + * + * @param path the path + * @return true if has read access + */ +bool isReadable(const std::string &path) noexcept; + +/** + * Check if the file is writable. + * + * @param path the path + * @return true if has write access + */ +bool isWritable(const std::string &path) noexcept; + +/** + * Check if the file is a regular file. + * + * @param path the path + * @return true if it is a file and false if not or not readable + */ +bool isFile(const std::string &path) noexcept; + +/** + * Check if the file is a directory. + * + * @param path the path + * @return true if it is a directory and false if not or not readable + */ +bool isDirectory(const std::string &path) noexcept; + +/** + * Check if the file is a symbolic link. + * + * @param path the path + * @return true if it is a symbolic link and false if not or not readable + */ +bool isSymlink(const std::string &path) noexcept; + +/** + * Read a directory and return a list of entries (not recursive). + * + * @param path the directory path + * @param flags the optional flags (see Flags) + * @return the list of entries + * @throw std::runtime_error on failure + */ +std::vector<Entry> readdir(const std::string &path, int flags = 0); + +/** + * Create a directory recursively. + * + * @param path the path + * @param mode the optional mode (not always supported) + * @throw std::runtime_error on failure + * @post all intermediate directories are created + */ +void mkdir(const std::string &path, int mode = 0700); + +/** + * Remove a directory recursively. + * + * If errors happens, they are silently discarded to remove as much as possible. + * + * @param path the path + */ +void rmdir(const std::string &path) noexcept; + +/** + * Search an item recursively. + * + * The predicate must have the following signature: + * void f(const std::string &base, const Entry &entry) + * + * Where: + * - base is the current parent directory in the tree + * - entry is the current entry + * + * @param base the base directory + * @param predicate the predicate + * @return the full path name to the file or empty string if never found + * @throw std::runtime_error on read errors + */ +template <typename Predicate> +std::string findIf(const std::string &base, Predicate &&predicate) +{ + /* + * Do not go deeply to the tree before testing all files in the current directory for performances reasons, we iterate + * this directory to search for the entry name and iterate again over all sub directories if not found. + */ + std::string path; + std::vector<Entry> entries = readdir(base); + + for (const auto &entry : entries) { + if (predicate(base, entry)) { + path = base + separator() + entry.name; + break; + } + } + + if (path.empty()) { + for (const auto &entry : entries) { + if (entry.type == Entry::Dir) { + path = findIf(base + separator() + entry.name, std::forward<Predicate>(predicate)); + + if (!path.empty()) + break; + } + } + } + + return path; +} + +/** + * Find a file by name recursively. + * + * @param base the base directory + * @param name the file name + * @return the full path name to the file or empty string if never found + * @throw std::runtime_error on read errors + */ +inline std::string find(const std::string &base, const std::string &name) +{ + return findIf(base, [&] (const auto &, const auto &entry) { return entry.name == name; }); +} + +/** + * Overload by regular expression. + * + * @param base the base directory + * @param regex the regular expression + * @return the full path name to the file or empty string if never found + * @throw std::runtime_error on read errors + */ +inline std::string find(const std::string &base, const std::regex ®ex) +{ + return findIf(base, [&] (const auto &, const auto &entry) { return std::regex_match(entry.name, regex); }); +} + +/** + * Get the current working directory. + * + * @return the current working directory + * @throw std::runtime_error on failure + */ +std::string cwd(); + +} // !fs + +} // !irccd + +#endif // !IRCCD_FS_H
--- a/lib/irccd/irccd.cpp Thu Mar 31 12:39:08 2016 +0200 +++ b/lib/irccd/irccd.cpp Thu Mar 31 13:27:05 2016 +0200 @@ -20,7 +20,7 @@ #include <stdexcept> #include "irccd.h" -#include "filesystem.h" +#include "fs.h" #include "logger.h" #include "path.h" #include "util.h"
--- a/lib/irccd/irccdctl.cpp Thu Mar 31 12:39:08 2016 +0200 +++ b/lib/irccd/irccdctl.cpp Thu Mar 31 13:27:05 2016 +0200 @@ -26,7 +26,7 @@ #include <irccd-config.h> #include "elapsed-timer.h" -#include "filesystem.h" +#include "fs.h" #include "ini.h" #include "irccdctl.h" #include "json.h"
--- a/lib/irccd/js-directory.cpp Thu Mar 31 12:39:08 2016 +0200 +++ b/lib/irccd/js-directory.cpp Thu Mar 31 13:27:05 2016 +0200 @@ -26,8 +26,7 @@ #include <irccd-config.h> -#include "directory.h" -#include "filesystem.h" +#include "fs.h" #include "js.h" #include "js-irccd.h" #include "path.h" @@ -71,18 +70,18 @@ * not directories to avoid going deeper recursively if the requested * file is in the current directory. */ - Directory directory(base); + auto entries = fs::readdir(base); - for (const DirectoryEntry &entry : directory) - if (entry.type != DirectoryEntry::Dir && pred(entry.name)) + for (const auto &entry : entries) + if (entry.type != fs::Entry::Dir && pred(entry.name)) return base + entry.name; if (!recursive) - throw std::out_of_range("entry not found"); + return ""; - for (const DirectoryEntry &entry : directory) { - if (entry.type == DirectoryEntry::Dir) { - std::string next = base + entry.name + fs::Separator; + for (const auto &entry : entries) { + if (entry.type == fs::Entry::Dir) { + std::string next = base + entry.name + fs::separator(); std::string path = findPath(next, true, pred); if (!path.empty()) @@ -166,26 +165,17 @@ */ duk::Ret remove(duk::ContextPtr ctx, const std::string &path, bool recursive) { - if (!recursive) { - ::remove(path.c_str()); - } else { - try { - Directory directory(path); + if (!fs::isDirectory(path)) + duk::raise(ctx, SystemError(EINVAL, "not a directory")); - for (const DirectoryEntry &entry : directory) { - if (entry.type == DirectoryEntry::Dir) { - (void)remove(ctx, path + fs::Separator + entry.name, true); - } else { - std::string filename = path + fs::Separator + entry.name; - - ::remove(filename.c_str()); - } - } - - ::remove(path.c_str()); - } catch (const std::exception &) { - // TODO: put the error in a log. - } + if (!recursive) { +#if defined(_WIN32) + ::RemoveDirectory(path.c_str()); +#else + ::remove(path.c_str()); +#endif + } else { + fs::rmdir(path.c_str()); } return 0; @@ -202,7 +192,9 @@ * - pattern, the regular expression or file name, * - recursive, set to true to search recursively (default: false). * Returns: - * The path to the file or undefined on errors or not found + * The path to the file or undefined if not found. + * Throws: + * - Any exception on error. */ duk::Ret methodFind(duk::ContextPtr ctx) { @@ -253,24 +245,29 @@ return 0; try { - Directory directory(duk::require<std::string>(ctx, 0), duk::optional<int>(ctx, 1, 0)); + std::string path = duk::require<std::string>(ctx, 0); + std::int8_t flags = duk::optional<int>(ctx, 1, 0); + + if (!fs::isDirectory(path)) + duk::raise(ctx, SystemError(EINVAL, "not a directory")); + + std::vector<fs::Entry> list = fs::readdir(path, flags); duk::push(ctx, duk::This{}); duk::push(ctx, "count"); - duk::push(ctx, directory.count()); + duk::push(ctx, (int)list.size()); duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE); duk::push(ctx, "path"); - duk::push(ctx, duk::require<std::string>(ctx, 0)); + duk::push(ctx, path); duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE); duk::push(ctx, "entries"); duk::push(ctx, duk::Array{}); - int i = 0; - for (const DirectoryEntry &entry : directory) { + for (unsigned i = 0; i < list.size(); ++i) { duk::push(ctx, duk::Object{}); - duk::putProperty(ctx, -1, "name", entry.name); - duk::putProperty(ctx, -1, "type", static_cast<int>(entry.type)); - duk::putProperty(ctx, -2, i++); + duk::putProperty(ctx, -1, "name", list[i].name); + duk::putProperty(ctx, -1, "type", static_cast<int>(list[i].type)); + duk::putProperty(ctx, -2, (int)i); } duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE); @@ -347,12 +344,12 @@ }; const duk::Map<int> constants{ - { "Dot", static_cast<int>(Directory::Dot) }, - { "DotDot", static_cast<int>(Directory::DotDot) }, - { "TypeUnknown", static_cast<int>(DirectoryEntry::Unknown) }, - { "TypeDir", static_cast<int>(DirectoryEntry::Dir) }, - { "TypeFile", static_cast<int>(DirectoryEntry::File) }, - { "TypeLink", static_cast<int>(DirectoryEntry::Link) } + { "Dot", static_cast<int>(fs::Dot) }, + { "DotDot", static_cast<int>(fs::DotDot) }, + { "TypeUnknown", static_cast<int>(fs::Entry::Unknown) }, + { "TypeDir", static_cast<int>(fs::Entry::Dir) }, + { "TypeFile", static_cast<int>(fs::Entry::File) }, + { "TypeLink", static_cast<int>(fs::Entry::Link) } }; } // !namespace @@ -365,7 +362,7 @@ duk::push(ctx, duk::Function{constructor, 2}); duk::push(ctx, constants); duk::push(ctx, functions); - duk::putProperty(ctx, -1, "separator", std::string{fs::Separator}); + duk::putProperty(ctx, -1, "separator", std::string{fs::separator()}); duk::push(ctx, duk::Object{}); duk::push(ctx, methods); duk::putProperty(ctx, -2, "prototype");
--- a/lib/irccd/js-file.cpp Thu Mar 31 12:39:08 2016 +0200 +++ b/lib/irccd/js-file.cpp Thu Mar 31 13:27:05 2016 +0200 @@ -26,13 +26,13 @@ # include <sys/stat.h> #endif -#include "filesystem.h" +#include "fs.h" #include "js-irccd.h" #include "js-file.h" -#if defined(HAVE_STAT) +namespace irccd { -namespace irccd { +#if defined(HAVE_STAT) /* * duk::TypeInfo specialization for struct stat
--- a/lib/irccd/path.cpp Thu Mar 31 12:39:08 2016 +0200 +++ b/lib/irccd/path.cpp Thu Mar 31 13:27:05 2016 +0200 @@ -56,7 +56,7 @@ # include "xdg.h" #endif -#include "filesystem.h" +#include "fs.h" #include "path.h" #include "system.h" #include "util.h" @@ -185,7 +185,7 @@ return base + WITH_CONFDIR; #else - return fs::isAbsolute(WITH_CONFDIR) ? WITH_CONFDIR : std::string(PREFIX) + fs::Separator + WITH_CONFDIR; + return fs::isAbsolute(WITH_CONFDIR) ? WITH_CONFDIR : std::string(PREFIX) + fs::separator() + WITH_CONFDIR; #endif } @@ -196,7 +196,7 @@ return base + WITH_DATADIR; #else - return fs::isAbsolute(WITH_DATADIR) ? WITH_CONFDIR : std::string(PREFIX) + fs::Separator + WITH_DATADIR; + return fs::isAbsolute(WITH_DATADIR) ? WITH_CONFDIR : std::string(PREFIX) + fs::separator() + WITH_DATADIR; #endif } @@ -207,7 +207,7 @@ return base + WITH_CACHEDIR; #else - return fs::isAbsolute(WITH_CACHEDIR) ? WITH_CACHEDIR : std::string(PREFIX) + fs::Separator + WITH_CACHEDIR; + return fs::isAbsolute(WITH_CACHEDIR) ? WITH_CACHEDIR : std::string(PREFIX) + fs::separator() + WITH_CACHEDIR; #endif } @@ -218,7 +218,7 @@ return base + WITH_PLUGINDIR; #else - return fs::isAbsolute(WITH_PLUGINDIR) ? WITH_PLUGINDIR : std::string(PREFIX) + fs::Separator + WITH_PLUGINDIR; + return fs::isAbsolute(WITH_PLUGINDIR) ? WITH_PLUGINDIR : std::string(PREFIX) + fs::separator() + WITH_PLUGINDIR; #endif } @@ -415,7 +415,7 @@ std::string name = fs::baseName(argv0); for (const auto &dir : util::split(sys::env("PATH"), std::string(1, Separator))) { - std::string path = dir + fs::Separator + name; + std::string path = dir + fs::separator() + name; if (fs::exists(path)) { base = path; @@ -425,12 +425,12 @@ /* Not found in PATH? add dummy value */ if (base.empty()) - base = std::string(".") + fs::Separator + WITH_BINDIR + fs::Separator + "dummy"; + base = std::string(".") + fs::separator() + WITH_BINDIR + fs::separator() + "dummy"; } } /* Find bin/<progname> */ - auto pos = base.rfind(std::string(WITH_BINDIR) + fs::Separator + fs::baseName(base)); + auto pos = base.rfind(std::string(WITH_BINDIR) + fs::separator() + fs::baseName(base)); if (pos != std::string::npos) base.erase(pos); @@ -457,7 +457,7 @@ /* Add a trailing / or \\ */ char c = input[input.length() - 1]; if (c != '/' && c != '\\') - input += fs::Separator; + input += fs::separator(); /* Now converts all / to \\ for Windows and the opposite for Unix */ #if defined(IRCCD_SYSTEM_WINDOWS)