view modules/fs/fs.cpp @ 555:730358cb0648

Fs: style and do not define _CRT_SECURE_NO_WARNINGS if already defined
author David Demelier <markand@malikania.fr>
date Thu, 16 Jun 2016 12:44:24 +0200
parents ecf5fb9319da
children 67da1bacd884
line wrap: on
line source

/*
 * 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) && !defined(_CRT_SECURE_NO_WARNINGS)
#   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