* limitations under the License.
*/
#include "filesystem_utils.h"
+#include "common/logger.h"
+#include "common/platform_exception.h"
+#include <dirent.h>
+#include <fcntl.h>
+#include <ftw.h>
#include <glib.h>
#include <libgen.h>
-#include "common/logger.h"
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
namespace FilesystemUtils {
-std::string get_storage_dir_path(int id, storage_directory_e typeToCheck) {
+using namespace std::string_literals;
+using namespace common;
+using common::tools::ReportError;
+
+void Mkdir(const std::string& path) {
+ ScopeLogger("%s", path.c_str());
+ int ret = ::mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
+
+ if (0 != ret) {
+ throw std::system_error{errno, std::generic_category()};
+ }
+}
+
+void Mkdir(const std::string& path, bool parents) {
+ // ScopeLogger("%s, %d", path.c_str(), parents); // disabled in recursive function
+ if (!parents) {
+ Mkdir(path);
+ return;
+ }
+
+ struct ::stat buf;
+ std::vector<std::string> stack;
+ // iterate path up to first existing element
+ for (std::string s = path; 0 != ::stat(s.c_str(), &buf); s = Dirname(s)) {
+ if (ENOENT == errno) {
+ stack.push_back(s);
+ } else {
+ throw std::system_error{errno, std::generic_category()};
+ }
+ }
+
+ if (stack.empty()) { // this means, that path exists, let Mkdir handle error
+ Mkdir(path);
+ return;
+ }
+
+ // from top to bottom
+ for (auto rit = stack.rbegin(); rit != stack.rend(); ++rit) {
+ try {
+ Mkdir(*rit);
+ } catch (const std::system_error& error) {
+ if (stack.rbegin() != rit) {
+ try {
+ RemoveDirectoryRecursively(*stack.rbegin());
+ } catch (const std::system_error& removalError) {
+ LoggerW(
+ "Could not remove parent directories created so far: %s."
+ "Some of them might still exist",
+ removalError.what());
+ }
+ }
+ throw;
+ }
+ }
+}
+
+void Unlink(const std::string& path) {
+ ScopeLogger();
+ int ret = ::unlink(path.c_str());
+ if (0 != ret) {
+ throw std::system_error{errno, std::generic_category()};
+ }
+}
+
+std::string PosixBasename(const std::string& path) {
+ ScopeLogger();
+ char* s = ::strdup(path.c_str());
+ if (!s) {
+ throw std::system_error{errno, std::generic_category(), path};
+ }
+
+ // basename will never fail
+ std::string name{::basename(s)};
+ free(s);
+ return name;
+}
+
+std::string Dirname(const std::string& path) {
+ ScopeLogger();
+ char* s = ::strdup(path.c_str());
+ if (!s) {
+ throw std::system_error{errno, std::generic_category(), path};
+ }
+ // dirname will never fail
+ std::string dir{::dirname(s)};
+ free(s);
+ return dir;
+}
+
+void CopyFile(const std::string& src, const std::string& dest, bool overwrite) {
+ ScopeLogger("From: %s; To %s", src.c_str(), dest.c_str());
+ struct stat buf {};
+ if (CheckIfExists(dest, &buf) && CheckIfDir(buf)) {
+ if (overwrite) {
+ RemoveDirectoryRecursively(dest);
+ } else {
+ throw std::system_error{EIO, std::generic_category(),
+ "Failed to copy file: overwrite is not allowed."};
+ }
+ }
+
+ GError* error = nullptr;
+ auto source_ptr = std::unique_ptr<GFile, decltype(&g_object_unref)>(
+ g_file_new_for_path(src.c_str()), g_object_unref);
+ auto dest_ptr = std::unique_ptr<GFile, decltype(&g_object_unref)>(
+ g_file_new_for_path(dest.c_str()), g_object_unref);
+ int flags = G_FILE_COPY_ALL_METADATA;
+ if (overwrite) {
+ flags |= G_FILE_COPY_OVERWRITE;
+ }
+
+ gboolean success =
+ g_file_copy(source_ptr.get(), dest_ptr.get(), static_cast<GFileCopyFlags>(flags), nullptr,
+ nullptr, nullptr, &error);
+ if (!success) {
+ std::string why{};
+ if (error) {
+ why = error->message;
+ g_error_free(error);
+ }
+ throw std::system_error{EIO, std::generic_category(), "Failed to copy file: "s + why};
+ }
+}
+
+void CopyDirectory(const std::string& src, const std::string& dest, bool overwrite) {
+ ScopeLogger("From: %s; To %s", src.c_str(), dest.c_str());
+ std::string dest_dir = dest + '/' + PosixBasename(src);
+ struct stat buf {};
+ bool exists = CheckIfExists(dest_dir, &buf);
+ if (exists && !CheckIfDir(buf)) {
+ if (overwrite) {
+ Unlink(dest_dir);
+ Mkdir(dest_dir);
+ } else {
+ throw std::system_error{EIO, std::generic_category(),
+ "Failed to copy directory: overwrite is not allowed."};
+ }
+ } else if (!exists) {
+ Mkdir(dest_dir);
+ }
+
+ ListDirectory(src, [&](const char* name, unsigned char type) {
+ if (DT_DIR == type) {
+ CopyDirectory(src + '/' + name, dest_dir, overwrite);
+ } else { // copying of regular files as well as other types of items pointed by src
+ CopyFile(src + '/' + name, dest_dir + '/' + name, overwrite);
+ }
+ // Set errno to 0 to prevent from reporting readdir error after successful iterating through
+ // directory.
+ errno = 0;
+ });
+}
+
+void ListDirectory(const std::string& path, std::function<void(const char*, unsigned char)> next) {
+ ScopeLogger("%s", path.c_str());
+ DIR* d = ::opendir(path.c_str());
+ if (nullptr == d) {
+ throw std::system_error{errno, std::generic_category(),
+ "Failed to open directory: "s + std::strerror(errno)};
+ }
+
+ std::unique_ptr<DIR, void (*)(DIR*)> dir_ptr(d, [](DIR * d) {
+ if (::closedir(d)) {
+ LoggerW("closedir failed");
+ }
+ });
+ errno = 0;
+
+ for (dirent* entry; (entry = ::readdir(d));) {
+ if (0 == std::strcmp(entry->d_name, ".") || 0 == std::strcmp(entry->d_name, "..")) {
+ continue;
+ }
+ next(entry->d_name, entry->d_type);
+ }
+
+ if (0 != errno) {
+ throw std::system_error{errno, std::generic_category(),
+ "Failed to read directory: "s + std::strerror(errno)};
+ }
+}
+
+void RemoveDirectoryRecursively(const std::string& path) {
+ ScopeLogger("%s", path);
+ auto res =
+ nftw(path.c_str(),
+ [](const char* fpath, const struct stat* sb, int typeflag, struct FTW* ftwbuf) -> int {
+ // if number of nested directories is large
+ // below log could decrease readability
+ // ScopeLogger("%s", fpath);
+
+ auto res = remove(fpath);
+ if (res) {
+ LoggerD("Failed to remove %s: %s", fpath, std::strerror(errno));
+ return errno;
+ }
+ return 0;
+ },
+ 128, FTW_DEPTH | FTW_PHYS);
+
+ if (res) {
+ throw std::system_error{res, std::generic_category(),
+ "Failed to remove directory recursively: "s + std::strerror(res)};
+ }
+}
+
+void RemoveDirectory(const std::string& path) {
+ ScopeLogger();
+ if (rmdir(path.c_str())) {
+ throw std::system_error{errno, std::generic_category(), "Failed to remove directory"};
+ }
+}
+
+std::string RealPath(const std::string& path) {
ScopeLogger();
- char* platformPath = NULL;
- int result = storage_get_directory(id, typeToCheck, &platformPath);
- if (STORAGE_ERROR_NONE != result) {
- LoggerD("Cannot retrieve path for type %i: %d (%s)", typeToCheck, result,
- get_error_message(result));
- return std::string();
+ char* real_path = realpath(path.c_str(), nullptr);
+ if (nullptr == real_path) {
+ throw std::system_error{errno, std::generic_category(), "Path is not valid."};
}
- std::string path = std::string(platformPath);
- free(platformPath);
- return path;
+ std::string s{real_path};
+ free(real_path);
+ return s;
}
-std::string get_dirname(const std::string& path) {
+bool CheckIfExists(const std::string& path, struct stat* buf) {
+ ScopeLogger();
+ if (stat(path.c_str(), buf)) {
+ if (ENOENT == errno) {
+ return false;
+ } else {
+ throw std::system_error{errno, std::generic_category(),
+ "Unable to check file existence: "s + std::strerror(errno)};
+ }
+ }
+ return true;
+}
+
+bool CheckIfDir(const struct stat& buf) {
+ ScopeLogger();
+ if (S_ISDIR(buf.st_mode)) {
+ return true;
+ }
+ return false;
+}
+
+bool CheckIfFile(const struct stat& buf) {
+ ScopeLogger();
+ if (S_ISREG(buf.st_mode)) {
+ return true;
+ }
+ return false;
+}
+
+void Rename(const std::string& path, const std::string& new_path) {
+ ScopeLogger();
+ if (::rename(path.c_str(), new_path.c_str())) {
+ throw std::system_error{errno, std::generic_category(),
+ "Unable to rename file or directory: "s + std::strerror(errno)};
+ }
+}
+
+void MoveFile(const std::string& path, const std::string& new_path, bool overwrite) {
+ ScopeLogger();
+ GError* error = nullptr;
+ auto source_ptr = std::unique_ptr<GFile, decltype(&g_object_unref)>(
+ g_file_new_for_path(path.c_str()), g_object_unref);
+ auto dest_ptr = std::unique_ptr<GFile, decltype(&g_object_unref)>(
+ g_file_new_for_path(new_path.c_str()), g_object_unref);
+ int flags = G_FILE_COPY_ALL_METADATA;
+ if (overwrite) {
+ flags |= G_FILE_COPY_OVERWRITE;
+ }
+ gboolean success =
+ g_file_move(source_ptr.get(), dest_ptr.get(), static_cast<GFileCopyFlags>(flags), nullptr,
+ nullptr, nullptr, &error);
+ if (!success) {
+ std::string why{};
+ if (error) {
+ why = error->message;
+ g_error_free(error);
+ }
+ throw std::system_error{EIO, std::generic_category(), "Failed to move file: "s + why};
+ }
+}
+
+void MoveDirectory(const std::string& src, const std::string& dest, bool overwrite) {
+ ScopeLogger("%s %s", src.c_str(), dest.c_str());
+ struct stat buf {};
+ const std::string& new_path = dest + '/' + PosixBasename(src);
+ // If directories are on the same mount point, we can simply try to rename them.
+ // However, it might be done only if new_path does not exist because move_directory should merge
+ // directories.
+ if (!CheckIfExists(new_path, &buf)) {
+ LoggerD("new_path %s", new_path.c_str());
+ auto result = ::rename(src.c_str(), new_path.c_str());
+ if (!result) {
+ return;
+ } else if (EXDEV != errno) {
+ // The directories are in the same mount point, but the operation has just failed.
+ throw std::system_error{EIO, std::generic_category(),
+ "Unable to move directory: "s + std::strerror(errno)};
+ }
+ }
+
+ // Move directory to other move point.
+ CopyDirectory(src, dest, overwrite);
+ RemoveDirectoryRecursively(src);
+}
+
+void TranslateException(const std::system_error& e, picojson::object& obj) {
+ ScopeLogger();
+ if (std::errc::no_such_file_or_directory == e.code()) {
+ LogAndReportError(NotFoundException(e.what()), obj);
+ } else {
+ LogAndReportError(IOException(e.what()), obj);
+ }
+}
+
+std::string GetDirname(const std::string& path) {
ScopeLogger();
char* dir = g_path_get_dirname(path.c_str());
if (dir) {
return std::string(".");
}
}
-
-std::string get_basename(const std::string& path) {
- ScopeLogger();
- // basename will modify content: pass a copy
- std::string buf = path.c_str();
- return std::string(basename(const_cast<char*>(buf.c_str())));
-}
}
#ifndef FILESYSTEM_FILESYSTEM_UTILS_H
#define FILESYSTEM_FILESYSTEM_UTILS_H
-#include <storage-expand.h>
-#include <string>
#include "common/picojson.h"
+#include "common/tools.h"
+
+#include <gio/gio.h>
+#include <glib-object.h>
+#include <sys/stat.h>
+#include <functional>
+#include <memory>
+#include <string>
+#include <system_error>
namespace extension {
namespace filesystem {
namespace FilesystemUtils {
/**
- * @brief get_storage_dir_path attempts to get path from storage.
- * If path cannot be retrieved then an empty string is returned.
- *
+ * @brief Wrapper for POSIX mkdir function.
+ * @throw std::system_error
+ */
+void Mkdir(const std::string& path);
+
+/**
+ * @brief Make directory using mkdir. If 'parents' is true, make parent directories as needed
+ * @throw std::system_error
*/
-std::string get_storage_dir_path(int id, storage_directory_e typeToCheck);
+void Mkdir(const std::string& path, bool parents);
+
+/**
+ * @brief Wrapper for POSIX unlink function
+ * @throw std::system_error
+ */
+void Unlink(const std::string& path);
+
+/**
+ * @brief Returns last element of path (wrapper for POSIX basename function)
+ * @throw std::system_error
+ */
+std::string PosixBasename(const std::string& path);
+
+/**
+ * @brief Returns parent directory of path (wrapper for POSIX dirname function)
+ * @throw std::system_error
+ */
+std::string Dirname(const std::string& path);
+
+/**
+ * @brief Wrapper for GLib g_file_copy function.
+ * @throw std::system_error
+ */
+void CopyFile(const std::string& src, const std::string& dest, bool overwrite);
+
+/**
+ * @brief Copies directory recursively
+ * @throw std::system_error
+ */
+void CopyDirectory(const std::string& src, const std::string& dest, bool overwrite);
+
+/**
+ * @brief Calls 'next' function with name for every entry in given directory
+ * @throw std::system_error
+ */
+void ListDirectory(const std::string& path, std::function<void(const char*, unsigned char)> next);
+
+/**
+ * @brief Removes directory recursively pointed by path.
+ * @throw std::system_error
+ */
+void RemoveDirectoryRecursively(const std::string& path);
+
+/**
+ * @brief Removes directory pointed by path.
+ * @throw std::system_error
+ */
+void RemoveDirectory(const std::string& path);
+
+/**
+ * @brief Returns the real path.
+ * @throw std::system_error
+ */
+std::string RealPath(const std::string& path);
+
+/**
+ * @brief Checks if path points to file or directory.
+ * @throw std::system_error
+ */
+bool CheckIfExists(const std::string& path, struct stat* buf);
+
+/**
+ * @brief Checks if path points to directory.
+ * @throw std::system_error
+ */
+bool CheckIfDir(const struct stat& buf);
+
+/**
+ * @brief Checks if path points to file.
+ * @throw std::system_error
+ */
+bool CheckIfFile(const struct stat& buf);
+
+/**
+ * @brief Renames file or directory.
+ * @throw std::system_error
+ */
+void Rename(const std::string& path, const std::string& new_path);
+
+/**
+ * @brief Wrapper for GLib g_file_move function.
+ * @throw std::system_error
+ */
+void MoveFile(const std::string& path, const std::string& new_path, bool overwrite);
+
+/**
+ * @brief Moves directory by recursively calling move_file.
+ * @throw std::system_error
+ */
+void MoveDirectory(const std::string& path, const std::string& new_path, bool overwrite);
+
+void TranslateException(const std::system_error& e, picojson::object& obj);
-std::string get_dirname(const std::string& path);
-std::string get_basename(const std::string& path);
+// This function is left only for compatibility with previous implementation in FilesystemManager
+std::string GetDirname(const std::string& path);
}
#endif // FILESYSTEM_FILESYSTEM_UTILS_H