Mercurial > code
diff modules/fs/fs.cpp @ 519:d6dad57e9e6b
Fs: import
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 01 Jun 2016 16:35:01 +0200 |
parents | |
children | ecf5fb9319da |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/fs/fs.cpp Wed Jun 01 16:35:01 2016 +0200 @@ -0,0 +1,493 @@ +/* + * 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. + */ + +#if defined(_WIN32) +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include <algorithm> +#include <cassert> +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <sstream> +#include <stdexcept> + +#if defined(_WIN32) +# include <direct.h> +# include <Windows.h> +# include <Shlwapi.h> +#else +# include <sys/types.h> +# include <dirent.h> +# include <unistd.h> +#endif + +#include "fs.hpp" + +namespace fs { + +namespace { + +/* + * error. + * ------------------------------------------------------------------ + * + * Function to retrieve system error in Windows API. + */ +#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 + +/* + * hasAccess. + * ------------------------------------------------------------------ + * + * Check if we have access to the file specified, mode is the same used as std::fopen. + */ +bool hasAccess(const std::string &path, const std::string &mode) +{ + assert(mode.length() == 1); + assert(mode[0] == 'r' || mode[0] == 'w'); + + auto fp = std::fopen(path.c_str(), mode.c_str()); + + if (fp == nullptr) + return false; + + std::fclose(fp); + + return true; +} + +/* + * typeOf. + * ------------------------------------------------------------------ + * + * Get the type of the specified file. + * + * Use GetFileAttributesA on Windows and stat if available. + * + * Receives the object as predicate parameter and return true on success. + */ +#if defined(_WIN32) + +template <typename Predicate> +bool typeOf(const std::string &path, Predicate &&predicate) +{ + DWORD result = GetFileAttributesA(path.c_str()); + + if (result == INVALID_FILE_ATTRIBUTES) + return false; + + return predicate(result); +} + +#elif defined(FS_HAVE_STAT) + +template <typename Predicate> +bool typeOf(const std::string &path, Predicate &&predicate) noexcept +{ + struct stat st; + + if (::stat(path.c_str(), &st) < 0) + return false; + + return predicate(st); +} + +#else + +template <typename Predicate> +bool typeOf(const std::string &path, Predicate &&predicate) noexcept +{ + throw std::runtime_error(std::strerror(ENOSYS)); +} + +#endif + +} // !namespace + +/* + * clean. + * ------------------------------------------------------------------ + */ +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; +} + +/* + * baseName. + * ------------------------------------------------------------------ + */ +std::string baseName(std::string path) +{ + auto pos = path.find_last_of("\\/"); + + if (pos != std::string::npos) + path = path.substr(pos + 1); + + return path; +} + +/* + * dirName. + * ------------------------------------------------------------------ + */ +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; +} + +/* + * isAbsolute. + * ------------------------------------------------------------------ + */ +bool isAbsolute(const std::string &path) noexcept +{ +#if defined(_WIN32) + return !isRelative(path); +#else + return path.size() > 0 && path[0] == '/'; +#endif +} + +/* + * isRelative. + * ------------------------------------------------------------------ + */ +bool isRelative(const std::string &path) noexcept +{ +#if defined(_WIN32) + return PathIsRelativeA(path.c_str()) == 1; +#else + return !isAbsolute(path); +#endif +} + +/* + * isReadable. + * ------------------------------------------------------------------ + */ +bool isReadable(const std::string &path) noexcept +{ + return hasAccess(path, "r"); +} + +/* + * isWritable. + * ------------------------------------------------------------------ + */ +bool isWritable(const std::string &path) noexcept +{ + return hasAccess(path, "w"); +} + +/* + * isFile. + * ------------------------------------------------------------------ + */ +bool isFile(const std::string &path) +{ + return typeOf(path, [] (const auto &object) { +#if defined(_WIN32) + return (object & FILE_ATTRIBUTE_ARCHIVE) == FILE_ATTRIBUTE_ARCHIVE; +#elif defined(FS_HAVE_STAT) + return S_ISREG(object.st_mode); +#endif + }); +} + +/* + * isDirectory. + * ------------------------------------------------------------------ + */ +bool isDirectory(const std::string &path) +{ + return typeOf(path, [] (const auto &object) { +#if defined(_WIN32) + return (object & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY; +#elif defined(FS_HAVE_STAT) + return S_ISDIR(object.st_mode); +#endif + }); +} + +/* + * isSymlink. + * ------------------------------------------------------------------ + */ +bool isSymlink(const std::string &path) +{ + return typeOf(path, [] (const auto &object) { +#if defined(_WIN32) + return (object & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT; +#elif defined(FS_HAVE_STAT) + return S_ISLNK(object.st_mode); +#endif + }); +} + +/* + * stat. + * ------------------------------------------------------------------ + */ +#if defined(FS_HAVE_STAT) + +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; +} + +#endif + +/* + * exists. + * ------------------------------------------------------------------ + */ +bool exists(const std::string &path) noexcept +{ +#if defined(FS_HAVE_STAT) + struct stat st; + + return ::stat(path.c_str(), &st) == 0; +#else + return hasAccess(path, "r"); +#endif +} + +/* + * readdir. + * ------------------------------------------------------------------ + */ +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; +} + +/* + * mkdir. + * ------------------------------------------------------------------ + */ +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; + } +} + +/* + * rmdir. + * ------------------------------------------------------------------ + */ +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 + remove(path.c_str()); + } + } catch (...) { + /* Silently discard to remove as much as possible */ + } + +#if defined(_WIN32) + RemoveDirectoryA(base.c_str()); +#else + remove(base.c_str()); +#endif +} + +/* + * cwd. + * ------------------------------------------------------------------ + */ +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